Completed
Push — master ( d3f222...49addd )
by Song
02:36
created

Column::upload()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Encore\Admin\Grid;
4
5
use 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
    ];
143
144
    /**
145
     * Defined columns.
146
     *
147
     * @var array
148
     */
149
    public static $defined = [];
150
151
    /**
152
     * @var array
153
     */
154
    protected static $htmlAttributes = [];
155
156
    /**
157
     * @var array
158
     */
159
    protected static $rowAttributes = [];
160
161
    /**
162
     * @var Model
163
     */
164
    protected static $model;
165
166
    /**
167
     * @var bool
168
     */
169
    protected $searchable = false;
170
171
    /**
172
     * @param string $name
173
     * @param string $label
174
     */
175
    public function __construct($name, $label)
176
    {
177
        $this->name = $name;
178
179
        $this->label = $this->formatLabel($label);
180
181
        $this->initAttributes();
182
    }
183
184
    /**
185
     * Initialize column attributes.
186
     */
187
    protected function initAttributes()
188
    {
189
        $name = str_replace('.', '-', $this->name);
190
191
        $this->setAttributes(['class' => "column-{$name}"]);
192
    }
193
194
    /**
195
     * Extend column displayer.
196
     *
197
     * @param $name
198
     * @param $displayer
199
     */
200
    public static function extend($name, $displayer)
201
    {
202
        static::$displayers[$name] = $displayer;
203
    }
204
205
    /**
206
     * Define a column globally.
207
     *
208
     * @param string $name
209
     * @param mixed  $definition
210
     */
211
    public static function define($name, $definition)
212
    {
213
        static::$defined[$name] = $definition;
214
    }
215
216
    /**
217
     * Set grid instance for column.
218
     *
219
     * @param Grid $grid
220
     */
221
    public function setGrid(Grid $grid)
222
    {
223
        $this->grid = $grid;
224
225
        $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...
226
    }
227
228
    /**
229
     * Set model for column.
230
     *
231
     * @param $model
232
     */
233
    public function setModel($model)
234
    {
235
        if (is_null(static::$model) && ($model instanceof BaseModel)) {
236
            static::$model = $model->newInstance();
237
        }
238
    }
239
240
    /**
241
     * Set original data for column.
242
     *
243
     * @param Collection $collection
244
     */
245
    public static function setOriginalGridModels(Collection $collection)
246
    {
247
        static::$originalGridModels = $collection;
248
    }
249
250
    /**
251
     * Set column attributes.
252
     *
253
     * @param array $attributes
254
     *
255
     * @return $this
256
     */
257
    public function setAttributes($attributes = [], $key = null)
258
    {
259
        if ($key) {
260
            static::$rowAttributes[$this->name][$key] = array_merge(
261
                Arr::get(static::$rowAttributes, "{$this->name}.{$key}", []),
262
                $attributes
263
            );
264
265
            return $this;
266
        }
267
268
        static::$htmlAttributes[$this->name] = array_merge(
269
            Arr::get(static::$htmlAttributes, $this->name, []),
270
            $attributes
271
        );
272
273
        return $this;
274
    }
275
276
    /**
277
     * Get column attributes.
278
     *
279
     * @param string $name
280
     *
281
     * @return mixed
282
     */
283
    public static function getAttributes($name, $key = null)
284
    {
285
        $rowAttributes = [];
286
287
        if ($key && Arr::has(static::$rowAttributes, "{$name}.{$key}")) {
288
            $rowAttributes = Arr::get(static::$rowAttributes, "{$name}.{$key}", []);
289
        }
290
291
        $columnAttributes = Arr::get(static::$htmlAttributes, $name, []);
292
293
        return array_merge($rowAttributes, $columnAttributes);
294
    }
295
296
    /**
297
     * Format attributes to html.
298
     *
299
     * @return string
300
     */
301
    public function formatHtmlAttributes()
302
    {
303
        $attrArr = [];
304
        foreach (static::getAttributes($this->name) as $name => $val) {
305
            $attrArr[] = "$name=\"$val\"";
306
        }
307
308
        return implode(' ', $attrArr);
309
    }
