Completed
Push — master ( 177b43...62f14b )
by Arjay
01:27
created

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