Completed
Push — master ( 71797f...8f0aef )
by Song
02:24
created

Column::view()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 8
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($title, $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, $delimiter = '&nbsp;')
40
 * @method $this suffix($suffix, $delimiter = '&nbsp;')
41
 * @method $this secret($dotCount = 6)
42
 * @method $this limit($limit = 100, $end = '...')
43
 */
44
class Column
45
{
46
    use Column\HasHeader;
47
48
    const SELECT_COLUMN_NAME = '__row_selector__';
49
50
    const ACTION_COLUMN_NAME = '__actions__';
51
52
    /**
53
     * @var Grid
54
     */
55
    protected $grid;
56
57
    /**
58
     * Name of column.
59
     *
60
     * @var string
61
     */
62
    protected $name;
63
64
    /**
65
     * Label of column.
66
     *
67
     * @var string
68
     */
69
    protected $label;
70
71
    /**
72
     * Original value of column.
73
     *
74
     * @var mixed
75
     */
76
    protected $original;
77
78
    /**
79
     * Attributes of column.
80
     *
81
     * @var array
82
     */
83
    protected $attributes = [];
84
85
    /**
86
     * Relation name.
87
     *
88
     * @var bool
89
     */
90
    protected $relation = false;
91
92
    /**
93
     * Relation column.
94
     *
95
     * @var string
96
     */
97
    protected $relationColumn;
98
99
    /**
100
     * Original grid data.
101
     *
102
     * @var Collection
103
     */
104
    protected static $originalGridModels;
105
106
    /**
107
     * @var []Closure
108
     */
109
    protected $displayCallbacks = [];
110
111
    /**
112
     * Displayers for grid column.
113
     *
114
     * @var array
115
     */
116
    public static $displayers = [
117
        'editable'      => Displayers\Editable::class,
118
        'switch'        => Displayers\SwitchDisplay::class,
119
        'switchGroup'   => Displayers\SwitchGroup::class,
120
        'select'        => Displayers\Select::class,
121
        'image'         => Displayers\Image::class,
122
        'label'         => Displayers\Label::class,
123
        'button'        => Displayers\Button::class,
124
        'link'          => Displayers\Link::class,
125
        'badge'         => Displayers\Badge::class,
126
        'progressBar'   => Displayers\ProgressBar::class,
127
        'progress'      => Displayers\ProgressBar::class,
128
        'radio'         => Displayers\Radio::class,
129
        'checkbox'      => Displayers\Checkbox::class,
130
        'orderable'     => Displayers\Orderable::class,
131
        'table'         => Displayers\Table::class,
132
        'expand'        => Displayers\Expand::class,
133
        'modal'         => Displayers\Modal::class,
134
        'carousel'      => Displayers\Carousel::class,
135
        'downloadable'  => Displayers\Downloadable::class,
136
        'copyable'      => Displayers\Copyable::class,
137
        'qrcode'        => Displayers\QRCode::class,
138
        'prefix'        => Displayers\Prefix::class,
139
        'suffix'        => Displayers\Suffix::class,
140
        'secret'        => Displayers\Secret::class,
141
        'limit'         => Displayers\Limit::class,
142
        'belongsTo'     => Displayers\BelongsTo::class,
143
        'belongsToMany' => Displayers\BelongsToMany::class,
144
    ];
145
146
    /**
147
     * Defined columns.
148
     *
149
     * @var array
150
     */
151
    public static $defined = [];
152
153
    /**
154
     * @var array
155
     */
156
    protected static $htmlAttributes = [];
157
158
    /**
159
     * @var array
160
     */
161
    protected static $rowAttributes = [];
162
163
    /**
164
     * @var Model
165
     */
166
    protected static $model;
167
168
    /**
169
     * @var bool
170
     */
171
    protected $searchable = false;
172
173
    /**
174
     * @param string $name
175
     * @param string $label
176
     */
177
    public function __construct($name, $label)
178
    {
179
        $this->name = $name;
180
181
        $this->label = $this->formatLabel($label);
182
183
        $this->initAttributes();
184
    }
185
186
    /**
187
     * Initialize column attributes.
188
     */
189
    protected function initAttributes()
190
    {
191
        $name = str_replace('.', '-', $this->name);
192
193
        $this->setAttributes(['class' => "column-{$name}"]);
194
    }
195
196
    /**
197
     * Extend column displayer.
198
     *
199
     * @param $name
200
     * @param $displayer
201
     */
202
    public static function extend($name, $displayer)
203
    {
204
        static::$displayers[$name] = $displayer;
205
    }
206
207
    /**
208
     * Define a column globally.
209
     *
210
     * @param string $name
211
     * @param mixed  $definition
212
     */
213
    public static function define($name, $definition)
214
    {
215
        static::$defined[$name] = $definition;
216
    }
217
218
    /**
219
     * Set grid instance for column.
220
     *
221
     * @param Grid $grid
222
     */
223
    public function setGrid(Grid $grid)
224
    {
225
        $this->grid = $grid;
226
227
        $this->setModel($grid->model()->eloquent());
0 ignored issues
show
Bug introduced by
The method eloquent does only exist in Encore\Admin\Grid\Model, but not in Illuminate\Database\Eloquent\Builder.

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...
228
    }
229
230
    /**
231
     * Set model for column.
232
     *
233
     * @param $model
234
     */
235
    public function setModel($model)
236
    {
237
        if (is_null(static::$model) && ($model instanceof BaseModel)) {
238
            static::$model = $model->newInstance();
239
        }
240
    }
241
242
    /**
243
     * Set original data for column.
244
     *
245
     * @param Collection $collection
246
     */
247
    public static function setOriginalGridModels(Collection $collection)
248
    {
249
        static::$originalGridModels = $collection;
250
    }
251
252
    /**
253
     * Set column attributes.
254
     *
255
     * @param array $attributes
256
     *
257
     * @return $this
258
     */
259
    public function setAttributes($attributes = [], $key = null)
260
    {
261
        if ($key) {
262
            static::$rowAttributes[$this->name][$key] = array_merge(
263
                Arr::get(static::$rowAttributes, "{$this->name}.{$key}", []),
264
                $attributes
265
            );
266
267
            return $this;
268
        }
269
270
        static::$htmlAttributes[$this->name] = array_merge(
271
            Arr::get(static::$htmlAttributes, $this->name, []),
272
            $attributes
273
        );
274
275
        return $this;
276
    }
277
278
    /**
279
     * Get column attributes.
280
     *
281
     * @param string $name
282
     *
283
     * @return mixed
284
     */
285
    public static function getAttributes($name, $key = null)
286
    {
287
        $rowAttributes = [];
288
289
        if ($key && Arr::has(static::$rowAttributes, "{$name}.{$key}")) {
290
            $rowAttributes = Arr::get(static::$rowAttributes, "{$name}.{$key}", []);
291
        }
292
293
        $columnAttributes = Arr::get(static::$htmlAttributes, $name, []);
294
295
        return array_merge($rowAttributes, $columnAttributes);
296
    }
297
298
    /**
299
     * Format attributes to html.
300
     *
301
     * @return string
302
     */
303
    public function formatHtmlAttributes()
304
    {
305
        $attrArr = [];
306
        foreach (static::getAttributes($this->name) as $name => $val) {
307
            $attrArr[] = "$name=\"$val\"";
308
        }
309
310
        return implode(' ', $attrArr);
311
    }
312
313
    /**
314
     * Set style of this column.
315
     *
316
     * @param string $style
317
     *
318
     * @return $this
319
     */
320
    public function style($style)
321
    {
322
        return $this->setAttributes(compact('style'));
323
    }
324
325
    /**
326
     * Set the width of column.
327
     *
328
     * @param int $width
329
     *
330
     * @return $this
331
     */
332
    public function width(int $width)
333
    {
334
        return $this->style("width: {$width}px;max-width: {$width}px;word-wrap: break-word;word-break: normal;");
335
    }
336
337
    /**
338
     * Set the color of column.
339
     *
340
     * @param string $color
341
     *
342
     * @return $this
343
     */
344
    public function color($color)
345
    {
346
        return $this->style("color:$color;");
347
    }
348
349
    /**
350
     * Get original column value.
351
     *
352
     * @return mixed
353
     */
354
    public function getOriginal()
355
    {
356
        return $this->original;
357
    }
358
359
    /**
360
     * Get name of this column.
361
     *
362
     * @return mixed
363
     */
364
    public function getName()
365
    {
366
        return $this->name;
367
    }
368
369
    /**
370
     * @return string
371
     */
372
    public function getClassName()
373
    {
374
        $name = str_replace('.', '-', $this->getName());
375
376
        return "column-{$name}";
377
    }
378
379
    /**
380
     * Format label.
381
     *
382
     * @param $label
383
     *
384
     * @return mixed
385
     */
386
    protected function formatLabel($label)
387
    {
388
        if ($label) {
389
            return $label;
390
        }
391
392
        $label = ucfirst($this->name);
393
394
        return __(str_replace(['.', '_'], ' ', $label));
395
    }
396
397
    /**
398
     * Get label of the column.
399
     *
400
     * @return mixed
401
     */
402
    public function getLabel()
403
    {
404
        return $this->label;
405
    }
406
407
    /**
408
     * Set relation.
409
     *
410
     * @param string $relation
411
     * @param string $relationColumn
412
     *
413
     * @return $this
414
     */
415
    public function setRelation($relation, $relationColumn = null)
416
    {
417
        $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...
418
        $this->relationColumn = $relationColumn;
419
420
        return $this;
421
    }
422
423
    /**
424
     * If this column is relation column.
425
     *
426
     * @return bool
427
     */
428
    protected function isRelation()
429
    {
430
        return (bool) $this->relation;
431
    }
432
433
    /**
434
     * Mark this column as sortable.
435
     *
436
     * @param null|string $cast
437
     *
438
     * @return Column|string
439
     */
440
    public function sortable($cast = null)
441
    {
442
        return $this->addSorter($cast);
443
    }
444
445
    /**
446
     * Set cast name for sortable.
447
     *
448
     * @return $this
449
     *
450
     * @deprecated Use `$column->sortable($cast)` instead.
451
     */
452
    public function cast($cast)
453
    {
454
        $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...
455
456
        return $this;
457
    }
458
459
    /**
460
     * Set help message for column.
461
     *
462
     * @param string $help
463
     *
464
     * @return $this|string
465
     */
466
    public function help($help = '')
467
    {
468
        return $this->addHelp($help);
469
    }
470
471
    /**
472
     * Set column filter.
473
     *
474
     * @param mixed|null $builder
475
     *
476
     * @return $this
477
     */
478
    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...
479
    {
480
        return $this->addFilter(...func_get_args());
481
    }
482
483
    /**
484
     * Set column as searchable.
485
     *
486
     * @return $this
487
     */
488
    public function searchable()
489
    {
490
        $this->searchable = true;
491
492
        $name = $this->getName();
493
        $query = request()->query();
494
495
        $this->prefix(function ($_, $original) use ($name, $query) {
496
            Arr::set($query, $name, $original);
497
498
            $url = request()->fullUrlWithQuery($query);
499
500
            return "<a href=\"{$url}\"><i class=\"fa fa-search\"></i></a>";
501
        }, '&nbsp;&nbsp;');
502
503
        return $this;
504
    }
505
506
    /**
507
     * Bind search query to grid model.
508
     *
509
     * @param Model $model
510
     */
511
    public function bindSearchQuery(Model $model)
512
    {
513
        if ($this->searchable && ($value = request($this->getName())) != '') {
514
            $model->where($this->getName(), $value);
0 ignored issues
show
Documentation Bug introduced by
The method where does not exist on object<Encore\Admin\Grid\Model>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
515
        }
516
    }
517
518
    /**
519
     * Add a display callback.
520
     *
521
     * @param Closure $callback
522
     *
523
     * @return $this
524
     */
525
    public function display(Closure $callback)
526
    {
527
        $this->displayCallbacks[] = $callback;
528
529
        return $this;
530
    }
531
532
    /**
533
     * Display using display abstract.
534
     *
535
     * @param string $abstract
536
     * @param array  $arguments
537
     *
538
     * @return $this
539
     */
540
    public function displayUsing($abstract, $arguments = [])
541
    {
542
        $grid = $this->grid;
543
544
        $column = $this;
545
546 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...
547
            /** @var AbstractDisplayer $displayer */
548
            $displayer = new $abstract($value, $grid, $column, $this);
549
550
            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...
551
        });
