Completed
Push — master ( acb427...0d7e58 )
by Arjay
08:04
created

Builder::addCheckbox()   B

Complexity

Conditions 4
Paths 3

Size

Total Lines 25
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 25
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 20
nc 3
nop 2
1
<?php
2
3
namespace Yajra\DataTables\Html;
4
5
use Illuminate\Support\Arr;
6
use Illuminate\Support\Str;
7
use Collective\Html\HtmlBuilder;
8
use Illuminate\Support\Collection;
9
use Illuminate\Support\HtmlString;
10
use Illuminate\Contracts\View\Factory;
11
use Illuminate\Support\Traits\Macroable;
12
use Illuminate\Contracts\Config\Repository;
13
14
class Builder
15
{
16
    use Macroable;
17
18
    /**
19
     * @var Collection
20
     */
21
    public $collection;
22
23
    /**
24
     * @var Repository
25
     */
26
    public $config;
27
28
    /**
29
     * @var Factory
30
     */
31
    public $view;
32
33
    /**
34
     * @var HtmlBuilder
35
     */
36
    public $html;
37
38
    /**
39
     * @var string|array
40
     */
41
    protected $ajax = '';
42
43
    /**
44
     * @var array
45
     */
46
    protected $tableAttributes = [];
47
48
    /**
49
     * @var string
50
     */
51
    protected $template = '';
52
53
    /**
54
     * @var array
55
     */
56
    protected $attributes = [];
57
58
    /**
59
     * @param Repository  $config
60
     * @param Factory     $view
61
     * @param HtmlBuilder $html
62
     */
63
    public function __construct(Repository $config, Factory $view, HtmlBuilder $html)
64
    {
65
        $this->config          = $config;
66
        $this->view            = $view;
67
        $this->html            = $html;
68
        $this->collection      = new Collection;
69
        $this->tableAttributes = $this->config->get('datatables-html.table', []);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->config->get('data...s-html.table', array()) of type * is incompatible with the declared type array of property $tableAttributes.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
70
    }
71
72
    /**
73
     * Generate DataTable javascript.
74
     *
75
     * @param  null  $script
76
     * @param  array $attributes
77
     * @return \Illuminate\Support\HtmlString
78
     */
79
    public function scripts($script = null, array $attributes = ['type' => 'text/javascript'])
80
    {
81
        $script     = $script ?: $this->generateScripts();
82
        $attributes = $this->html->attributes($attributes);
83
84
        return new HtmlString("<script{$attributes}>{$script}</script>\n");
85
    }
86
87
    /**
88
     * Get generated raw scripts.
89
     *
90
     * @return \Illuminate\Support\HtmlString
91
     */
92
    public function generateScripts()
93
    {
94
        $parameters = $this->generateJson();
95
96
        return new HtmlString(
97
            sprintf($this->template(), $this->getTableAttribute('id'), $parameters)
98
        );
99
    }
100
101
    /**
102
     * Get generated json configuration.
103
     *
104
     * @return string
105
     */
106
    public function generateJson()
107
    {
108
        $args = array_merge(
109
            $this->attributes, [
110
                'ajax'    => $this->ajax,
111
                'columns' => $this->collection->map(function (Column $column) {
112
                    $column = $column->toArray();
113
                    unset($column['attributes']);
114
115
                    return $column;
116
                })->toArray(),
117
            ]
118
        );
119
120
        return $this->parameterize($args);
121
    }
122
123
    /**
124
     * Generate DataTables js parameters.
125
     *
126
     * @param  array $attributes
127
     * @return string
128
     */
129
    public function parameterize($attributes = [])
130
    {
131
        $parameters = (new Parameters($attributes))->toArray();
132
133
        $values       = [];
134
        $replacements = [];
135
136
        foreach (array_dot($parameters) as $key => $value) {
137
            if ($this->isCallbackFunction($value, $key)) {
138
                $values[] = trim($value);
139
                array_set($parameters, $key, '%' . $key . '%');
140
                $replacements[] = '"%' . $key . '%"';
141
            }
142
        }
143
144
        foreach ($parameters as $key => $value) {
145
            array_set($new, $key, $value);
146
        }
147
148
        $json = json_encode($new);
0 ignored issues
show
Bug introduced by
The variable $new does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
149
150
        $json = str_replace($replacements, $values, $json);
151
152
        return $json;
153
    }
154
155
    /**
156
     * Check if given key & value is a valid callback js function.
157
     *
158
     * @param string $value
159
     * @param string $key
160
     * @return bool
161
     */
162
    protected function isCallbackFunction($value, $key)
163
    {
164
        return Str::startsWith(trim($value), $this->config->get('datatables-html.callback', ['$', '$.', 'function'])) || Str::contains($key, 'editor');
165
    }
166
167
    /**
168
     * Get javascript template to use.
169
     *
170
     * @return string
171
     */
172
    protected function template()
173
    {
174
        return $this->view->make(
175
            $this->template ?: $this->config->get('datatables.script_template', 'datatables::script')
176
        )->render();
177
    }
178
179
    /**
180
     * Retrieves HTML table attribute value.
181
     *
182
     * @param string $attribute
183
     * @return mixed
184
     * @throws \Exception
185
     */
186
    public function getTableAttribute($attribute)
187
    {
188
        if (! array_key_exists($attribute, $this->tableAttributes)) {
189
            throw new \Exception("Table attribute '{$attribute}' does not exist.");
190
        }
191
192
        return $this->tableAttributes[$attribute];
193
    }
194
195
    /**
196
     * Get table computed table attributes.
197
     *
198
     * @return array
199
     */
200
    public function getTableAttributes()
201
    {
202
        return $this->tableAttributes;
203
    }
204
205
    /**
206
     * Sets multiple HTML table attributes at once.
207
     *
208
     * @param array $attributes
209
     * @return $this
210
     */
211
    public function setTableAttributes(array $attributes)
212
    {
213
        foreach ($attributes as $attribute => $value) {
214
            $this->tableAttributes[$attribute] = $value;
215
        }
216
217
        return $this;
218
    }
219
220
    /**
221
     * Sets HTML table "id" attribute.
222
     *
223
     * @param string $id
224
     * @return $this
225
     */
226
    public function setTableId($id)
227
    {
228
        return $this->setTableAttribute('id', $id);
229
    }
230
231
    /**
232
     * Sets HTML table attribute(s).
233
     *
234
     * @param string|array $attribute
235
     * @param mixed        $value
236
     * @return $this
237
     */
238
    public function setTableAttribute($attribute, $value = null)
239
    {
240
        if (is_array($attribute)) {
241
            return $this->setTableAttributes($attribute);
242
        }
243
244
        $this->tableAttributes[$attribute] = $value;
245
246
        return $this;
247
    }
248
249
    /**
250
     * Add class names to the "class" attribute of HTML table.
251
     *
252
     * @param string|array $class
253
     * @return $this
254
     */
255
    public function addTableClass($class)
256
    {
257
        $class        = is_array($class) ? implode(' ', $class) : $class;
258
        $currentClass = Arr::get(array_change_key_case($this->tableAttributes), 'class');
259
260
        $classes = preg_split('#\s+#', $currentClass . ' ' . $class, null, PREG_SPLIT_NO_EMPTY);
261
        $class   = implode(' ', array_unique($classes));
262
263
        return $this->setTableAttribute('class', $class);
264
    }
265
266
    /**
267
     * Remove class names from the "class" attribute of HTML table.
268
     *
269
     * @param string|array $class
270
     * @return $this
271
     */
272
    public function removeTableClass($class)
273
    {
274
        $class        = is_array($class) ? implode(' ', $class) : $class;
275
        $currentClass = Arr::get(array_change_key_case($this->tableAttributes), 'class');
276
277
        $classes = array_diff(
278
            preg_split('#\s+#', $currentClass, null, PREG_SPLIT_NO_EMPTY),
279
            preg_split('#\s+#', $class, null, PREG_SPLIT_NO_EMPTY)
280
        );
281
        $class   = implode(' ', array_unique($classes));
282
283
        return $this->setTableAttribute('class', $class);
284
    }
285
286
    /**
287
     * Add a column in collection usingsl attributes.
288
     *
289
     * @param  array $attributes
290
     * @return $this
291
     */
292
    public function addColumn(array $attributes)
293
    {
294
        $this->collection->push(new Column($attributes));
295
296
        return $this;
297
    }
298
299
    /**
300
     * Add a Column object at the beginning of collection.
301
     *
302
     * @param \Yajra\DataTables\Html\Column $column
303
     * @return $this
304
     */
305
    public function addBefore(Column $column)
306
    {
307
        $this->collection->prepend($column);
308
309
        return $this;
310
    }
311
312
    /**
313
     * Add a column at the beginning of collection using attributes.
314
     *
315
     * @param  array $attributes
316
     * @return $this
317
     */
318
    public function addColumnBefore(array $attributes)
319
    {
320
        $this->collection->prepend(new Column($attributes));
321
322
        return $this;
323
    }
324
325
    /**
326
     * Add a Column object in collection.
327
     *
328
     * @param \Yajra\DataTables\Html\Column $column
329
     * @return $this
330
     */
331
    public function add(Column $column)
332
    {
333
        $this->collection->push($column);
334
335
        return $this;
336
    }
337
338
    /**
339
     * Set datatables columns from array definition.
340
     *
341
     * @param array $columns
342
     * @return $this
343
     */
344
    public function columns(array $columns)
345
    {
346
        $this->collection = new Collection;
347
348
        foreach ($columns as $key => $value) {
349
            if (! is_a($value, Column::class)) {
350
                if (is_array($value)) {
351
                    $attributes = array_merge(
352
                        [
353
                            'name' => $value['name'] ?? $value['data'] ?? $key,
354
                            'data' => $value['data'] ?? $key,
355
                        ],
356
                        $this->setTitle($key, $value)
357
                    );
358
                } else {
359
                    $attributes = [
360
                        'name'  => $value,
361
                        'data'  => $value,
362
                        'title' => $this->getQualifiedTitle($value),
363
                    ];
364
                }
365
366
                $this->collection->push(new Column($attributes));
367
            } else {
368
                $this->collection->push($value);
369
            }
370
        }
371
372
        return $this;
373
    }
374
375
    /**
376
     * Set title attribute of an array if not set.
377
     *
378
     * @param string $title
379
     * @param array  $attributes
380
     * @return array
381
     */
382
    public function setTitle($title, array $attributes)
383
    {
384
        if (! isset($attributes['title'])) {
385
            $attributes['title'] = $this->getQualifiedTitle($title);
386
        }
387
388
        return $attributes;
389
    }
390
391
    /**
392
     * Convert string into a readable title.
393
     *
394
     * @param string $title
395
     * @return string
396
     */
397
    public function getQualifiedTitle($title)
398
    {
399
        return Str::title(str_replace(['.', '_'], ' ', Str::snake($title)));
400
    }
401
402
    /**
403
     * Add a checkbox column.
404
     *
405
     * @param  array $attributes
406
     * @param  bool|int $position true to prepend, false to append or a zero-based index for positioning
407
     * @return $this
408
     */
409
    public function addCheckbox(array $attributes = [], $position = false)
410
    {
411
        $attributes = array_merge([
412
            'defaultContent' => '<input type="checkbox" ' . $this->html->attributes($attributes) . '/>',
413
            'title'          => '<input type="checkbox" ' . $this->html->attributes($attributes + ['id' => 'dataTablesCheckbox']) . '/>',
414
            'data'           => 'checkbox',
415
            'name'           => 'checkbox',
416
            'orderable'      => false,
417
            'searchable'     => false,
418
            'exportable'     => false,
419
            'printable'      => true,
420
            'width'          => '10px',
421
        ], $attributes);
422
        $column = new Column($attributes);
423
424
        if ($position === true) {
425
            $this->collection->prepend($column);
426
        } elseif ($position === false || $position >= $this->collection->count()) {
427
            $this->collection->push($column);
428
        } else {
429
            $this->collection->splice($position, 0, [$column]);
430
        }
431
432
        return $this;
433
    }
434
435
    /**
436
     * Add a action column.
437
     *
438
     * @param  array $attributes
439
     * @return $this
440
     */
441
    public function addAction(array $attributes = [])
442
    {
443
        $attributes = array_merge([
444
            'defaultContent' => '',
445
            'data'           => 'action',
446
            'name'           => 'action',
447
            'title'          => 'Action',
448
            'render'         => null,
449
            'orderable'      => false,
450
            'searchable'     => false,
451
            'exportable'     => false,
452
            'printable'      => true,
453
            'footer'         => '',
454
        ], $attributes);
455
        $this->collection->push(new Column($attributes));
456
457
        return $this;
458
    }
459
460
    /**
461
     * Add a index column.
462
     *
463
     * @param  array $attributes
464
     * @return $this
465
     */
466
    public function addIndex(array $attributes = [])
467
    {
468
        $indexColumn = $this->config->get('datatables.index_column', 'DT_Row_Index');
469
        $attributes  = array_merge([
470
            'defaultContent' => '',
471
            'data'           => $indexColumn,
472
            'name'           => $indexColumn,
473
            'title'          => '',
474
            'render'         => null,
475
            'orderable'      => false,
476
            'searchable'     => false,
477
            'exportable'     => false,
478
            'printable'      => true,
479
            'footer'         => '',
480
        ], $attributes);
481
        $this->collection->push(new Column($attributes));
482
483
        return $this;
484
    }
485
486
    /**
487
     * Setup ajax parameter for datatables pipeline plugin.
488
     *
489
     * @param  string $url
490
     * @param  string $pages
491
     * @return $this
492
     */
493
    public function pipeline($url, $pages)
494
    {
495
        $this->ajax = "$.fn.dataTable.pipeline({ url: '{$url}', pages: {$pages} })";
496
497
        return $this;
498
    }
499
500
    /**
501
     * Setup "ajax" parameter with POST method.
502
     *
503
     * @param  string|array $attributes
504
     * @return $this
505
     */
506
    public function postAjax($attributes = '')
507
    {
508
        if (! is_array($attributes)) {
509
            $attributes = ['url' => (string) $attributes];
510
        }
511
512
        unset($attributes['method']);
513
        Arr::set($attributes, 'type', 'POST');
514
        Arr::set($attributes, 'headers.X-HTTP-Method-Override', 'GET');
515
516
        return $this->ajax($attributes);
517
    }
518
519
    /**
520
     * Setup ajax parameter.
521
     *
522
     * @param  string|array $attributes
523
     * @return $this
524
     */
525
    public function ajax($attributes = '')
526
    {
527
        $this->ajax = $attributes;
528
529
        return $this;
530
    }
531
532
    /**
533
     * Generate DataTable's table html.
534
     *
535
     * @param array $attributes
536
     * @param bool  $drawFooter
537
     * @param bool  $drawSearch
538
     * @return \Illuminate\Support\HtmlString
539
     */
540
    public function table(array $attributes = [], $drawFooter = false, $drawSearch = false)
541
    {
542
        $this->setTableAttributes($attributes);
543
544
        $th       = $this->compileTableHeaders();
545
        $htmlAttr = $this->html->attributes($this->tableAttributes);
546
547
        $tableHtml  = '<table ' . $htmlAttr . '>';
548
        $searchHtml = $drawSearch ? '<tr class="search-filter">' . implode('',
549
                $this->compileTableSearchHeaders()) . '</tr>' : '';
550
        $tableHtml .= '<thead><tr>' . implode('', $th) . '</tr>' . $searchHtml . '</thead>';
551
        if ($drawFooter) {
552
            $tf        = $this->compileTableFooter();
553
            $tableHtml .= '<tfoot><tr>' . implode('', $tf) . '</tr></tfoot>';
554
        }
555
        $tableHtml .= '</table>';
556
557
        return new HtmlString($tableHtml);
558
    }
559
560
    /**
561
     * Compile table headers and to support responsive extension.
562
     *
563
     * @return array
564
     */
565
    private function compileTableHeaders()
566
    {
567
        $th = [];
568
        foreach ($this->collection->toArray() as $row) {
569
            $thAttr = $this->html->attributes(array_merge(
570
                array_only($row, ['class', 'id', 'width', 'style', 'data-class', 'data-hide']),
571
                $row['attributes']
572
            ));
573
            $th[]   = '<th ' . $thAttr . '>' . $row['title'] . '</th>';
574
        }
575
576
        return $th;
577
    }
578
579
    /**
580
     * Compile table search headers.
581
     *
582
     * @return array
583
     */
584
    private function compileTableSearchHeaders()
585
    {
586
        $search = [];
587
        foreach ($this->collection->all() as $key => $row) {
588
            $search[] = $row['searchable'] ? '<th>' . (isset($row->search) ? $row->search : '') . '</th>' : '<th></th>';
589
        }
590
591
        return $search;
592
    }
593
594
    /**
595
     * Compile table footer contents.
596
     *
597
     * @return array
598
     */
599
    private function compileTableFooter()
600
    {
601
        $footer = [];
602
        foreach ($this->collection->all() as $row) {
603
            if (is_array($row->footer)) {
604
                $footerAttr = $this->html->attributes(array_only($row->footer,
605
                    ['class', 'id', 'width', 'style', 'data-class', 'data-hide']));
606
                $title      = isset($row->footer['title']) ? $row->footer['title'] : '';
607
                $footer[]   = '<th ' . $footerAttr . '>' . $title . '</th>';
608
            } else {
609
                $footer[] = '<th>' . $row->footer . '</th>';
610
            }
611
        }
612
613
        return $footer;
614
    }
615
616
    /**
617
     * Configure DataTable's parameters.
618
     *
619
     * @param  array $attributes
620
     * @return $this
621
     */
622
    public function parameters(array $attributes = [])
623
    {
624
        $this->attributes = array_merge($this->attributes, $attributes);
625
626
        return $this;
627
    }
628
629
    /**
630
     * Set custom javascript template.
631
     *
632
     * @param string $template
633
     * @return $this
634
     */
635
    public function setTemplate($template)
636
    {
637
        $this->template = $template;
638
639
        return $this;
640
    }
641
642
    /**
643
     * Get collection of columns.
644
     *
645
     * @return \Illuminate\Support\Collection
646
     */
647
    public function getColumns()
648
    {
649
        return $this->collection;
650
    }
651
652
    /**
653
     * Remove column by name.
654
     *
655
     * @param array $names
656
     * @return $this
657
     */
658
    public function removeColumn(...$names)
659
    {
660
        foreach ($names as $name) {
661
            $this->collection = $this->collection->filter(function (Column $column) use ($name) {
662
                return $column->name !== $name;
663
            })->flatten();
664
        }
665
666
        return $this;
667
    }
668
669
    /**
670
     * Minify ajax url generated when using get request
671
     * by deleting unnecessary url params.
672
     *
673
     * @param string $url
674
     * @param string $script
675
     * @param array  $data
676
     * @param array  $ajaxParameters
677
     * @return $this
678
     */
679
    public function minifiedAjax($url = '', $script = null, $data = [], $ajaxParameters = [])
680
    {
681
        $this->ajax = [];
682
        $appendData = $this->makeDataScript($data);
683
684
        $this->ajax['url']  = $url;
685
        $this->ajax['type'] = 'GET';
686
        if (isset($this->attributes['serverSide']) ? $this->attributes['serverSide'] : true) {
687
            $this->ajax['data'] = 'function(data) {
688
            for (var i = 0, len = data.columns.length; i < len; i++) {
689
                if (!data.columns[i].search.value) delete data.columns[i].search;
690
                if (data.columns[i].searchable === true) delete data.columns[i].searchable;
691
                if (data.columns[i].orderable === true) delete data.columns[i].orderable;
692
                if (data.columns[i].data === data.columns[i].name) delete data.columns[i].name;
693
            }
694
            delete data.search.regex;';
695
        } else {
696
            $this->ajax['data'] = 'function(data){';
697
        }
698
699
        if ($appendData) {
700
            $this->ajax['data'] .= $appendData;
701
        }
702
703
        if ($script) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $script of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
704
            $this->ajax['data'] .= $script;
705
        }
706
707
        $this->ajax['data'] .= '}';
708
709
        $this->ajax = array_merge($this->ajax, $ajaxParameters);
710
711
        return $this;
712
    }
713
714
    /**
715
     * Make a data script to be appended on ajax request of dataTables.
716
     *
717
     * @param array $data
718
     * @return string
719
     */
720
    protected function makeDataScript(array $data)
721
    {
722
        $script = '';
723
        foreach ($data as $key => $value) {
724
            $script .= PHP_EOL . "data.{$key} = '{$value}';";
725
        }
726
727
        return $script;
728
    }
729
730
    /**
731
     * Compile DataTable callback value.
732
     *
733
     * @param mixed $callback
734
     * @return mixed|string
735
     */
736
    private function compileCallback($callback)
737
    {
738
        if (is_callable($callback)) {
739
            return value($callback);
740
        } elseif ($this->view->exists($callback)) {
741
            return $this->view->make($callback)->render();
742
        }
743
744
        return $callback;
745
    }
746
}
747