Completed
Push — master ( 330eae...a7fcf0 )
by Song
02:55
created

Column::getLabel()   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 Closure;
6
use Encore\Admin\Grid;
7
use Encore\Admin\Grid\Displayers\AbstractDisplayer;
8
use Illuminate\Contracts\Support\Arrayable;
9
use Illuminate\Database\Eloquent\Model;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Encore\Admin\Grid\Model.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
10
use Illuminate\Support\Facades\URL;
11
use Illuminate\Support\Str;
12
13
class Column
14
{
15
    /**
16
     * @var Grid
17
     */
18
    protected $grid;
19
20
    /**
21
     * Name of column.
22
     *
23
     * @var string
24
     */
25
    protected $name;
26
27
    /**
28
     * Label of column.
29
     *
30
     * @var string
31
     */
32
    protected $label;
33
34
    /**
35
     * Original value of column.
36
     *
37
     * @var mixed
38
     */
39
    protected $original;
40
41
    /**
42
     * Is column sortable.
43
     *
44
     * @var bool
45
     */
46
    protected $sortable = false;
47
48
    /**
49
     * Sort arguments.
50
     *
51
     * @var array
52
     */
53
    protected $sort;
54
55
    /**
56
     * Attributes of column.
57
     *
58
     * @var array
59
     */
60
    protected $attributes = [];
61
62
    /**
63
     * Relation name.
64
     *
65
     * @var bool
66
     */
67
    protected $relation = false;
68
69
    /**
70
     * Relation column.
71
     *
72
     * @var string
73
     */
74
    protected $relationColumn;
75
76
    /**
77
     * Original grid data.
78
     *
79
     * @var array
80
     */
81
    protected static $originalGridData = [];
82
83
    /**
84
     * @var []Closure
85
     */
86
    protected $displayCallbacks = [];
87
88
    /**
89
     * Displayers for grid column.
90
     *
91
     * @var array
92
     */
93
    public static $displayers = [];
94
95
    /**
96
     * Defined columns.
97
     *
98
     * @var array
99
     */
100
    public static $defined = [];
101
102
    /**
103
     * @var array
104
     */
105
    protected static $htmlAttributes = [];
106
107
    /**
108
     * @var Model
109
     */
110
    protected static $model;
111
112
    const SELECT_COLUMN_NAME = '__row_selector__';
113
114
    /**
115
     * @param string $name
116
     * @param string $label
117
     */
118
    public function __construct($name, $label)
119
    {
120
        $this->name = $name;
121
122
        $this->label = $this->formatLabel($label);
123
    }
124
125
    /**
126
     * Extend column displayer.
127
     *
128
     * @param $name
129
     * @param $displayer
130
     */
131
    public static function extend($name, $displayer)
132
    {
133
        static::$displayers[$name] = $displayer;
134
    }
135
136
    /**
137
     * Define a column globally.
138
     *
139
     * @param string $name
140
     * @param mixed  $definition
141
     */
142
    public static function define($name, $definition)
143
    {
144
        static::$defined[$name] = $definition;
145
    }
146
147
    /**
148
     * Set grid instance for column.
149
     *
150
     * @param Grid $grid
151
     */
152
    public function setGrid(Grid $grid)
153
    {
154
        $this->grid = $grid;
155
156
        $this->setModel($grid->model()->eloquent());
157
    }
158
159
    /**
160
     * Set model for column.
161
     *
162
     * @param $model
163
     */
164
    public function setModel($model)
165
    {
166
        if (is_null(static::$model) && ($model instanceof Model)) {
167
            static::$model = $model->newInstance();
168
        }
169
    }
170
171
    /**
172
     * Set original data for column.
173
     *
174
     * @param array $input
175
     */
176
    public static function setOriginalGridData(array $input)
177
    {
178
        static::$originalGridData = $input;
179
    }
180
181
    /**
182
     * Set column attributes.
183
     *
184
     * @param array $attributes
185
     *
186
     * @return $this
187
     */
188
    public function setAttributes($attributes = [])
189
    {
190
        static::$htmlAttributes[$this->name] = $attributes;
191
192
        return $this;
193
    }
194
195
    /**
196
     * Get column attributes.
197
     *
198
     * @param string $name
199
     *
200
     * @return mixed
201
     */
202
    public static function getAttributes($name)
203
    {
204
        return array_get(static::$htmlAttributes, $name, '');
205
    }
206
207
    /**
208
     * Set style of this column.
209
     *
210
     * @param string $style
211
     *
212
     * @return Column
213
     */
214
    public function style($style)
215
    {
216
        return $this->setAttributes(compact('style'));
217
    }
218
219
    /**
220
     * Get name of this column.
221
     *
222
     * @return mixed
223
     */
224
    public function getName()
225
    {
226
        return $this->name;
227
    }
228
229
    /**
230
     * Format label.
231
     *
232
     * @param $label
233
     *
234
     * @return mixed
235
     */
236
    protected function formatLabel($label)
237
    {
238
        $label = $label ?: ucfirst($this->name);
239
240
        return str_replace(['.', '_'], ' ', $label);
241
    }
242
243
    /**
244
     * Get label of the column.
245
     *
246
     * @return mixed
247
     */
248
    public function getLabel()
249
    {
250
        return $this->label;
251
    }
252
253
    /**
254
     * Set relation.
255
     *
256
     * @param string $relation
257
     * @param string $relationColumn
258
     *
259
     * @return $this
260
     */
261
    public function setRelation($relation, $relationColumn = null)
262
    {
263
        $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...
264
        $this->relationColumn = $relationColumn;
265
266
        return $this;
267
    }
268
269
    /**
270
     * If this column is relation column.
271
     *
272
     * @return bool
273
     */
274
    protected function isRelation()
275
    {
276
        return (bool) $this->relation;
277
    }
278
279
    /**
280
     * Mark this column as sortable.
281
     *
282
     * @return Column
283
     */
284
    public function sortable()
285
    {
286
        $this->sortable = true;
287
288
        return $this;
289
    }
290
291
    /**
292
     * Add a display callback.
293
     *
294
     * @param Closure $callback
295
     *
296
     * @return $this
297
     */
298
    public function display(Closure $callback)
299
    {
300
        $this->displayCallbacks[] = $callback;
301
302
        return $this;
303
    }
304
305
    /**
306
     * Display column using array value map.
307
     *
308
     * @param array $values
309
     * @param null  $default
310
     *
311
     * @return $this
312
     */
313 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...
314
    {
315
        return $this->display(function ($value) use ($values, $default) {
316
            if (is_null($value)) {
317
                return $default;
318
            }
319
320
            return array_get($values, $value, $default);
321
        });
322
    }
323
324
    /**
325
     * If has display callbacks.
326
     *
327
     * @return bool
328
     */
329
    protected function hasDisplayCallbacks()
330
    {
331
        return !empty($this->displayCallbacks);
332
    }
333
334
    /**
335
     * Call all of the "display" callbacks column.
336
     *
337
     * @param mixed $value
338
     * @param int   $key
339
     *
340
     * @return mixed
341
     */
342
    protected function callDisplayCallbacks($value, $key)
343
    {
344
        foreach ($this->displayCallbacks as $callback) {
345
346
            $previous = $value;
347
348
            $callback = $this->bindOriginalRow($callback, $key);
349
            $value = call_user_func_array($callback, [$value, $this]);
350
351
            if (($value instanceof static) &&
352
                ($last = array_pop($this->displayCallbacks))
353
            ) {
354
                $value = call_user_func($last, $previous);
355
            }
356
        }
357
358
        return $value;
359
    }
360
361
    /**
362
     * Set original grid data to column.
363
     *
364
     * @param Closure $callback
365
     * @param int     $key
366
     *
367
     * @return Closure
368
     */
369
    protected function bindOriginalRow(Closure $callback, $key)
370
    {
371
        $originalRow = static::$originalGridData[$key];
372
373
        return $callback->bindTo(static::$model->newFromBuilder($originalRow));
374
    }
375
376
    /**
377
     * Fill all data to every column.
378
     *
379
     * @param array $data
380
     *
381
     * @return mixed
382
     */
383
    public function fill(array $data)
384
    {
385
        foreach ($data as $key => &$row) {
386
            $this->original = $value = array_get($row, $this->name);
387
388
            $value = $this->htmlEntityEncode($value);
389
390
            array_set($row, $this->name, $value);
391
392
            if ($this->isDefinedColumn()) {
393
                $this->useDefinedColumn();
394
            }
395
396
            if ($this->hasDisplayCallbacks()) {
397
                $value = $this->callDisplayCallbacks($this->original, $key);
398
                array_set($row, $this->name, $value);
399
            }
400
        }
401
402
        return $data;
403
    }
404
405
    /**
406
     * If current column is a defined column.
407
     *
408
     * @return bool
409
     */
410
    protected function isDefinedColumn()
411
    {
412
        return array_key_exists($this->name, static::$defined);
413
    }
414
415
    /**
416
     * Use a defined column.
417
     *
418
     * @throws \Exception
419
     */
420
    protected function useDefinedColumn()
421
    {
422
        // clear all display callbacks.
423
        $this->displayCallbacks = [];
424
425
        $class = static::$defined[$this->name];
426
427
        if ($class instanceof Closure) {
428
            $this->display($class);
429
430
            return;
431
        }
432
433
        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...
434
            throw new \Exception("Invalid column definition [$class]");
435
        }
436
437
        $grid = $this->grid;
438
        $column = $this;
439
440
        $this->display(function ($value) use ($grid, $column, $class) {
441
            /** @var AbstractDisplayer $definition */
442
            $definition = new $class($value, $grid, $column, $this);
443
444
            return $definition->display();
445
        });
446
    }