552
    }
553
554
    /**
555
     * Display column using array value map.
556
     *
557
     * @param array $values
558
     * @param null  $default
559
     *
560
     * @return $this
561
     */
562 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...
563
    {
564
        return $this->display(function ($value) use ($values, $default) {
565
            if (is_null($value)) {
566
                return $default;
567
            }
568
569
            return Arr::get($values, $value, $default);
570
        });
571
    }
572
573
    /**
574
     * Replace output value with giving map.
575
     *
576
     * @param array $replacements
577
     *
578
     * @return $this
579
     */
580
    public function replace(array $replacements)
581
    {
582
        return $this->display(function ($value) use ($replacements) {
583
            if (isset($replacements[$value])) {
584
                return $replacements[$value];
585
            }
586
587
            return $value;
588
        });
589
    }
590
591
    /**
592
     * Render this column with the given view.
593
     *
594
     * @param string $view
595
     *
596
     * @return $this
597
     */
598
    public function view($view)
599
    {
600
        return $this->display(function ($value) use ($view) {
601
            $model = $this;
602
603
            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...
604
        });
605
    }
606
607
    /**
608
     * Hide this column by default.
609
     *
610
     * @return $this
611
     */
612
    public function hide()
613
    {
614
        $this->grid->hideColumns($this->getName());
615
616
        return $this;
617
    }
