Completed
Push — master ( 3c8116...95fc21 )
by Arjay
01:54
created

DataTableAbstract::setFilteredRecords()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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