447
448
    /**
449
     * Convert characters to HTML entities recursively.
450
     *
451
     * @param array|string $item
452
     *
453
     * @return mixed
454
     */
455
    protected function htmlEntityEncode($item)
456
    {
457
        if (is_array($item)) {
458
            array_walk_recursive($item, function (&$value) {
459
                $value = htmlentities($value);
460
            });
461
        } else {
462
            $item = htmlentities($item);
463
        }
464
465
        return $item;
466
    }
467
468
    /**
469
     * Create the column sorter.
470
     *
471
     * @return string|void
472
     */
473
    public function sorter()
474
    {
475
        if (!$this->sortable) {
476
            return;
477
        }
478
479
        $icon = 'fa-sort';
480
        $type = 'desc';
481
482
        if ($this->isSorted()) {
483
            $type = $this->sort['type'] == 'desc' ? 'asc' : 'desc';
484
            $icon .= "-amount-{$this->sort['type']}";
485
        }
486
487
        $query = app('request')->all();
488
        $query = array_merge($query, [$this->grid->model()->getSortName() => ['column' => $this->name, 'type' => $type]]);
489
490
        $url = URL::current().'?'.http_build_query($query);
491
492
        return "<a class=\"fa fa-fw $icon\" href=\"$url\"></a>";
493
    }