618
619
    /**
620
     * Add column to total-row.
621
     *
622
     * @param null $display
623
     *
624
     * @return $this
625
     */
626
    public function totalRow($display = null)
627
    {
628
        $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...
629
630
        return $this;
631
    }
632
633
    /**
634
     * Convert file size to a human readable format like `100mb`.
635
     *
636
     * @return $this
637
     */
638
    public function filesize()
639
    {
640
        return $this->display(function ($value) {
641
            return file_size($value);
642
        });
643
    }
644
645
    /**
646
     * Display the fields in the email format as gavatar.
647
     *
648
     * @param int $size
649
     *
650
     * @return $this
651
     */
652
    public function gravatar($size = 30)
653
    {
654
        return $this->display(function ($value) use ($size) {
655
            $src = sprintf(
656
                'https://www.gravatar.com/avatar/%s?s=%d',
657
                md5(strtolower($value)),
658
                $size
659
            );
660
661
            return "<img src='$src' class='img img-circle'/>";
662
        });
663
    }
664
665
    /**
666
     * Display field as a loading icon.
667
     *
668
     * @param array $values
669
     * @param array $others
670
     *
671
     * @return $this
672
     */
673
    public function loading($values = [], $others = [])
674
    {
675
        return $this->display(function ($value) use ($values, $others) {
676
            $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...
677
678
            if (in_array($value, $values)) {
679
                return '<i class="fa fa-refresh fa-spin text-primary"></i>';
680
            }
681
682
            return Arr::get($others, $value, $value);
683
        });
684
    }
