Completed
Push — master ( 7761d4...144654 )
by Song
02:35
created

Grid::isOrderable()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Encore\Admin;
4
5
use Closure;
6
use Encore\Admin\Exception\Handle;
7
use Encore\Admin\Facades\Admin as AdminManager;
8
use Encore\Admin\Grid\Column;
9
use Encore\Admin\Grid\Exporter;
10
use Encore\Admin\Grid\Filter;
11
use Encore\Admin\Grid\Model;
12
use Encore\Admin\Grid\Row;
13
use Encore\Admin\Pagination\AdminThreePresenter;
14
use Illuminate\Database\Eloquent\Model as Eloquent;
15
use Illuminate\Database\Eloquent\Relations\BelongsTo;
16
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
17
use Illuminate\Database\Eloquent\Relations\HasMany;
18
use Illuminate\Database\Eloquent\Relations\HasOne;
19
use Illuminate\Database\Eloquent\Relations\MorphToMany;
20
use Illuminate\Database\Eloquent\Relations\Relation;
21
use Illuminate\Support\Collection;
22
use Illuminate\Support\Facades\Input;
23
use Illuminate\Support\Facades\Schema;
24
use Jenssegers\Mongodb\Eloquent\Model as MongodbModel;
25
26
class Grid
27
{
28
    /**
29
     * The grid data model instance.
30
     *
31
     * @var \Encore\Admin\Grid\Model
32
     */
33
    protected $model;
34
35
    /**
36
     * Collection of all grid columns.
37
     *
38
     * @var \Illuminate\Support\Collection
39
     */
40
    protected $columns;
41
42
    /**
43
     * Collection of all data rows.
44
     *
45
     * @var \Illuminate\Support\Collection
46
     */
47
    protected $rows;
48
49
    /**
50
     * Rows callable fucntion.
51
     *
52
     * @var \Closure
53
     */
54
    protected $rowsCallback;
55
56
    /**
57
     * All column names of the grid.
58
     *
59
     * @var array
60
     */
61
    public $columnNames = [];
62
63
    /**
64
     * Grid builder.
65
     *
66
     * @var \Closure
67
     */
68
    protected $builder;
69
70
    /**
71
     * Mark if the grid is builded.
72
     *
73
     * @var bool
74
     */
75
    protected $builded = false;
76
77
    /**
78
     * All variables in grid view.
79
     *
80
     * @var array
81
     */
82
    protected $variables = [];
83
84
    /**
85
     * The grid Filter.
86
     *
87
     * @var \Encore\Admin\Grid\Filter
88
     */
89
    protected $filter;
90
91
    /**
92
     * Resource path of the grid.
93
     *
94
     * @var
95
     */
96
    protected $resourcePath;
97
98
    /**
99
     * Default primary key name.
100
     *
101
     * @var string
102
     */
103
    protected $keyName = 'id';
104
105
    /**
106
     * Allow batch deletion.
107
     *
108
     * @var bool
109
     */
110
    protected $allowBatchDeletion = true;
111
112
    /**
113
     * Allow creation.
114
     *
115
     * @var bool
116
     */
117
    protected $allowCreation = true;
118
119
    /**
120
     * Allow actions.
121
     *
122
     * @var bool
123
     */
124
    protected $allowActions = true;
125
126
    /**
127
     * Allow export data.
128
     *
129
     * @var bool
130
     */
131
    protected $allowExport = true;
132
133
    /**
134
     * Is grid rows orderable.
135
     *
136
     * @var bool
137
     */
138
    protected $orderable = false;
139
140
    /**
141
     * @var Exporter
142
     */
143
    protected $exporter;
144
145
    /**
146
     * Create a new grid instance.
147
     *
148
     * @param Eloquent $model
149
     * @param callable $builder
150
     */
151
    public function __construct(Eloquent $model, Closure $builder)
152
    {
153
        $this->keyName = $model->getKeyName();
154
        $this->model = new Model($model);
155
        $this->columns = new Collection();
156
        $this->rows = new Collection();
157
        $this->builder = $builder;
158
159
        $this->setupFilter();
160
        $this->setupExporter();
161
    }
162
163
    /**
164
     * Get primary key name of model.
165
     *
166
     * @return string
167
     */
168
    public function getKeyName()
169
    {
170
        return $this->keyName ?: 'id';
171
    }
172
173
    /**
174
     * Add column to Grid.
175
     *
176
     * @param string $name
177
     * @param string $label
178
     *
179
     * @return Column
180
     */
181
    public function column($name, $label = '')
182
    {
183
        $relationName = $relationColumn = '';
184
185
        if (strpos($name, '.') !== false) {
186
            list($relationName, $relationColumn) = explode('.', $name);
187
188
            $relation = $this->model()->eloquent()->$relationName();
189
190
            $label = empty($label) ? ucfirst($relationColumn) : $label;
191
        }
192
193
        $column = $this->addColumn($name, $label);
194
195
        if (isset($relation) && $relation instanceof Relation) {
196
            $this->model()->with($relationName);
0 ignored issues
show
Documentation Bug introduced by
The method with 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...
197
            $column->setRelation($relation, $relationColumn);
0 ignored issues
show
Unused Code introduced by
The call to Column::setRelation() has too many arguments starting with $relationColumn.

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...
198
        }
199
200
        return $column;
201
    }
202
203
    /**
204
     * Batch add column to grid.
205
     *
206
     * @example
207
     * 1.$grid->columns(['name' => 'Name', 'email' => 'Email' ...]);
208
     * 2.$grid->columns('name', 'email' ...)
209
     *
210
     * @param array $columns
211
     *
212
     * @return Collection|void
213
     */
214
    public function columns($columns = [])
215
    {
216
        if (func_num_args() == 0) {
217
            return $this->columns;
218
        }
219
220
        if (func_num_args() == 1 && is_array($columns)) {
221
            foreach ($columns as $column => $label) {
222
                $this->column($column, $label);
223
            }
224
225
            return;
226
        }
227
228
        foreach (func_get_args() as $column) {
229
            $this->column($column);
230
        }
231
    }
232
233
    /**
234
     * Add column to grid.
235
     *
236
     * @param string $column
237
     * @param string $label
238
     *
239
     * @return Column
240
     */
241
    protected function addColumn($column = '', $label = '')
242
    {
243
        $column = new Column($column, $label);
244
        $column->setGrid($this);
245
246
        return $this->columns[] = $column;
247
    }
248
249
    public function blank($label)
250
    {
251
        return $this->addColumn('blank', $label);
252
    }
253
254
    /**
255
     * Get Grid model.
256
     *
257
     * @return Model
258
     */
259
    public function model()
260
    {
261
        return $this->model;
262
    }
263
264
    /**
265
     * Paginate the grid.
266
     *
267
     * @param int $perPage
268
     *
269
     * @return void
270
     */
271
    public function paginate($perPage = null)
272
    {
273
        $this->model()->paginate($perPage);
0 ignored issues
show
Bug introduced by
The method paginate() does not exist on Encore\Admin\Grid\Model. Did you maybe mean usePaginate()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
274
    }
275
276
    /**
277
     * Get the grid paginator.
278
     *
279
     * @return mixed
280
     */
281
    public function paginator()
282
    {
283
        $query = Input::all();
284
285
        return $this->model()->eloquent()->appends($query)->render(
286
            new AdminThreePresenter($this->model()->eloquent())
0 ignored issues
show
Documentation introduced by
$this->model()->eloquent() is of type object<Illuminate\Database\Eloquent\Model>, but the function expects a object<Illuminate\Contracts\Pagination\Paginator>.

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...
287
        );
288
    }
289
290
    /**
291
     * Build the grid.
292
     *
293
     * @return void
294
     */
295
    public function build()
296
    {
297
        if ($this->builded) {
298
            return;
299
        }
300
301
        $data = $this->processFilter();
302
303
        $this->columns->map(function (Column $column) use (&$data) {
304
            $data = $column->map($data);
305
306
            $this->columnNames[] = $column->getName();
307
        });
308
309
        $this->buildRows($data);
310
311
        $this->builded = true;
312
    }
313
314
    /**
315
     * Process the grid filter.
316
     *
317
     * @return array
318
     */
319
    public function processFilter()
320
    {
321
        call_user_func($this->builder, $this);
322
323
        return $this->filter->execute();
324
    }
325
326
    /**
327
     * Build the grid rows.
328
     *
329
     * @param array $data
330
     *
331
     * @return void
332
     */
333
    protected function buildRows(array $data)
334
    {
335
        $this->rows = collect($data)->map(function ($val, $key) {
336
            $row = new Row($key, $val);
337
338
            $row->setKeyName($this->keyName);
339
            $row->setPath($this->resource());
340
341
            return $row;
342
        });
343
344
        if ($this->rowsCallback) {
345
            $this->rows->map($this->rowsCallback);
346
        }
347
    }
348
349
    /**
350
     * Set grid row callback function.
351
     *
352
     * @param callable $callable
353
     *
354
     * @return Collection|void
355
     */
356
    public function rows(Closure $callable = null)
357
    {
358
        if (is_null($callable)) {
359
            return $this->rows;
360
        }
361
362
        $this->rowsCallback = $callable;
363
    }
364
365
    /**
366
     * Setup grid filter.
367
     *
368
     * @return void
369
     */
370
    protected function setupFilter()
371
    {
372
        $this->filter = new Filter($this, $this->model());
373
    }
374
375
    /**
376
     * Setup grid exporter.
377
     *
378
     * @return void
379
     */
380
    protected function setupExporter()
381
    {
382
        if (Input::has('_export')) {
383
            $exporter = new Exporter($this);
384
385
            $exporter->export();
386
        }
387
    }
388
389
    /**
390
     * Export url.
391
     *
392
     * @return string
393
     */
394
    public function exportUrl()
395
    {
396
        $query = $query = Input::all();
397
        $query['_export'] = true;
398
399
        return $this->resource().'?'.http_build_query($query);
400
    }
401
402
    /**
403
     * If allow batch delete.
404
     *
405
     * @return bool
406
     */
407
    public function allowBatchDeletion()
408
    {
409
        return $this->allowBatchDeletion;
410
    }
411
412
    /**
413
     * Disable batch deletion.
414
     */
415
    public function disableBatchDeletion()
416
    {
417
        $this->allowBatchDeletion = false;
418
    }
419
420
    /**
421
     * Disable creation.
422
     */
423
    public function disableCreation()
424
    {
425
        $this->allowCreation = false;
426
    }
427
428
    /**
429
     * If allow creation.
430
     *
431
     * @return bool
432
     */
433
    public function allowCreation()
434
    {
435
        return $this->allowCreation;
436
    }
437
438
    /**
439
     * If allow actions.
440
     *
441
     * @return bool
442
     */
443
    public function allowActions()
444
    {
445
        return $this->allowActions;
446
    }
447
448
    /**
449
     * Disable all actions.
450
     */
451
    public function disableActions()
452
    {
453
        $this->allowActions = false;
454
    }
455
456
    /**
457
     * If grid allows export.s.
458
     *
459
     * @return bool
460
     */
461
    public function allowExport()
462
    {
463
        return $this->allowExport;
464
    }
465
466
    /**
467
     * Disable export.
468
     */
469
    public function disableExport()
470
    {
471
        $this->allowExport = false;
472
    }
473
474
    /**
475
     * Set grid as orderable.
476
     *
477
     * @return $this
478
     */
479
    public function orderable()
480
    {
481
        $this->orderable = true;
482
483
        return $this;
484
    }
485
486
    /**
487
     * Is the grid orderable.
488
     *
489
     * @return bool
490
     */
491
    public function isOrderable()
492
    {
493
        return $this->orderable;
494
    }
495
496
    /**
497
     * Set the grid filter.
498
     *
499
     * @param callable $callback
500
     */
501
    public function filter(Closure $callback)
502
    {
503
        call_user_func($callback, $this->filter);
504
    }
505
506
    /**
507
     * Render the grid filter.
508
     *
509
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
510
     */
511
    public function renderFilter()
512
    {
513
        return $this->filter->render();
514
    }
515
516
    /**
517
     * Get current resource uri.
518
     *
519
     * @param string $path
520
     *
521
     * @return string
522
     */
523
    public function resource($path = null)
524
    {
525
        if (!empty($path)) {
526
            $this->resourcePath = $path;
527
528
            return $this;
529
        }
530
531
        if (!empty($this->resourcePath)) {
532
            return $this->resourcePath;
533
        }
534
535
        return app('router')->current()->getPath();
536
    }
537
538
    public function pathOfCreate()
539
    {
540
        $path = $query = '';
541
542
        extract(parse_url($this->resource()));
0 ignored issues
show
Bug introduced by
parse_url($this->resource()) cannot be passed to extract() as the parameter $var_array expects a reference.
Loading history...
543
544
        return '/'.trim($path, '/').'/create'.$query;
545
    }
546
547
    /**
548
     * Add variables to grid view.
549
     *
550
     * @param array $variables
551
     *
552
     * @return $this
553
     */
554
    public function with($variables = [])
555
    {
556
        $this->variables = $variables;
557
558
        return $this;
559
    }
560
561
    /**
562
     * Get all variables will used in grid view.
563
     *
564
     * @return array
565
     */
566
    protected function variables()
567
    {
568
        $this->variables['grid'] = $this;
569
570
        return $this->variables;
571
    }
572
573
    /**
574
     * Get the string contents of the grid view.
575
     *
576
     * @return string
577
     */
578
    public function render()
579
    {
580
        try {
581
            $this->build();
582
        } catch (\Exception $e) {
583
            return with(new Handle($e))->render();
584
        }
585
586
        return view('admin::grid', $this->variables())->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...
587
    }
588
589
    /**
590
     * Dynamically add columns to the grid view.
591
     *
592
     * @param $method
593
     * @param $arguments
594
     *
595
     * @return $this|Column
596
     */
597
    public function __call($method, $arguments)
598
    {
599
        $label = isset($arguments[0]) ? $arguments[0] : ucfirst($method);
600
601
        if ($this->model()->eloquent() instanceof MongodbModel) {
0 ignored issues
show
Bug introduced by
The class Jenssegers\Mongodb\Eloquent\Model does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
602
            return $this->addColumn($method, $label);
603
        }
604
605
        $connection = $this->model()->eloquent()->getConnectionName();
606
        if (Schema::connection($connection)->hasColumn($this->model()->getTable(), $method)) {
607
            return $this->addColumn($method, $label);
608
        }
609
610
        $relation = $this->model()->eloquent()->$method();
611
612
        if ($relation instanceof HasOne || $relation instanceof BelongsTo) {
613
            $this->model()->with($method);
0 ignored issues
show
Documentation Bug introduced by
The method with 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...
614
615
            return $this->addColumn($method, $label)->setRelation($method);
616
        }
617
618
        if ($relation instanceof HasMany || $relation instanceof BelongsToMany || $relation instanceof MorphToMany) {
619
            $this->model()->with($method);
0 ignored issues
show
Documentation Bug introduced by
The method with 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...
620
621
            return $this->addColumn($method, $label);
622
        }
623
    }
624
625
    /**
626
     * Js code for grid.
627
     *
628
     * @return string
629
     */
630
    public function script()
631
    {
632
        $path = app('router')->current()->getPath();
633
        $token = csrf_token();
634
        $confirm = trans('admin::lang.delete_confirm');
635
636
        return <<<EOT
637
638
$('.grid-select-all').change(function() {
639
    if (this.checked) {
640
        $('.grid-item').prop("checked", true);
641
    } else {
642
        $('.grid-item').prop("checked", false);
643
    }
644
});
645
646
$('.batch-delete').on('click', function() {
647
    var selected = [];
648
    $('.grid-item:checked').each(function(){
649
        selected.push($(this).data('id'));
650
    });
651
652
    if (selected.length == 0) {
653
        return;
654
    }
655
656
    if(confirm("{$confirm}")) {
657
        $.post('/{$path}/' + selected.join(), {_method:'delete','_token':'{$token}'}, function(data){
658
            $.pjax.reload('#pjax-container');
659
            noty({
660
                text: "<strong>Succeeded!</strong>",
661
                type:'success',
662
                timeout: 1000
663
            });
664
        });
665
    }
666
});
667
668
$('.grid-refresh').on('click', function() {
669
    $.pjax.reload('#pjax-container');
670
671
    noty({
672
        text: "<strong>Succeeded!</strong>",
673
        type:'success',
674
        timeout: 1000
675
    });
676
});
677
678
var grid_order = function(id, direction) {
679
    $.post('/{$path}/' + id, {_method:'PUT', _token:'{$token}', _orderable:direction}, function(data){
680
681
        if (data.status) {
682
            noty({
683
                text: "<strong>Succeeded!</strong>",
684
                type:'success',
685
                timeout: 1000
686
            });
687
688
            $.pjax.reload('#pjax-container');
689
        }
690
    });
691
}
692
693
$('.grid-order-up').on('click', function() {
694
    grid_order($(this).data('id'), 1);
695
});
696
697
$('.grid-order-down').on('click', function() {
698
    grid_order($(this).data('id'), 0);
699
});
700
701
EOT;
702
    }
703
704
    /**
705
     * Get the string contents of the grid view.
706
     *
707
     * @return string
708
     */
709
    public function __toString()
710
    {
711
        AdminManager::script($this->script());
712
713
        return $this->render();
714
    }
715
}
716