310
311
    /**
312
     * Set style of this column.
313
     *
314
     * @param string $style
315
     *
316
     * @return $this
317
     */
318
    public function style($style)
319
    {
320
        return $this->setAttributes(compact('style'));
321
    }
322
323
    /**
324
     * Set the width of column.
325
     *
326
     * @param int $width
327
     *
328
     * @return $this
329
     */
330
    public function width(int $width)
331
    {
332
        return $this->style("width: {$width}px;max-width: {$width}px;word-wrap: break-word;word-break: normal;");
333
    }
334
335
    /**
336
     * Set the color of column.
337
     *
338
     * @param string $color
339
     *
340
     * @return $this
341
     */
342
    public function color($color)
343
    {
344
        return $this->style("color:$color;");
345
    }
346
347
    /**
348
     * Get original column value.
349
     *
350
     * @return mixed
351
     */
352
    public function getOriginal()
353
    {
354
        return $this->original;
355
    }
356
357
    /**
358
     * Get name of this column.
359
     *
360
     * @return mixed
361
     */
362
    public function getName()
363
    {
364
        return $this->name;
365
    }
366
367
    /**
368
     * @return string
369
     */
370
    public function getClassName()
371
    {
372
        $name = str_replace('.', '-', $this->getName());
373
374
        return "column-{$name}";
375
    }
376
377
    /**
378
     * Format label.
379
     *
380
     * @param $label
381
     *
382
     * @return mixed
383
     */
384
    protected function formatLabel($label)
385
    {
386
        if ($label) {
387
            return $label;
388
        }
389
390
        $label = ucfirst($this->name);
391
392
        return __(str_replace(['.', '_'], ' ', $label));
393
    }
394
395
    /**
396
     * Get label of the column.
397
     *
398
     * @return mixed
399
     */
400
    public function getLabel()
401
    {
402
        return $this->label;
403
    }
404
405
    /**
406
     * Set relation.
407
     *
408
     * @param string $relation
409
     * @param string $relationColumn
410
     *
411
     * @return $this
412
     */
413
    public function setRelation($relation, $relationColumn = null)
414
    {
415
        $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...
416
        $this->relationColumn = $relationColumn;
417
418
        return $this;
419
    }
420
421
    /**
422
     * If this column is relation column.
423
     *
424
     * @return bool
425
     */
426
    protected function isRelation()
427
    {
428
        return (bool) $this->relation;
429
    }
430
431
    /**
432
     * Mark this column as sortable.
433
     *
434
     * @param null|string $cast
435
     *
436
     * @return Column|string
437
     */
438
    public function sortable($cast = null)
439
    {
440
        return $this->addSorter($cast);
441
    }
442
443
    /**
444
     * Set cast name for sortable.
445
     *
446
     * @return $this
447
     *
448
     * @deprecated Use `$column->sortable($cast)` instead.
449
     */
450
    public function cast($cast)
451
    {
452
        $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...
453
454
        return $this;
455
    }
456
457
    /**
458
     * Set help message for column.
459
     *
460
     * @param string $help
461
     *
462
     * @return $this|string
463
     */
464
    public function help($help = '')
465
    {
466
        return $this->addHelp($help);
467
    }
468
469
    /**
470
     * Set column filter.
471
     *
472
     * @param mixed|null $builder
473
     *
474
     * @return $this
475
     */
476
    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...
477
    {
478
        return $this->addFilter(...func_get_args());
479
    }
480
481
    /**
482
     * Set column as searchable.
483
     *
484
     * @return $this
485
     */
486
    public function searchable()
487
    {
488
        $this->searchable = true;
489
490
        $name = $this->getName();
491
        $query = request()->query();
492
493
        $this->prefix(function ($_, $original) use ($name, $query) {
494
            Arr::set($query, $name, $original);
495
496
            $url = request()->fullUrlWithQuery($query);
497
498
            return "<a href=\"{$url}\"><i class=\"fa fa-search\"></i></a>";
499
        }, '&nbsp;&nbsp;');
500
501
        return $this;
502
    }
