Completed
Push — master ( 8f0aef...6f5bb3 )
by Song
02:43
created

Column   F

Complexity

Total Complexity 100

Size/Duplication

Total Lines 1066
Duplicated Lines 3.56 %

Coupling/Cohesion

Components 3
Dependencies 7

Importance

Changes 0
Metric Value
dl 38
loc 1066
rs 1.336
c 0
b 0
f 0
wmc 100
lcom 3
cbo 7

57 Methods

Rating   Name   Duplication   Size   Complexity  
A display() 0 6 1
A displayUsing() 6 13 1
A using() 10 10 2
A replace() 0 10 2
A __construct() 0 8 1
A initAttributes() 0 6 1
A extend() 0 4 1
A define() 0 4 1
A setGrid() 0 6 1
A setModel() 0 6 3
A setOriginalGridModels() 0 4 1
A setAttributes() 0 18 2
A getAttributes() 0 12 3
A formatHtmlAttributes() 0 9 2
A style() 0 4 1
A width() 0 4 1
A color() 0 4 1
A getOriginal() 0 4 1
A getName() 0 4 1
A getClassName() 0 6 1
A formatLabel() 0 10 2
A getLabel() 0 4 1
A setRelation() 0 7 1
A isRelation() 0 4 1
A sortable() 0 4 1
A cast() 0 6 1
A help() 0 4 1
A filter() 0 4 1
A searchable() 0 17 1
A bindSearchQuery() 0 6 3
A repeat() 0 17 3
A view() 0 8 1
A hide() 0 6 1
A totalRow() 0 6 1
A filesize() 0 6 1
A gravatar() 0 12 1
A loading() 0 12 2
A icon() 0 14 3
A diffForHumans() 0 10 2
A date() 0 6 1
A bool() 0 8 3
A default() 0 6 2
A action() 0 19 2
A dot() 0 12 2
A belongsTo() 8 8 2
A belongsToMany() 8 8 2
A hasDisplayCallbacks() 0 4 1
A callDisplayCallbacks() 0 18 4
A bindOriginalRowModel() 0 6 1
A fill() 0 21 4
A isDefinedColumn() 0 4 1
A useDefinedColumn() 0 27 4
A htmlEntityEncode() 0 12 2
A resolveDisplayer() 0 8 2
A callSupportDisplayer() 0 14 4
A callBuiltinDisplayer() 6 22 4
A __call() 0 13 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Column often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Column, and based on these observations, apply Extract Interface, too.

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