Completed
Push — master ( b3476e...5a1910 )
by Arjay
08:06
created

Builder::isCallbackFunction()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 2
eloc 2
nc 2
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(['name' => $key, 'data' => $key], $this->setTitle($key, $value));
352
                } else {
353
                    $attributes = [
354
                        'name'  => $value,
355
                        'data'  => $value,
356
                        'title' => $this->getQualifiedTitle($value),
357
                    ];
358
                }
359
360
                $this->collection->push(new Column($attributes));
361
            } else {
362
                $this->collection->push($value);
363
            }
364
        }
365
366
        return $this;
367
    }
368
369
    /**
370
     * Set title attribute of an array if not set.
371
     *
372
     * @param string $title
373
     * @param array  $attributes
374
     * @return array
375
     */
376
    public function setTitle($title, array $attributes)
377
    {
378
        if (! isset($attributes['title'])) {
379
            $attributes['title'] = $this->getQualifiedTitle($title);
380
        }
381
382
        return $attributes;
383
    }
384
385
    /**
386
     * Convert string into a readable title.
387
     *
388
     * @param string $title
389
     * @return string
390
     */
391
    public function getQualifiedTitle($title)
392
    {
393
        return Str::title(str_replace(['.', '_'], ' ', Str::snake($title)));
394
    }
395
396
    /**
397
     * Add a checkbox column.
398
     *
399
     * @param  array $attributes
400
     * @return $this
401
     */
402
    public function addCheckbox(array $attributes = [])
403
    {
404
        $attributes = array_merge([
405
            'defaultContent' => '<input type="checkbox" ' . $this->html->attributes($attributes) . '/>',
406
            'title'          => '<input type="checkbox" ' . $this->html->attributes($attributes + ['id' => 'dataTablesCheckbox']) . '/>',
407
            'data'           => 'checkbox',
408
            'name'           => 'checkbox',
409
            'orderable'      => false,
410
            'searchable'     => false,
411
            'exportable'     => false,
412
            'printable'      => true,
413
            'width'          => '10px',
414
        ], $attributes);
415
        $this->collection->push(new Column($attributes));
416
417
        return $this;
418
    }
419
420
    /**
421
     * Add a action column.
422
     *
423
     * @param  array $attributes
424
     * @return $this
425
     */
426
    public function addAction(array $attributes = [])
427
    {
428
        $attributes = array_merge([
429
            'defaultContent' => '',
430
            'data'           => 'action',
431
            'name'           => 'action',
432
            'title'          => 'Action',
433
            'render'         => null,
434
            'orderable'      => false,
435
            'searchable'     => false,
436
            'exportable'     => false,
437
            'printable'      => true,
438
            'footer'         => '',
439
        ], $attributes);
440
        $this->collection->push(new Column($attributes));
441
442
        return $this;
443
    }
444
445
    /**
446
     * Add a index column.
447
     *
448
     * @param  array $attributes
449
     * @return $this
450
     */
451
    public function addIndex(array $attributes = [])
452
    {
453
        $indexColumn = $this->config->get('datatables.index_column', 'DT_Row_Index');
454
        $attributes  = array_merge([
455
            'defaultContent' => '',
456
            'data'           => $indexColumn,
457
            'name'           => $indexColumn,
458
            'title'          => '',
459
            'render'         => null,
460
            'orderable'      => false,
461
            'searchable'     => false,
462
            'exportable'     => false,
463
            'printable'      => true,
464
            'footer'         => '',
465
        ], $attributes);
466
        $this->collection->push(new Column($attributes));
467
468
        return $this;
469
    }
470
471
    /**
472
     * Setup ajax parameter for datatables pipeline plugin.
473
     *
474
     * @param  string $url
475
     * @param  string $pages
476
     * @return $this
477
     */
478
    public function pipeline($url, $pages)
479
    {
480
        $this->ajax = "$.fn.dataTable.pipeline({ url: '{$url}', pages: {$pages} })";
481
482
        return $this;
483
    }
484
485
    /**
486
     * Setup "ajax" parameter with POST method.
487
     *
488
     * @param  string|array $attributes
489
     * @return $this
490
     */
491
    public function postAjax($attributes = '')
492
    {
493
        if (! is_array($attributes)) {
494
            $attributes = ['url' => (string) $attributes];
495
        }
496
497
        unset($attributes['method']);
498
        Arr::set($attributes, 'type', 'POST');
499
        Arr::set($attributes, 'headers.X-HTTP-Method-Override', 'GET');
500
501
        return $this->ajax($attributes);
502
    }
503
504
    /**
505
     * Setup ajax parameter.
506
     *
507
     * @param  string|array $attributes
508
     * @return $this
509
     */
510
    public function ajax($attributes = '')
511
    {
512
        $this->ajax = $attributes;
513
514
        return $this;
515
    }
516
517
    /**
518
     * Generate DataTable's table html.
519
     *
520
     * @param array $attributes
521
     * @param bool  $drawFooter
522
     * @param bool  $drawSearch
523
     * @return \Illuminate\Support\HtmlString
524
     */
525
    public function table(array $attributes = [], $drawFooter = false, $drawSearch = false)
526
    {
527
        $this->setTableAttributes($attributes);
528
529
        $th       = $this->compileTableHeaders();
530
        $htmlAttr = $this->html->attributes($this->tableAttributes);
531
532
        $tableHtml  = '<table ' . $htmlAttr . '>';
533
        $searchHtml = $drawSearch ? '<tr class="search-filter">' . implode('',
534
                $this->compileTableSearchHeaders()) . '</tr>' : '';
535
        $tableHtml .= '<thead><tr>' . implode('', $th) . '</tr>' . $searchHtml . '</thead>';
536
        if ($drawFooter) {
537
            $tf        = $this->compileTableFooter();
538
            $tableHtml .= '<tfoot><tr>' . implode('', $tf) . '</tr></tfoot>';
539
        }
540
        $tableHtml .= '</table>';
541
542
        return new HtmlString($tableHtml);
543
    }