503
504
    /**
505
     * Bind search query to grid model.
506
     *
507
     * @param Model $model
508
     */
509
    public function bindSearchQuery(Model $model)
510
    {
511
        if ($this->searchable && ($value = request($this->getName())) != '') {
512
            $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...
513
        }
514
    }
515
516
    /**
517
     * Add a display callback.
518
     *
519
     * @param Closure $callback
520
     *
521
     * @return $this
522
     */
523
    public function display(Closure $callback)
524
    {
525
        $this->displayCallbacks[] = $callback;
526
527
        return $this;
528
    }
529
530
    /**
531
     * Display using display abstract.
532
     *
533
     * @param string $abstract
534
     * @param array  $arguments
535
     *
536
     * @return $this
537
     */
538
    public function displayUsing($abstract, $arguments = [])
539
    {
540
        $grid = $this->grid;
541
542
        $column = $this;
543
544 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...
545
            /** @var AbstractDisplayer $displayer */
546
            $displayer = new $abstract($value, $grid, $column, $this);
547
548
            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...
549
        });
550
    }
551
552
    /**
553
     * Display column using array value map.
554
     *
555
     * @param array $values
556
     * @param null  $default
557
     *
558
     * @return $this
559
     */
560 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...
561
    {
562
        return $this->display(function ($value) use ($values, $default) {
563
            if (is_null($value)) {
564
                return $default;
565
            }
566
567
            return Arr::get($values, $value, $default);
568
        });
569
    }
570
571
    /**
572
     * Replace output value with giving map.
573
     *
574
     * @param array $replacements
575
     *
576
     * @return $this
577
     */
578
    public function replace(array $replacements)
579
    {
580
        return $this->display(function ($value) use ($replacements) {
581
            if (isset($replacements[$value])) {
582
                return $replacements[$value];
583
            }
584
585
            return $value;
586
        });
587
    }
588
589
    /**
590
     * @param string|Closure $input
591
     * @param string $seperator
592
     *
593
     * @return $this
594
     */
595
    public function repeat($input, $seperator = '')
596
    {
597
        if (is_string($input)) {
598
            $input = function () use ($input) {
599
                return $input;
600
            };
601
        }
602
603
        if ($input instanceof Closure) {
604
            return $this->display(function ($value) use ($input, $seperator) {
605
                return join($seperator, array_fill(0, (int) $value, $input->call($this, [$value])));
606
            });
607
        }
608
609
        return $this;
610
    }
611
612
    /**
613
     * Render this column with the given view.
614
     *
615
     * @param string $view
616
     *
617
     * @return $this
618
     */
619
    public function view($view)
620
    {
621
        return $this->display(function ($value) use ($view) {
622
            $model = $this;
623
624
            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...
625
        });
626
    }
627
628
    /**
629
     * Hide this column by default.
630
     *
631
     * @return $this
632
     */
633
    public function hide()
634
    {
635
        $this->grid->hideColumns($this->getName());
636
637
        return $this;
638
    }
639
640
    /**
641
     * Add column to total-row.
642
     *
643
     * @param null $display
644
     *
645
     * @return $this
646
     */
647
    public function totalRow($display = null)
648
    {
649
        $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...
650
651
        return $this;
652
    }
653
654
    /**
655
     * Convert file size to a human readable format like `100mb`.
656
     *
657
     * @return $this
658
     */
659
    public function filesize()
660
    {
661
        return $this->display(function ($value) {
662
            return file_size($value);
663
        });
664
    }
665
666
    /**
667
     * Display the fields in the email format as gavatar.
668
     *
669
     * @param int $size
670
     *
671
     * @return $this
672
     */
673
    public function gravatar($size = 30)
674
    {
675
        return $this->display(function ($value) use ($size) {
676
            $src = sprintf(
677
                'https://www.gravatar.com/avatar/%s?s=%d',
678
                md5(strtolower($value)),
679
                $size
680
            );
681
682
            return "<img src='$src' class='img img-circle'/>";
683
        });
684
    }