685
686
    /**
687
     * Display column as an font-awesome icon based on it's value.
688
     *
689
     * @param array  $setting
690
     * @param string $default
691
     *
692
     * @return $this
693
     */
694
    public function icon(array $setting, $default = '')
695
    {
696
        return $this->display(function ($value) use ($setting, $default) {
697
            $fa = '';
698
699
            if (isset($setting[$value])) {
700
                $fa = $setting[$value];
701
            } elseif ($default) {
702
                $fa = $default;
703
            }
704
705
            return "<i class=\"fa fa-{$fa}\"></i>";
706
        });
707
    }
708
709
    /**
710
     * Return a human readable format time.
711
     *
712
     * @param null $locale
713
     *
714
     * @return $this
715
     */
716
    public function diffForHumans($locale = null)
717
    {
718
        if ($locale) {
719
            Carbon::setLocale($locale);
720
        }
721
722
        return $this->display(function ($value) {
723
            return Carbon::parse($value)->diffForHumans();
724
        });
725
    }
726
727
    /**
728
     * Returns a string formatted according to the given format string.
729
     *
730
     * @param string $format
731
     *
732
     * @return $this
733
     */
734
    public function date($format)
735
    {
736
        return $this->display(function ($value) use ($format) {
737
            return date($format, strtotime($value));
738
        });
739
    }
740
741
    /**
742
     * Display column as boolean , `✓` for true, and `✗` for false.
743
     *
744
     * @param array $map
745
     * @param bool  $default
746
     *
747
     * @return $this
748
     */
749
    public function bool(array $map = [], $default = false)
750
    {
751
        return $this->display(function ($value) use ($map, $default) {
752
            $bool = empty($map) ? boolval($value) : Arr::get($map, $value, $default);
753
754
            return $bool ? '<i class="fa fa-check text-green"></i>' : '<i class="fa fa-close text-red"></i>';
755
        });
756
    }
757
758
    /**
759
     * Display column as a default value if empty.
760
     *
761
     * @param string $default
762
     * @return $this
763
     */
764
    public function default($default = '-')
