Completed
Push — master ( 787eb0...976e70 )
by Arjay
09:00
created

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