685
686
    /**
687
     * Display field as a loading icon.
688
     *
689
     * @param array $values
690
     * @param array $others
691
     *
692
     * @return $this
693
     */
694
    public function loading($values = [], $others = [])
695
    {
696
        return $this->display(function ($value) use ($values, $others) {
697
            $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...
698
699
            if (in_array($value, $values)) {
700
                return '<i class="fa fa-refresh fa-spin text-primary"></i>';
701
            }
702
703
            return Arr::get($others, $value, $value);
704
        });
705
    }
706
707
    /**
708
     * Display column as an font-awesome icon based on it's value.
709
     *
710
     * @param array  $setting
711
     * @param string $default
712
     *
713
     * @return $this
714
     */
715
    public function icon(array $setting, $default = '')
716
    {
717
        return $this->display(function ($value) use ($setting, $default) {
718
            $fa = '';
719
720
            if (isset($setting[$value])) {
721
                $fa = $setting[$value];
722
            } elseif ($default) {
723
                $fa = $default;
724
            }
725
726
            return "<i class=\"fa fa-{$fa}\"></i>";
727
        });
728
    }
729
730
    /**
731
     * Return a human readable format time.
732
     *
733
     * @param null $locale
734
     *
735
     * @return $this
736
     */
737
    public function diffForHumans($locale = null)
738
    {
739
        if ($locale) {
740
            Carbon::setLocale($locale);
741
        }
742
743
        return $this->display(function ($value) {
744
            return Carbon::parse($value)->diffForHumans();
745
        });
746
    }
747
748
    /**
749
     * Returns a string formatted according to the given format string.
750
     *
751
     * @param string $format
752
     *
753
     * @return $this
754
     */
755
    public function date($format)
756
    {
757
        return $this->display(function ($value) use ($format) {
758
            return date($format, strtotime($value));
759
        });
760
    }
761
762
    /**
763
     * Display column as boolean , `✓` for true, and `✗` for false.
764
     *
765
     * @param array $map
766
     * @param bool  $default
767
     *
768
     * @return $this
769
     */
770
    public function bool(array $map = [], $default = false)
771
    {
772
        return $this->display(function ($value) use ($map, $default) {
773
            $bool = empty($map) ? boolval($value) : Arr::get($map, $value, $default);
774
775
            return $bool ? '<i class="fa fa-check text-green"></i>' : '<i class="fa fa-close text-red"></i>';
776
        });
777
    }
778
779
    /**
780
     * Display column as a default value if empty.
781
     *
782
     * @param string $default
783
     * @return $this
784
     */
785
    public function default($default = '-')
786
    {
787
        return $this->display(function ($value) use ($default) {
788
            return $value ?: $default;
789
        });
790
    }
791
792
    /**
793
     * Display column using a grid row action.
794
     *
795
     * @param string $action
796
     *
797
     * @return $this
798
     */
799
    public function action($action)
800
    {
801
        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...
802
            throw new \InvalidArgumentException("Action class [$action] must be sub-class of [Encore\Admin\Actions\GridAction]");
803
        }
804
805
        $grid = $this->grid;
806
807
        return $this->display(function ($_, $column) use ($action, $grid) {
808
            /** @var RowAction $action */
809
            $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...
810
811
            return $action
812
                ->asColumn()
813
                ->setGrid($grid)
814
                ->setColumn($column)
815
                ->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...
816
        });
817
    }
818
819
    /**
820
     * Add a `dot` before column text.
821
     *
822
     * @param array  $options
823
     * @param string $default
824
     *
825
     * @return $this
826
     */
827
    public function dot($options = [], $default = '')
828
    {
829
        return $this->prefix(function ($_, $original) use ($options, $default) {
830
            if (is_null($original)) {
831
                $style = $default;
832
            } else {
833
                $style = Arr::get($options, $original, $default);
834
            }
835
836
            return "<span class=\"label-{$style}\" style='width: 8px;height: 8px;padding: 0;border-radius: 50%;display: inline-block;'></span>";
837
        }, '&nbsp;&nbsp;');
838
    }
