Completed
Push — master ( f5a787...4b5e24 )
by Song
02:26
created

Column::bindSearchQuery()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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