Completed
Push — master ( e98ec0...b5ecb4 )
by Arjay
07:49
created

DataTableAbstract::create()   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 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Yajra\DataTables;
4
5
use Illuminate\Contracts\Logging\Log;
6
use Illuminate\Contracts\Support\Arrayable;
7
use Illuminate\Contracts\Support\Jsonable;
8
use Illuminate\Http\JsonResponse;
9
use Illuminate\Support\Str;
10
use Illuminate\Support\Traits\Macroable;
11
use Yajra\DataTables\Contracts\DataTable;
12
use Yajra\DataTables\Exceptions\Exception;
13
use Yajra\DataTables\Processors\DataProcessor;
14
use Yajra\DataTables\Utilities\Helper;
15
16
/**
17
 * @method DataTableAbstract setTransformer($transformer)
18
 * @method DataTableAbstract setSerializer($transformer)
19
 * @property mixed transformer
20
 * @property mixed serializer
21
 * @see     https://github.com/yajra/laravel-datatables-fractal for transformer related methods.
22
 */
23
abstract class DataTableAbstract implements DataTable, Arrayable, Jsonable
24
{
25
    use Macroable;
26
27
    /**
28
     * DataTables Request object.
29
     *
30
     * @var \Yajra\DataTables\Utilities\Request
31
     */
32
    public $request;
33
34
    /**
35
     * @var \Illuminate\Contracts\Logging\Log
36
     */
37
    protected $logger;
38
39
    /**
40
     * Array of result columns/fields.
41
     *
42
     * @var array
43
     */
44
    protected $columns = [];
45
46
    /**
47
     * DT columns definitions container (add/edit/remove/filter/order/escape).
48
     *
49
     * @var array
50
     */
51
    protected $columnDef = [
52
        'index'  => false,
53
        'append' => [],
54
        'edit'   => [],
55
        'filter' => [],
56
        'order'  => [],
57
    ];
58
59
    /**
60
     * Extra/Added columns.
61
     *
62
     * @var array
63
     */
64
    protected $extraColumns = [];
65
66
    /**
67
     * Total records.
68
     *
69
     * @var int
70
     */
71
    protected $totalRecords = 0;
72
73
    /**
74
     * Total filtered records.
75
     *
76
     * @var int
77
     */
78
    protected $filteredRecords = 0;
79
80
    /**
81
     * Auto-filter flag.
82
     *
83
     * @var bool
84
     */
85
    protected $autoFilter = true;
86
87
    /**
88
     * Callback to override global search.
89
     *
90
     * @var callable
91
     */
92
    protected $filterCallback;
93
94
    /**
95
     * DT row templates container.
96
     *
97
     * @var array
98
     */
99
    protected $templates = [
100
        'DT_RowId'    => '',
101
        'DT_RowClass' => '',
102
        'DT_RowData'  => [],
103
        'DT_RowAttr'  => [],
104
    ];
105
106
    /**
107
     * [internal] Track if any filter was applied for at least one column.
108
     *
109
     * @var boolean
110
     */
111
    protected $isFilterApplied = false;
112
113
    /**
114
     * Custom ordering callback.
115
     *
116
     * @var callable
117
     */
118
    protected $orderCallback;
119
120
    /**
121
     * Skip paginate as needed.
122
     *
123
     * @var bool
124
     */
125
    protected $skipPaging = false;
126
127
    /**
128
     * Array of data to append on json response.
129
     *
130
     * @var array
131
     */
132
    protected $appends = [];
133
134
    /**
135
     * @var \Yajra\DataTables\Utilities\Config
136
     */
137
    protected $config;
138
139
    /**
140
     * Can the DataTable engine be created with these parameters.
141
     *
142
     * @param mixed $source
143
     * @return boolean
144
     */
145
    public static function canCreate($source)
146
    {
147
        return false;
148
    }
149
 
150
    /**
151
     * Factory method, create and return an instance for the DataTable engine.
152
     *
153
     * @param mixed $source
154
     * @return DataTableAbstract
155
     */
156
    public static function create($source)
157
    {
158
        return new static($source);
0 ignored issues
show
Unused Code introduced by
The call to DataTableAbstract::__construct() has too many arguments starting with $source.

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...
159
    }
160
161
    /**
162
     * Add column in collection.
163
     *
164
     * @param string          $name
165
     * @param string|callable $content
166
     * @param bool|int        $order
167
     * @return $this
168
     */
169
    public function addColumn($name, $content, $order = false)
170
    {
171
        $this->extraColumns[] = $name;
172
173
        $this->columnDef['append'][] = ['name' => $name, 'content' => $content, 'order' => $order];
174
175
        return $this;
176
    }
177
178
    /**
179
     * Add DT row index column on response.
180
     *
181
     * @return $this
182
     */
183
    public function addIndexColumn()
184
    {
185
        $this->columnDef['index'] = true;
186
187
        return $this;
188
    }
189
190
    /**
191
     * Edit column's content.
192
     *
193
     * @param string          $name
194
     * @param string|callable $content
195
     * @return $this
196
     */
197
    public function editColumn($name, $content)
198
    {
199
        $this->columnDef['edit'][] = ['name' => $name, 'content' => $content];
200
201
        return $this;
202
    }
203
204
    /**
205
     * Remove column from collection.
206
     *
207
     * @return $this
208
     */
209
    public function removeColumn()
210
    {
211
        $names                     = func_get_args();
212
        $this->columnDef['excess'] = array_merge($this->getColumnsDefinition()['excess'], $names);
213
214
        return $this;
215
    }
216
217
    /**
218
     * Declare columns to escape values.
219
     *
220
     * @param string|array $columns
221
     * @return $this
222
     */
223
    public function escapeColumns($columns = '*')
224
    {
225
        $this->columnDef['escape'] = $columns;
226
227
        return $this;
228
    }
229
230
    /**
231
     * Set columns that should not be escaped.
232
     *
233
     * @param array $columns
234
     * @return $this
235
     */
236
    public function rawColumns(array $columns)
237
    {
238
        $this->columnDef['raw'] = $columns;
239
240
        return $this;
241
    }
242
243
    /**
244
     * Sets DT_RowClass template.
245
     * result: <tr class="output_from_your_template">.
246
     *
247
     * @param string|callable $content
248
     * @return $this
249
     */
250
    public function setRowClass($content)
251
    {
252
        $this->templates['DT_RowClass'] = $content;
253
254
        return $this;
255
    }
256
257
    /**
258
     * Sets DT_RowId template.
259
     * result: <tr id="output_from_your_template">.
260
     *
261
     * @param string|callable $content
262
     * @return $this
263
     */
264
    public function setRowId($content)
265
    {
266
        $this->templates['DT_RowId'] = $content;
267
268
        return $this;
269
    }
270
271
    /**
272
     * Set DT_RowData templates.
273
     *
274
     * @param array $data
275
     * @return $this
276
     */
277
    public function setRowData(array $data)
278
    {
279
        $this->templates['DT_RowData'] = $data;
280
281
        return $this;
282
    }
283
284
    /**
285
     * Add DT_RowData template.
286
     *
287
     * @param string          $key
288
     * @param string|callable $value
289
     * @return $this
290
     */
291
    public function addRowData($key, $value)
292
    {
293
        $this->templates['DT_RowData'][$key] = $value;
294
295
        return $this;
296
    }
297
298
    /**
299
     * Set DT_RowAttr templates.
300
     * result: <tr attr1="attr1" attr2="attr2">.
301
     *
302
     * @param array $data
303
     * @return $this
304
     */
305
    public function setRowAttr(array $data)
306
    {
307
        $this->templates['DT_RowAttr'] = $data;
308
309
        return $this;
310
    }
311
312
    /**
313
     * Add DT_RowAttr template.
314
     *
315
     * @param string          $key
316
     * @param string|callable $value
317
     * @return $this
318
     */
319
    public function addRowAttr($key, $value)
320
    {
321
        $this->templates['DT_RowAttr'][$key] = $value;
322
323
        return $this;
324
    }
325
326
    /**
327
     * Append data on json response.
328
     *
329
     * @param mixed $key
330
     * @param mixed $value
331
     * @return $this
332
     */
333
    public function with($key, $value = '')
334
    {
335
        if (is_array($key)) {
336
            $this->appends = $key;
337
        } elseif (is_callable($value)) {
338
            $this->appends[$key] = value($value);
339
        } else {
340
            $this->appends[$key] = value($value);
341
        }
342
343
        return $this;
344
    }
345
346
    /**
347
     * Override default ordering method with a closure callback.
348
     *
349
     * @param callable $closure
350
     * @return $this
351
     */
352
    public function order(callable $closure)
353
    {
354
        $this->orderCallback = $closure;
355
356
        return $this;
357
    }
358
359
    /**
360
     * Update list of columns that is not allowed for search/sort.
361
     *
362
     * @param  array $blacklist
363
     * @return $this
364
     */
365
    public function blacklist(array $blacklist)
366
    {
367
        $this->columnDef['blacklist'] = $blacklist;
368
369
        return $this;
370
    }
371
372
    /**
373
     * Update list of columns that is allowed for search/sort.
374
     *
375
     * @param  string|array $whitelist
376
     * @return $this
377
     */
378
    public function whitelist($whitelist = '*')
379
    {
380
        $this->columnDef['whitelist'] = $whitelist;
381
382
        return $this;
383
    }
384
385
    /**
386
     * Set smart search config at runtime.
387
     *
388
     * @param bool $bool
389
     * @return $this
390
     */
391
    public function smart($bool = true)
392
    {
393
        $this->config->set(['datatables.search.smart' => $bool]);
394
395
        return $this;
396
    }
397
398
    /**
399
     * Set total records manually.
400
     *
401
     * @param int $total
402
     * @return $this
403
     */
404
    public function setTotalRecords($total)
405
    {
406
        $this->totalRecords = $total;
407
408
        return $this;
409
    }
410
411
    /**
412
     * Skip pagination as needed.
413
     *
414
     * @return $this
415
     */
416
    public function skipPaging()
417
    {
418
        $this->skipPaging = true;
419
420
        return $this;
421
    }
422
423
    /**
424
     * Push a new column name to blacklist.
425
     *
426
     * @param string $column
427
     * @return $this
428
     */
429
    public function pushToBlacklist($column)
430
    {
431
        if (!$this->isBlacklisted($column)) {
432
            $this->columnDef['blacklist'][] = $column;
433
        }
434
435
        return $this;
436
    }
437
438
    /**
439
     * Check if column is blacklisted.
440
     *
441
     * @param string $column
442
     * @return bool
443
     */
444
    protected function isBlacklisted($column)
445
    {
446
        $colDef = $this->getColumnsDefinition();
447
448
        if (in_array($column, $colDef['blacklist'])) {
449
            return true;
450
        }
451
452
        if ($colDef['whitelist'] === '*' || in_array($column, $colDef['whitelist'])) {
453
            return false;
454
        }
455
456
        return true;
457
    }
458
459
    /**
460
     * Get columns definition.
461
     *
462
     * @return array
463
     */
464
    protected function getColumnsDefinition()
465
    {
466
        $config  = $this->config->get('datatables.columns');
467
        $allowed = ['excess', 'escape', 'raw', 'blacklist', 'whitelist'];
468
469
        return array_replace_recursive(array_only($config, $allowed), $this->columnDef);
470
    }
471
472
    /**
473
     * Perform sorting of columns.
474
     */
475
    public function ordering()
476
    {
477
        if ($this->orderCallback) {
478
            return call_user_func($this->orderCallback, $this->resolveCallbackParameter());
479
        }
480
481
        return $this->defaultOrdering();
482
    }
483
484
    /**
485
     * Resolve callback parameter instance.
486
     *
487
     * @return mixed
488
     */
489
    abstract protected function resolveCallbackParameter();
490
491
    /**
492
     * Perform default query orderBy clause.
493
     */
494
    abstract protected function defaultOrdering();
495
496
    /**
497
     * Set auto filter off and run your own filter.
498
     * Overrides global search.
499
     *
500
     * @param callable $callback
501
     * @param bool     $globalSearch
502
     * @return $this
503
     */
504
    public function filter(callable $callback, $globalSearch = false)
505
    {
506
        $this->autoFilter      = $globalSearch;
507
        $this->isFilterApplied = true;
508
        $this->filterCallback  = $callback;
509
510
        return $this;
511
    }
512
513
    /**
514
     * Convert instance to array.
515
     *
516
     * @return array
517
     */
518
    public function toArray()
519
    {
520
        return $this->make()->getData(true);
521
    }
522
523
    /**
524
     * Convert the object to its JSON representation.
525
     *
526
     * @param  int $options
527
     * @return \Illuminate\Http\JsonResponse
528
     */
529
    public function toJson($options = 0)
530
    {
531
        if ($options) {
532
            $this->config->set('datatables.json.options', $options);
533
        }
534
535
        return $this->make();
536
    }
537
538
    /**
539
     * Perform necessary filters.
540
     *
541
     * @return void
542
     */
543
    protected function filterRecords()
544
    {
545
        if ($this->autoFilter && $this->request->isSearchable()) {
546
            $this->filtering();
547
        }
548
549
        if (is_callable($this->filterCallback)) {
550
            call_user_func($this->filterCallback, $this->resolveCallbackParameter());
551
        }
552
553
        $this->columnSearch();
554
        $this->filteredRecords = $this->isFilterApplied ? $this->count() : $this->totalRecords;
555
    }
556
557
    /**
558
     * Perform global search.
559
     *
560
     * @return void
561
     */
562
    public function filtering()
563
    {
564
        $keyword = $this->request->keyword();
565
566
        if ($this->config->isMultiTerm()) {
567
            $this->smartGlobalSearch($keyword);
568
569
            return;
570
        }
571
572
        $this->globalSearch($keyword);
573
    }
574
575
    /**
576
     * Perform multi-term search by splitting keyword into
577
     * individual words and searches for each of them.
578
     *
579
     * @param string $keyword
580
     */
581
    protected function smartGlobalSearch($keyword)
582
    {
583
        collect(explode(' ', $keyword))
584
            ->reject(function ($keyword) {
585
                return trim($keyword) === '';
586
            })
587
            ->each(function ($keyword) {
588
                $this->globalSearch($keyword);
589
            });
590
    }
591
592
    /**
593
     * Perform global search for the given keyword.
594
     *
595
     * @param string $keyword
596
     */
597
    abstract protected function globalSearch($keyword);
598
599
    /**
600
     * Apply pagination.
601
     *
602
     * @return void
603
     */
604
    protected function paginate()
605
    {
606
        if ($this->request->isPaginationable() && !$this->skipPaging) {
607
            $this->paging();
608
        }
609
    }
610
611
    /**
612
     * Transform output.
613
     *
614
     * @param mixed $results
615
     * @param mixed $processed
616
     * @return array
617
     */
618
    protected function transform($results, $processed)
619
    {
620
        if (isset($this->transformer) && class_exists('Yajra\\DataTables\\Transformers\\FractalTransformer')) {
621
            return app('datatables.transformer')->transform(
622
                $results,
623
                $this->transformer,
624
                $this->serializer ?? null
625
            );
626
        }
627
628
        return Helper::transform($processed);
629
    }
630
631
    /**
632
     * Get processed data.
633
     *
634
     * @param mixed $results
635
     * @param bool  $object
636
     * @return array
637
     */
638
    protected function processResults($results, $object = false)
639
    {
640
        $processor = new DataProcessor(
641
            $results,
642
            $this->getColumnsDefinition(),
643
            $this->templates,
644
            $this->request->input('start')
645
        );
646
647
        return $processor->process($object);
648
    }
649
650
    /**
651
     * Render json response.
652
     *
653
     * @param array $data
654
     * @return \Illuminate\Http\JsonResponse
655
     */
656
    protected function render(array $data)
657
    {
658
        $output = array_merge([
659
            'draw'            => (int) $this->request->input('draw'),
660
            'recordsTotal'    => $this->totalRecords,
661
            'recordsFiltered' => $this->filteredRecords,
662
            'data'            => $data,
663
        ], $this->appends);
664
665
        if ($this->config->isDebugging()) {
666
            $output = $this->showDebugger($output);
667
        }
668
669
        return new JsonResponse(
670
            $output,
671
            200,
672
            $this->config->get('datatables.json.header', []),
673
            $this->config->get('datatables.json.options', 0)
674
        );
675
    }
676
677
    /**
678
     * Append debug parameters on output.
679
     *
680
     * @param  array $output
681
     * @return array
682
     */
683
    protected function showDebugger(array $output)
684
    {
685
        $output["input"] = $this->request->all();
686
687
        return $output;
688
    }
689
690
    /**
691
     * Return an error json response.
692
     *
693
     * @param \Exception $exception
694
     * @return \Illuminate\Http\JsonResponse
695
     * @throws \Yajra\DataTables\Exceptions\Exception
696
     */
697
    protected function errorResponse(\Exception $exception)
698
    {
699
        $error = $this->config->get('datatables.error');
700
        if ($error === 'throw') {
701
            throw new Exception($exception->getMessage(), $code = 0, $exception);
702
        }
703
704
        $this->getLogger()->error($exception);
705
706
        return new JsonResponse([
707
            'draw'            => (int) $this->request->input('draw'),
708
            'recordsTotal'    => (int) $this->totalRecords,
709
            'recordsFiltered' => 0,
710
            'data'            => [],
711
            'error'           => $error ? __($error) : "Exception Message:\n\n" . $exception->getMessage(),
712
        ]);
713
    }
714
715
    /**
716
     * Get monolog/logger instance.
717
     *
718
     * @return \Illuminate\Contracts\Logging\Log
719
     */
720
    public function getLogger()
721
    {
722
        $this->logger = $this->logger ?: app(Log::class);
723
724
        return $this->logger;
725
    }
726
727
    /**
728
     * Set monolog/logger instance.
729
     *
730
     * @param \Illuminate\Contracts\Logging\Log $logger
731
     * @return $this
732
     */
733
    public function setLogger(Log $logger)
734
    {
735
        $this->logger = $logger;
736
737
        return $this;
738
    }
739
740
    /**
741
     * Setup search keyword.
742
     *
743
     * @param  string $value
744
     * @return string
745
     */
746
    protected function setupKeyword($value)
747
    {
748
        if ($this->config->isSmartSearch()) {
749
            $keyword = '%' . $value . '%';
750
            if ($this->config->isWildcard()) {
751
                $keyword = Helper::wildcardLikeString($value);
752
            }
753
            // remove escaping slash added on js script request
754
            $keyword = str_replace('\\', '%', $keyword);
755
756
            return $keyword;
757
        }
758
759
        return $value;
760
    }
761
762
    /**
763
     * Get column name to be use for filtering and sorting.
764
     *
765
     * @param integer $index
766
     * @param bool    $wantsAlias
767
     * @return string
768
     */
769
    protected function getColumnName($index, $wantsAlias = false)
770
    {
771
        $column = $this->request->columnName($index);
772
773
        // DataTables is using make(false)
774
        if (is_numeric($column)) {
775
            $column = $this->getColumnNameByIndex($index);
776
        }
777
778
        if (Str::contains(Str::upper($column), ' AS ')) {
779
            $column = Helper::extractColumnName($column, $wantsAlias);
780
        }
781
782
        return $column;
783
    }
784
785
    /**
786
     * Get column name by order column index.
787
     *
788
     * @param int $index
789
     * @return string
790
     */
791
    protected function getColumnNameByIndex($index)
792
    {
793
        $name = (isset($this->columns[$index]) && $this->columns[$index] != '*')
794
            ? $this->columns[$index] : $this->getPrimaryKeyName();
795
796
        return in_array($name, $this->extraColumns, true) ? $this->getPrimaryKeyName() : $name;
797
    }
798
799
    /**
800
     * If column name could not be resolved then use primary key.
801
     *
802
     * @return string
803
     */
804
    protected function getPrimaryKeyName()
805
    {
806
        return 'id';
807
    }
808
}
809