839
840
    /**
841
     * @param string $selectable
842
     *
843
     * @return $this
844
     */
845 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...
846
    {
847
        if (method_exists($selectable, 'display')) {
848
            $this->display($selectable::display());
849
        }
850
851
        return $this->displayUsing(Grid\Displayers\BelongsTo::class, [$selectable]);
852
    }
853
854
    /**
855
     * @param string $selectable
856
     *
857
     * @return $this
858
     */
859 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...
860
    {
861
        if (method_exists($selectable, 'display')) {
862
            $this->display($selectable::display());
863
        }
864
865
        return $this->displayUsing(Grid\Displayers\BelongsToMany::class, [$selectable]);
866
    }
867
868
    /**
869
     * Upload file.
870
     *
871
     * @return $this
872
     */
873
    public function upload()
874
    {
875
        return $this->displayUsing(Grid\Displayers\Upload::class);
876
    }
877
878
    /**
879
     * Upload many files.
880
     *
881
     * @return $this
882
     */
883
    public function uplaodMany()
884
    {
885
        return $this->displayUsing(Grid\Displayers\Upload::class, [true]);
886
    }
887
888
    /**
889
     * If has display callbacks.
890
     *
891
     * @return bool
892
     */
893
    protected function hasDisplayCallbacks()
894
    {
895
        return !empty($this->displayCallbacks);
896
    }
897
898
    /**
899
     * Call all of the "display" callbacks column.
900
     *
901
     * @param mixed $value
902
     * @param int   $key
903
     *
904
     * @return mixed
905
     */
906
    protected function callDisplayCallbacks($value, $key)
907
    {
908
        foreach ($this->displayCallbacks as $callback) {
909
            $previous = $value;
910
911
            $callback = $this->bindOriginalRowModel($callback, $key);
912
            $value = call_user_func_array($callback, [$value, $this]);
913
914
            if (($value instanceof static) &&
915
                ($last = array_pop($this->displayCallbacks))
916
            ) {
917
                $last = $this->bindOriginalRowModel($last, $key);
918
                $value = call_user_func($last, $previous);
919
            }
920
        }
921
922
        return $value;
923
    }
924
925
    /**
926
     * Set original grid data to column.
927
     *
928
     * @param Closure $callback
929
     * @param int     $key
930
     *
931
     * @return Closure
932
     */
933
    protected function bindOriginalRowModel(Closure $callback, $key)
934
    {
935
        $rowModel = static::$originalGridModels[$key];
936
937
        return $callback->bindTo($rowModel);
938
    }
939
940
    /**
941
     * Fill all data to every column.
942
     *
943
     * @param array $data
944
     *
945
     * @return mixed
946
     */
947
    public function fill(array $data)
948
    {
949
        foreach ($data as $key => &$row) {
950
            $this->original = $value = Arr::get($row, $this->name);
951
952
            $value = $this->htmlEntityEncode($value);
953
954
            Arr::set($row, $this->name, $value);
955
956
            if ($this->isDefinedColumn()) {
957
                $this->useDefinedColumn();
958
            }
959
960
            if ($this->hasDisplayCallbacks()) {
961
                $value = $this->callDisplayCallbacks($this->original, $key);
962
                Arr::set($row, $this->name, $value);
963
            }
964
        }
965
966
        return $data;
967
    }
968
969
    /**
970
     * If current column is a defined column.
971
     *
972
     * @return bool
973
     */
974
    protected function isDefinedColumn()
975
    {
976
        return array_key_exists($this->name, static::$defined);
977
    }
978
979
    /**
980
     * Use a defined column.
981
     *
982
     * @throws \Exception
983
     */
984
    protected function useDefinedColumn()