765
    {
766
        return $this->display(function ($value) use ($default) {
767
            return $value ?: $default;
768
        });
769
    }
770
771
    /**
772
     * Display column using a grid row action.
773
     *
774
     * @param string $action
775
     *
776
     * @return $this
777
     */
778
    public function action($action)
779
    {
780
        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...
781
            throw new \InvalidArgumentException("Action class [$action] must be sub-class of [Encore\Admin\Actions\GridAction]");
782
        }
783
784
        $grid = $this->grid;
785
786
        return $this->display(function ($_, $column) use ($action, $grid) {
787
            /** @var RowAction $action */
788
            $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...
789
790
            return $action
791
                ->asColumn()
792
                ->setGrid($grid)
793
                ->setColumn($column)
794
                ->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...
795
        });
796
    }
797
798
    /**
799
     * Add a `dot` before column text.
800
     *
801
     * @param array  $options
802
     * @param string $default
803
     *
804
     * @return $this
805
     */
806
    public function dot($options = [], $default = '')
807
    {
808
        return $this->prefix(function ($_, $original) use ($options, $default) {
809
            if (is_null($original)) {
810
                $style = $default;
811
            } else {
812
                $style = Arr::get($options, $original, $default);
813
            }
814
815
            return "<span class=\"label-{$style}\" style='width: 8px;height: 8px;padding: 0;border-radius: 50%;display: inline-block;'></span>";
816
        }, '&nbsp;&nbsp;');
817
    }
818
819
    /**
820
     * @param string $selectable
821
     *
822
     * @return $this
823
     */
824 View Code Duplication
    public function belongsTo($selectable)
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...
825
    {
826
        if (method_exists($selectable, 'display')) {
827
            $this->display($selectable::display());
828
        }
829
830
        return $this->displayUsing(Grid\Displayers\BelongsTo::class, [$selectable]);
831
    }
832
833
    /**
834
     * @param string $selectable
835
     *
836
     * @return $this
837
     */
838 View Code Duplication
    public function belongsToMany($selectable)
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...
839
    {
840
        if (method_exists($selectable, 'display')) {
841
            $this->display($selectable::display());
842
        }
843
844
        return $this->displayUsing(Grid\Displayers\BelongsToMany::class, [$selectable]);
845
    }
846
847
    /**
848
     * If has display callbacks.
849
     *
850
     * @return bool
851
     */
852
    protected function hasDisplayCallbacks()
853
    {
854
        return !empty($this->displayCallbacks);
855
    }
856
857
    /**
858
     * Call all of the "display" callbacks column.
859
     *
860
     * @param mixed $value
861
     * @param int   $key
862
     *
863
     * @return mixed
864
     */
865
    protected function callDisplayCallbacks($value, $key)
866
    {
867
        foreach ($this->displayCallbacks as $callback) {
868
            $previous = $value;
869
870
            $callback = $this->bindOriginalRowModel($callback, $key);
871
            $value = call_user_func_array($callback, [$value, $this]);
872
873
            if (($value instanceof static) &&
874
                ($last = array_pop($this->displayCallbacks))
875
            ) {
876
                $last = $this->bindOriginalRowModel($last, $key);
877
                $value = call_user_func($last, $previous);
878
            }
879
        }
880
881
        return $value;
882
    }
883
884
    /**
885
     * Set original grid data to column.
886
     *
887
     * @param Closure $callback
888
     * @param int     $key
889
     *
890
     * @return Closure
891
     */
892
    protected function bindOriginalRowModel(Closure $callback, $key)
893
    {
894
        $rowModel = static::$originalGridModels[$key];
895
896
        return $callback->bindTo($rowModel);
897
    }
898
899
    /**
900
     * Fill all data to every column.
901
     *
902
     * @param array $data
903
     *
904
     * @return mixed
905
     */
906
    public function fill(array $data)
907
    {
908
        foreach ($data as $key => &$row) {
909
            $this->original = $value = Arr::get($row, $this->name);
910
911
            $value = $this->htmlEntityEncode($value);
912
913
            Arr::set($row, $this->name, $value);
914
915
            if ($this->isDefinedColumn()) {
916
                $this->useDefinedColumn();
917
            }
918
919
            if ($this->hasDisplayCallbacks()) {
920
                $value = $this->callDisplayCallbacks($this->original, $key);
921
                Arr::set($row, $this->name, $value);
922
            }
923
        }
924
925
        return $data;
926
    }