544
545
    /**
546
     * Compile table headers and to support responsive extension.
547
     *
548
     * @return array
549
     */
550
    private function compileTableHeaders()
551
    {
552
        $th = [];
553
        foreach ($this->collection->toArray() as $row) {
554
            $thAttr = $this->html->attributes(array_merge(
555
                array_only($row, ['class', 'id', 'width', 'style', 'data-class', 'data-hide']),
556
                $row['attributes']
557
            ));
558
            $th[]   = '<th ' . $thAttr . '>' . $row['title'] . '</th>';
559
        }
560
561
        return $th;
562
    }
563
564
    /**
565
     * Compile table search headers.
566
     *
567
     * @return array
568
     */
569
    private function compileTableSearchHeaders()
570
    {
571
        $search = [];
572
        foreach ($this->collection->all() as $key => $row) {
573
            $search[] = $row['searchable'] ? '<th>' . (isset($row->search) ? $row->search : '') . '</th>' : '<th></th>';
574
        }
575
576
        return $search;
577
    }
578
579
    /**
580
     * Compile table footer contents.
581
     *
582
     * @return array
583
     */
584
    private function compileTableFooter()
585
    {
586
        $footer = [];
587
        foreach ($this->collection->all() as $row) {
588
            if (is_array($row->footer)) {
589
                $footerAttr = $this->html->attributes(array_only($row->footer,
590
                    ['class', 'id', 'width', 'style', 'data-class', 'data-hide']));
591
                $title      = isset($row->footer['title']) ? $row->footer['title'] : '';
592
                $footer[]   = '<th ' . $footerAttr . '>' . $title . '</th>';
593
            } else {
594
                $footer[] = '<th>' . $row->footer . '</th>';
595
            }
596
        }
597
598
        return $footer;
599
    }
600
601
    /**
602
     * Configure DataTable's parameters.
603
     *
604
     * @param  array $attributes
605
     * @return $this
606
     */
607
    public function parameters(array $attributes = [])
608
    {
609
        $this->attributes = array_merge($this->attributes, $attributes);
610
611
        return $this;
612
    }
613
614
    /**
615
     * Set custom javascript template.
616
     *
617
     * @param string $template
618
     * @return $this
619
     */
620
    public function setTemplate($template)
621
    {
622
        $this->template = $template;
623
624
        return $this;
625
    }
626
627
    /**
628
     * Get collection of columns.
629
     *
630
     * @return \Illuminate\Support\Collection
631
     */
632
    public function getColumns()
633
    {
634
        return $this->collection;
635
    }
636
637
    /**
638
     * Remove column by name.
639
     *
640
     * @param array $names
641
     * @return $this
642
     */
643
    public function removeColumn(...$names)
644
    {
645
        foreach ($names as $name) {
646
            $this->collection = $this->collection->filter(function (Column $column) use ($name) {
647
                return $column->name !== $name;
648
            })->flatten();
649
        }
650
651
        return $this;
652
    }
653
654
    /**
655
     * Minify ajax url generated when using get request
656
     * by deleting unnecessary url params.
657
     *
658
     * @param string $url
659
     * @param string $script
660
     * @param array  $data
661
     * @return $this
662
     */
663
    public function minifiedAjax($url = '', $script = null, $data = [])
664
    {
665
        $this->ajax = [];
666
        $appendData = $this->makeDataScript($data);
667
668
        $this->ajax['url']  = $url;
669
        $this->ajax['type'] = 'GET';
670
        if (isset($this->attributes['serverSide']) ? $this->attributes['serverSide'] : true) {
671
            $this->ajax['data'] = 'function(data) {
672
            for (var i = 0, len = data.columns.length; i < len; i++) {
673
                if (!data.columns[i].search.value) delete data.columns[i].search;
674
                if (data.columns[i].searchable === true) delete data.columns[i].searchable;
675
                if (data.columns[i].orderable === true) delete data.columns[i].orderable;
676
                if (data.columns[i].data === data.columns[i].name) delete data.columns[i].name;
677
            }
678
            delete data.search.regex;';
679
        } else {
680
            $this->ajax['data'] = 'function(data){';
681
        }
682
683
        if ($appendData) {
684
            $this->ajax['data'] .= $appendData;
685
        }
686
687
        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...
688
            $this->ajax['data'] .= $script;
689
        }
690
691
        $this->ajax['data'] .= '}';
692
693
        return $this;
694
    }
695
696
    /**
697
     * Make a data script to be appended on ajax request of dataTables.
698
     *
699
     * @param array $data
700
     * @return string
701
     */
702
    protected function makeDataScript(array $data)
703
    {
704
        $script = '';
705
        foreach ($data as $key => $value) {
706
            $script .= PHP_EOL . "data.{$key} = '{$value}';";
707
        }
708
709
        return $script;
710
    }
711
712
    /**
713
     * Compile DataTable callback value.
714
     *
715
     * @param mixed $callback
716
     * @return mixed|string
717
     */
718
    private function compileCallback($callback)
719
    {
720
        if (is_callable($callback)) {
721
            return value($callback);
722
        } elseif ($this->view->exists($callback)) {
723
            return $this->view->make($callback)->render();
724
        }
725
726
        return $callback;
727
    }
728
}
729