Completed
Push — master ( 014bed...f221e4 )
by Arjay
02:09
created

DataTableAbstract::only()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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