927
928
    /**
929
     * If current column is a defined column.
930
     *
931
     * @return bool
932
     */
933
    protected function isDefinedColumn()
934
    {
935
        return array_key_exists($this->name, static::$defined);
936
    }
937
938
    /**
939
     * Use a defined column.
940
     *
941
     * @throws \Exception
942
     */
943
    protected function useDefinedColumn()
944
    {
945
        // clear all display callbacks.
946
        $this->displayCallbacks = [];
947
948
        $class = static::$defined[$this->name];
949
950
        if ($class instanceof Closure) {
951
            $this->display($class);
952
953
            return;
954
        }
955
956
        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...
957
            throw new \Exception("Invalid column definition [$class]");
958
        }
959
960
        $grid = $this->grid;
961
        $column = $this;
962
963
        $this->display(function ($value) use ($grid, $column, $class) {
964
            /** @var AbstractDisplayer $definition */
965
            $definition = new $class($value, $grid, $column, $this);
966
967
            return $definition->display();
968
        });
969
    }
970
971
    /**
972
     * Convert characters to HTML entities recursively.
973
     *
974
     * @param array|string $item
975
     *
976
     * @return mixed
977
     */
978
    protected function htmlEntityEncode($item)
979
    {
980
        if (is_array($item)) {
981
            array_walk_recursive($item, function (&$value) {
982
                $value = htmlentities($value);
983
            });
984
        } else {
985
            $item = htmlentities($item);
986
        }
987
988
        return $item;
989
    }
990
991
    /**
992
     * Find a displayer to display column.
993
     *
994
     * @param string $abstract
995
     * @param array  $arguments
996
     *
997
     * @return $this
998
     */
999
    protected function resolveDisplayer($abstract, $arguments)
1000
    {
1001
        if (array_key_exists($abstract, static::$displayers)) {
1002
            return $this->callBuiltinDisplayer(static::$displayers[$abstract], $arguments);
1003
        }
1004
1005
        return $this->callSupportDisplayer($abstract, $arguments);
1006
    }
1007
1008
    /**
1009
     * Call Illuminate/Support displayer.
1010
     *
1011
     * @param string $abstract
1012
     * @param array  $arguments
1013
     *
1014
     * @return $this
1015
     */
1016
    protected function callSupportDisplayer($abstract, $arguments)
1017
    {
1018
        return $this->display(function ($value) use ($abstract, $arguments) {
1019
            if (is_array($value) || $value instanceof Arrayable) {
1020
                return call_user_func_array([collect($value), $abstract], $arguments);
1021
            }
1022
1023
            if (is_string($value)) {
1024
                return call_user_func_array([Str::class, $abstract], array_merge([$value], $arguments));
1025
            }
1026
1027
            return $value;
1028
        });
1029
    }
1030
1031
    /**
1032
     * Call Builtin displayer.
1033
     *
1034
     * @param string $abstract
1035
     * @param array  $arguments
1036
     *
1037
     * @return $this
1038
     */
1039
    protected function callBuiltinDisplayer($abstract, $arguments)
1040
    {
1041
        if ($abstract instanceof Closure) {
1042
            return $this->display(function ($value) use ($abstract, $arguments) {
1043
                return $abstract->call($this, ...array_merge([$value], $arguments));
1044
            });
1045
        }
1046
1047
        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...
1048
            $grid = $this->grid;
1049
            $column = $this;
1050
1051 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...
1052
                /** @var AbstractDisplayer $displayer */
1053
                $displayer = new $abstract($value, $grid, $column, $this);
1054
1055
                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...
1056
            });
1057
        }
1058
1059
        return $this;
1060
    }
1061
1062
    /**
1063
     * Passes through all unknown calls to builtin displayer or supported displayer.
1064
     *
1065
     * Allow fluent calls on the Column object.
1066
     *
1067
     * @param string $method
1068
     * @param array  $arguments
1069
     *
1070
     * @return $this
1071
     */
1072
    public function __call($method, $arguments)
1073
    {
1074
        if ($this->isRelation() && !$this->relationColumn) {
1075
            $this->name = "{$this->relation}.$method";
1076
            $this->label = $this->formatLabel($arguments[0] ?? null);
1077
1078
            $this->relationColumn = $method;
1079
1080
            return $this;
1081
        }
1082
1083
        return $this->resolveDisplayer($method, $arguments);
1084
    }
1085
}
1086