494
495
    /**
496
     * Determine if this column is currently sorted.
497
     *
498
     * @return bool
499
     */
500
    protected function isSorted()
501
    {
502
        $this->sort = app('request')->get($this->grid->model()->getSortName());
503
504
        if (empty($this->sort)) {
505
            return false;
506
        }
507
508
        return isset($this->sort['column']) && $this->sort['column'] == $this->name;
509
    }
510
511
    /**
512
     * Find a displayer to display column.
513
     *
514
     * @param string $abstract
515
     * @param array  $arguments
516
     *
517
     * @return Column
518
     */
519
    protected function resolveDisplayer($abstract, $arguments)
520
    {
521
        if (array_key_exists($abstract, static::$displayers)) {
522
            return $this->callBuiltinDisplayer(static::$displayers[$abstract], $arguments);
523
        }
524
525
        return $this->callSupportDisplayer($abstract, $arguments);
526
    }
527
528
    /**
529
     * Call Illuminate/Support displayer.
530
     *
531
     * @param string $abstract
532
     * @param array  $arguments
533
     *
534
     * @return Column
535
     */
536
    protected function callSupportDisplayer($abstract, $arguments)
537
    {
538
        return $this->display(function ($value) use ($abstract, $arguments) {
539
            if (is_array($value) || $value instanceof Arrayable) {
540
                return call_user_func_array([collect($value), $abstract], $arguments);
541
            }
542
543
            if (is_string($value)) {
544
                return call_user_func_array([Str::class, $abstract], array_merge([$value], $arguments));
545
            }
546
547
            return $value;
548
        });
549
    }
550
551
    /**
552
     * Call Builtin displayer.
553
     *
554
     * @param string $abstract
555
     * @param array  $arguments
556
     *
557
     * @return Column
558
     */
559
    protected function callBuiltinDisplayer($abstract, $arguments)
560
    {
561
        if ($abstract instanceof Closure) {
562
            return $this->display(function ($value) use ($abstract, $arguments) {
563
                return $abstract->call($this, ...array_merge([$value], $arguments));
564
            });
565
        }
566
567
        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...
568
            $grid = $this->grid;
569
            $column = $this;
570
571
            return $this->display(function ($value) use ($abstract, $grid, $column, $arguments) {
572
                $displayer = new $abstract($value, $grid, $column, $this);
573
574
                return $displayer->display(...$arguments);
575
            });
576
        }
577
578
        return $this;
579
    }
580
581
    /**
582
     * Passes through all unknown calls to builtin displayer or supported displayer.
583
     *
584
     * Allow fluent calls on the Column object.
585
     *
586
     * @param string $method
587
     * @param array  $arguments
588
     *
589
     * @return $this
590
     */
591
    public function __call($method, $arguments)
592
    {
593
        if ($this->isRelation() && !$this->relationColumn) {
594
            $this->name = "{$this->relation}.$method";
595
            $this->label = isset($arguments[0]) ? $arguments[0] : ucfirst($method);
596
597
            $this->relationColumn = $method;
598
599
            return $this;
600
        }
601
602
        return $this->resolveDisplayer($method, $arguments);
603
    }
604
}
605