985
    {
986
        // clear all display callbacks.
987
        $this->displayCallbacks = [];
988
989
        $class = static::$defined[$this->name];
990
991
        if ($class instanceof Closure) {
992
            $this->display($class);
993
994
            return;
995
        }
996
997
        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...
998
            throw new \Exception("Invalid column definition [$class]");
999
        }
1000
1001
        $grid = $this->grid;
1002
        $column = $this;
1003
1004
        $this->display(function ($value) use ($grid, $column, $class) {
1005
            /** @var AbstractDisplayer $definition */
1006
            $definition = new $class($value, $grid, $column, $this);
1007
1008
            return $definition->display();
1009
        });
1010
    }
1011
1012
    /**
1013
     * Convert characters to HTML entities recursively.
1014
     *
1015
     * @param array|string $item
1016
     *
1017
     * @return mixed
1018
     */
1019
    protected function htmlEntityEncode($item)
1020
    {
1021
        if (is_array($item)) {
1022
            array_walk_recursive($item, function (&$value) {
1023
                $value = htmlentities($value);
1024
            });
1025
        } else {
1026
            $item = htmlentities($item);
1027
        }
1028
1029
        return $item;
1030
    }
1031
1032
    /**
1033
     * Find a displayer to display column.
1034
     *
1035
     * @param string $abstract
1036
     * @param array  $arguments
1037
     *
1038
     * @return $this
1039
     */
1040
    protected function resolveDisplayer($abstract, $arguments)
1041
    {
1042
        if (array_key_exists($abstract, static::$displayers)) {
1043
            return $this->callBuiltinDisplayer(static::$displayers[$abstract], $arguments);
1044
        }
1045
1046
        return $this->callSupportDisplayer($abstract, $arguments);
1047
    }
1048
1049
    /**
1050
     * Call Illuminate/Support displayer.
1051
     *
1052
     * @param string $abstract
1053
     * @param array  $arguments
1054
     *
1055
     * @return $this
1056
     */
1057
    protected function callSupportDisplayer($abstract, $arguments)
1058
    {
1059
        return $this->display(function ($value) use ($abstract, $arguments) {
1060
            if (is_array($value) || $value instanceof Arrayable) {
1061
                return call_user_func_array([collect($value), $abstract], $arguments);
1062
            }
1063
1064
            if (is_string($value)) {
1065
                return call_user_func_array([Str::class, $abstract], array_merge([$value], $arguments));
1066
            }
1067
1068
            return $value;
1069
        });
1070
    }
1071
1072
    /**
1073
     * Call Builtin displayer.
1074
     *
1075
     * @param string $abstract
1076
     * @param array  $arguments
1077
     *
1078
     * @return $this
1079
     */
1080
    protected function callBuiltinDisplayer($abstract, $arguments)
1081
    {
1082
        if ($abstract instanceof Closure) {
1083
            return $this->display(function ($value) use ($abstract, $arguments) {
1084
                return $abstract->call($this, ...array_merge([$value], $arguments));
1085
            });
1086
        }
1087
1088
        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...
1089
            $grid = $this->grid;
1090
            $column = $this;
1091
1092 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...
1093
                /** @var AbstractDisplayer $displayer */
1094
                $displayer = new $abstract($value, $grid, $column, $this);
1095
1096
                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...
1097
            });
1098
        }
1099
1100
        return $this;
1101
    }
1102
1103
    /**
1104
     * Passes through all unknown calls to builtin displayer or supported displayer.
1105
     *
1106
     * Allow fluent calls on the Column object.
1107
     *
1108
     * @param string $method
1109
     * @param array  $arguments
1110
     *
1111
     * @return $this
1112
     */
1113
    public function __call($method, $arguments)
1114
    {
1115
        if ($this->isRelation() && !$this->relationColumn) {
1116
            $this->name = "{$this->relation}.$method";
1117
            $this->label = $this->formatLabel($arguments[0] ?? null);
1118
1119
            $this->relationColumn = $method;
1120
1121
            return $this;
1122
        }
1123
1124
        return $this->resolveDisplayer($method, $arguments);
1125
    }
1126
}
1127