Completed
Push — master ( b38d46...cfdcaf )
by Arjay
07:35
created

BaseEngine::removeColumn()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 0
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace Yajra\Datatables\Engines;
4
5
use Illuminate\Contracts\Logging\Log;
6
use Illuminate\Http\JsonResponse;
7
use Illuminate\Support\Str;
8
use Illuminate\Support\Traits\Macroable;
9
use Yajra\Datatables\Contracts\DataTableEngine;
10
use Yajra\Datatables\Exception;
11
use Yajra\Datatables\Helper;
12
use Yajra\Datatables\Processors\DataProcessor;
13
14
/**
15
 * Class BaseEngine.
16
 *
17
 * @package Yajra\Datatables\Engines
18
 * @method setTransformer($transformer)
19
 * @method setSerializer($transformer)
20
 * @see https://github.com/yajra/laravel-datatables-fractal for transformer related methods.
21
 * @author  Arjay Angeles <[email protected]>
22
 */
23
abstract class BaseEngine implements DataTableEngine
24
{
25
    use Macroable;
26
27
    /**
28
     * Datatables Request object.
29
     *
30
     * @var \Yajra\Datatables\Request
31
     */
32
    public $request;
33
34
    /**
35
     * @var \Illuminate\Contracts\Logging\Log
36
     */
37
    protected $logger;
38
39
    /**
40
     * Array of result columns/fields.
41
     *
42
     * @var array
43
     */
44
    protected $columns = [];
45
46
    /**
47
     * DT columns definitions container (add/edit/remove/filter/order/escape).
48
     *
49
     * @var array
50
     */
51
    protected $columnDef = [
52
        'index'  => false,
53
        'append' => [],
54
        'edit'   => [],
55
        'filter' => [],
56
        'order'  => [],
57
    ];
58
59
    /**
60
     * Extra/Added columns.
61
     *
62
     * @var array
63
     */
64
    protected $extraColumns = [];
65
66
    /**
67
     * Total records.
68
     *
69
     * @var int
70
     */
71
    protected $totalRecords = 0;
72
73
    /**
74
     * Total filtered records.
75
     *
76
     * @var int
77
     */
78
    protected $filteredRecords = 0;
79
80
    /**
81
     * Auto-filter flag.
82
     *
83
     * @var bool
84
     */
85
    protected $autoFilter = true;
86
87
    /**
88
     * Callback to override global search.
89
     *
90
     * @var callable
91
     */
92
    protected $filterCallback;
93
94
    /**
95
     * DT row templates container.
96
     *
97
     * @var array
98
     */
99
    protected $templates = [
100
        'DT_RowId'    => '',
101
        'DT_RowClass' => '',
102
        'DT_RowData'  => [],
103
        'DT_RowAttr'  => [],
104
    ];
105
106
    /**
107
     * [internal] Track if any filter was applied for at least one column.
108
     *
109
     * @var boolean
110
     */
111
    protected $isFilterApplied = false;
112
113
    /**
114
     * Custom ordering callback.
115
     *
116
     * @var callable
117
     */
118
    protected $orderCallback;
119
120
    /**
121
     * Skip paginate as needed.
122
     *
123
     * @var bool
124
     */
125
    protected $skipPaging = false;
126
127
    /**
128
     * Array of data to append on json response.
129
     *
130
     * @var array
131
     */
132
    protected $appends = [];
133
134
    /**
135
     * @var \Yajra\Datatables\Config
136
     */
137
    protected $config;
138
139
    /**
140
     * Add column in collection.
141
     *
142
     * @param string          $name
143
     * @param string|callable $content
144
     * @param bool|int        $order
145
     * @return $this
146
     */
147
    public function addColumn($name, $content, $order = false)
148
    {
149
        $this->extraColumns[] = $name;
150
151
        $this->columnDef['append'][] = ['name' => $name, 'content' => $content, 'order' => $order];
152
153
        return $this;
154
    }
155
156
    /**
157
     * Add DT row index column on response.
158
     *
159
     * @return $this
160
     */
161
    public function addIndexColumn()
162
    {
163
        $this->columnDef['index'] = true;
164
165
        return $this;
166
    }
167
168
    /**
169
     * Edit column's content.
170
     *
171
     * @param string          $name
172
     * @param string|callable $content
173
     * @return $this
174
     */
175
    public function editColumn($name, $content)
176
    {
177
        $this->columnDef['edit'][] = ['name' => $name, 'content' => $content];
178
179
        return $this;
180
    }
181
182
    /**
183
     * Remove column from collection.
184
     *
185
     * @return $this
186
     */
187
    public function removeColumn()
188
    {
189
        $names                     = func_get_args();
190
        $this->columnDef['excess'] = array_merge($this->columnDef['excess'], $names);
191
192
        return $this;
193
    }
194
195
    /**
196
     * Declare columns to escape values.
197
     *
198
     * @param string|array $columns
199
     * @return $this
200
     */
201
    public function escapeColumns($columns = '*')
202
    {
203
        $this->columnDef['escape'] = $columns;
204
205
        return $this;
206
    }
207
208
    /**
209
     * Set columns that should not be escaped.
210
     *
211
     * @param array $columns
212
     * @return $this
213
     */
214
    public function rawColumns(array $columns)
215
    {
216
        $this->columnDef['raw'] = $columns;
217
218
        return $this;
219
    }
220
221
    /**
222
     * Sets DT_RowClass template.
223
     * result: <tr class="output_from_your_template">.
224
     *
225
     * @param string|callable $content
226
     * @return $this
227
     */
228
    public function setRowClass($content)
229
    {
230
        $this->templates['DT_RowClass'] = $content;
231
232
        return $this;
233
    }
234
235
    /**
236
     * Sets DT_RowId template.
237
     * result: <tr id="output_from_your_template">.
238
     *
239
     * @param string|callable $content
240
     * @return $this
241
     */
242
    public function setRowId($content)
243
    {
244
        $this->templates['DT_RowId'] = $content;
245
246
        return $this;
247
    }
248
249
    /**
250
     * Set DT_RowData templates.
251
     *
252
     * @param array $data
253
     * @return $this
254
     */
255
    public function setRowData(array $data)
256
    {
257
        $this->templates['DT_RowData'] = $data;
258
259
        return $this;
260
    }
261
262
    /**
263
     * Add DT_RowData template.
264
     *
265
     * @param string          $key
266
     * @param string|callable $value
267
     * @return $this
268
     */
269
    public function addRowData($key, $value)
270
    {
271
        $this->templates['DT_RowData'][$key] = $value;
272
273
        return $this;
274
    }
275
276
    /**
277
     * Set DT_RowAttr templates.
278
     * result: <tr attr1="attr1" attr2="attr2">.
279
     *
280
     * @param array $data
281
     * @return $this
282
     */
283
    public function setRowAttr(array $data)
284
    {
285
        $this->templates['DT_RowAttr'] = $data;
286
287
        return $this;
288
    }
289
290
    /**
291
     * Add DT_RowAttr template.
292
     *
293
     * @param string          $key
294
     * @param string|callable $value
295
     * @return $this
296
     */
297
    public function addRowAttr($key, $value)
298
    {
299
        $this->templates['DT_RowAttr'][$key] = $value;
300
301
        return $this;
302
    }
303
304
    /**
305
     * Append data on json response.
306
     *
307
     * @param mixed $key
308
     * @param mixed $value
309
     * @return $this
310
     */
311
    public function with($key, $value = '')
312
    {
313
        if (is_array($key)) {
314
            $this->appends = $key;
315
        } elseif (is_callable($value)) {
316
            $this->appends[$key] = value($value);
317
        } else {
318
            $this->appends[$key] = value($value);
319
        }
320
321
        return $this;
322
    }
323
324
    /**
325
     * Override default ordering method with a closure callback.
326
     *
327
     * @param callable $closure
328
     * @return $this
329
     */
330
    public function order(callable $closure)
331
    {
332
        $this->orderCallback = $closure;
333
334
        return $this;
335
    }
336
337
    /**
338
     * Update list of columns that is not allowed for search/sort.
339
     *
340
     * @param  array $blacklist
341
     * @return $this
342
     */
343
    public function blacklist(array $blacklist)
344
    {
345
        $this->columnDef['blacklist'] = $blacklist;
346
347
        return $this;
348
    }
349
350
    /**
351
     * Update list of columns that is allowed for search/sort.
352
     *
353
     * @param  string|array $whitelist
354
     * @return $this
355
     */
356
    public function whitelist($whitelist = '*')
357
    {
358
        $this->columnDef['whitelist'] = $whitelist;
359
360
        return $this;
361
    }
362
363
    /**
364
     * Set smart search config at runtime.
365
     *
366
     * @param bool $bool
367
     * @return $this
368
     */
369
    public function smart($bool = true)
370
    {
371
        $this->config->set(['datatables.search.smart' => $bool]);
372
373
        return $this;
374
    }
375
376
    /**
377
     * Set total records manually.
378
     *
379
     * @param int $total
380
     * @return $this
381
     */
382
    public function setTotalRecords($total)
383
    {
384
        $this->totalRecords = $total;
385
386
        return $this;
387
    }
388
389
    /**
390
     * Skip pagination as needed.
391
     *
392
     * @return $this
393
     */
394
    public function skipPaging()
395
    {
396
        $this->skipPaging = true;
397
398
        return $this;
399
    }
400
401
    /**
402
     * Push a new column name to blacklist.
403
     *
404
     * @param string $column
405
     * @return $this
406
     */
407
    public function pushToBlacklist($column)
408
    {
409
        if (!$this->isBlacklisted($column)) {
410
            $this->columnDef['blacklist'][] = $column;
411
        }
412
413
        return $this;
414
    }
415
416
    /**
417
     * Check if column is blacklisted.
418
     *
419
     * @param string $column
420
     * @return bool
421
     */
422
    protected function isBlacklisted($column)
423
    {
424
        $colDef = $this->getColumnsDefinition();
425
426
        if (in_array($column, $colDef['blacklist'])) {
427
            return true;
428
        }
429
430
        if ($colDef['whitelist'] === '*' || in_array($column, $colDef['whitelist'])) {
431
            return false;
432
        }
433
434
        return true;
435
    }
436
437
    /**
438
     * Get columns definition.
439
     *
440
     * @return array
441
     */
442
    protected function getColumnsDefinition()
443
    {
444
        $config  = $this->config->get('datatables.columns');
445
        $allowed = ['excess', 'escape', 'raw', 'blacklist', 'whitelist'];
446
447
        return array_merge(array_only($config, $allowed), $this->columnDef);
448
    }
449
450
    /**
451
     * Perform sorting of columns.
452
     */
453
    public function ordering()
454
    {
455
        if ($this->orderCallback) {
456
            return call_user_func($this->orderCallback, $this->resolveCallbackParameter());
457
        }
458
459
        return $this->defaultOrdering();
460
    }
461
462
    /**
463
     * Resolve callback parameter instance.
464
     *
465
     * @return mixed
466
     */
467
    abstract protected function resolveCallbackParameter();
468
469
    /**
470
     * Perform default query orderBy clause.
471
     */
472
    abstract protected function defaultOrdering();
473
474
    /**
475
     * Set auto filter off and run your own filter.
476
     * Overrides global search.
477
     *
478
     * @param callable $callback
479
     * @param bool     $globalSearch
480
     * @return $this
481
     */
482
    public function filter(callable $callback, $globalSearch = false)
483
    {
484
        $this->autoFilter      = $globalSearch;
485
        $this->isFilterApplied = true;
486
        $this->filterCallback  = $callback;
487
488
        return $this;
489
    }
490
491
    /**
492
     * Perform necessary filters.
493
     *
494
     * @return void
495
     */
496
    protected function filterRecords()
497
    {
498
        if ($this->autoFilter && $this->request->isSearchable()) {
499
            $this->filtering();
500
        }
501
502
        if (is_callable($this->filterCallback)) {
503
            call_user_func($this->filterCallback, $this->resolveCallbackParameter());
504
        }
505
506
        $this->columnSearch();
507
        $this->filteredRecords = $this->isFilterApplied ? $this->count() : $this->totalRecords;
508
    }
509
510
    /**
511
     * Perform global search.
512
     *
513
     * @return void
514
     */
515
    public function filtering()
516
    {
517
        $keyword = $this->request->keyword();
518
519
        if ($this->config->isMultiTerm()) {
520
            $this->smartGlobalSearch($keyword);
521
522
            return;
523
        }
524
525
        $this->globalSearch($keyword);
526
    }
527
528
    /**
529
     * Perform multi-term search by splitting keyword into
530
     * individual words and searches for each of them.
531
     *
532
     * @param string $keyword
533
     */
534
    protected function smartGlobalSearch($keyword)
535
    {
536
        $keywords = array_filter(explode(' ', $keyword));
537
538
        foreach ($keywords as $keyword) {
539
            $this->globalSearch($keyword);
540
        }
541
    }
542
543
    /**
544
     * Perform global search for the given keyword.
545
     *
546
     * @param string $keyword
547
     */
548
    abstract protected function globalSearch($keyword);
549
550
    /**
551
     * Apply pagination.
552
     *
553
     * @return void
554
     */
555
    protected function paginate()
556
    {
557
        if ($this->request->isPaginationable() && !$this->skipPaging) {
558
            $this->paging();
559
        }
560
    }
561
562
    /**
563
     * Transform output.
564
     *
565
     * @param mixed $output
566
     * @return array
567
     */
568
    protected function transform($output)
569
    {
570
        if (isset($this->transformer) && class_exists('Yajra\\Datatables\\Transformers\\FractalTransformer')) {
571
            return resolve('datatables.transformer')->transform($output, $this->transformer, $this->serializer ?? null);
0 ignored issues
show
Bug introduced by
The property transformer does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
Bug introduced by
The property serializer does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
572
        }
573
574
        return Helper::transform($output);
575
    }
576
577
    /**
578
     * Get processed data.
579
     *
580
     * @param bool|false $object
581
     * @return array
582
     */
583
    protected function getProcessedData($object = false)
584
    {
585
        $processor = new DataProcessor(
586
            $this->results(),
587
            $this->getColumnsDefinition(),
588
            $this->templates,
589
            $this->request->input('start')
590
        );
591
592
        return $processor->process($object);
593
    }
594
595
    /**
596
     * Render json response.
597
     *
598
     * @param array $data
599
     * @return \Illuminate\Http\JsonResponse
600
     */
601
    protected function render(array $data)
602
    {
603
        $output = array_merge([
604
            'draw'            => (int) $this->request->input('draw'),
605
            'recordsTotal'    => $this->totalRecords,
606
            'recordsFiltered' => $this->filteredRecords,
607
            'data'            => $data,
608
        ], $this->appends);
609
610
        if ($this->config->isDebugging()) {
611
            $output = $this->showDebugger($output);
612
        }
613
614
        return new JsonResponse(
615
            $output,
616
            200,
617
            $this->config->get('datatables.json.header', []),
618
            $this->config->get('datatables.json.options', 0)
619
        );
620
    }
621
622
    /**
623
     * Append debug parameters on output.
624
     *
625
     * @param  array $output
626
     * @return array
627
     */
628
    abstract protected function showDebugger(array $output);
629
630
    /**
631
     * Return an error json response.
632
     *
633
     * @param \Exception $exception
634
     * @return \Illuminate\Http\JsonResponse
635
     * @throws \Yajra\Datatables\Exception
636
     */
637
    protected function errorResponse(\Exception $exception)
638
    {
639
        $error = $this->config->get('datatables.error');
640
        if ($error === 'throw') {
641
            throw new Exception($exception->getMessage(), $code = 0, $exception);
642
        }
643
644
        $this->getLogger()->error($exception);
645
646
        return new JsonResponse([
647
            'draw'            => (int) $this->request->input('draw'),
648
            'recordsTotal'    => (int) $this->totalRecords,
649
            'recordsFiltered' => 0,
650
            'data'            => [],
651
            'error'           => $error ? __($error) : "Exception Message:\n\n" . $exception->getMessage(),
652
        ]);
653
    }
654
655
    /**
656
     * Get monolog/logger instance.
657
     *
658
     * @return \Illuminate\Contracts\Logging\Log
659
     */
660
    public function getLogger()
661
    {
662
        $this->logger = $this->logger ?: resolve(Log::class);
663
664
        return $this->logger;
665
    }
666
667
    /**
668
     * Set monolog/logger instance.
669
     *
670
     * @param \Illuminate\Contracts\Logging\Log $logger
671
     * @return $this
672
     */
673
    public function setLogger(Log $logger)
674
    {
675
        $this->logger = $logger;
676
677
        return $this;
678
    }
679
680
    /**
681
     * Setup search keyword.
682
     *
683
     * @param  string $value
684
     * @return string
685
     */
686
    protected function setupKeyword($value)
687
    {
688
        if ($this->config->isSmartSearch()) {
689
            $keyword = '%' . $value . '%';
690
            if ($this->config->isWildcard()) {
691
                $keyword = Helper::wildcardLikeString($value);
692
            }
693
            // remove escaping slash added on js script request
694
            $keyword = str_replace('\\', '%', $keyword);
695
696
            return $keyword;
697
        }
698
699
        return $value;
700
    }
701
702
    /**
703
     * Get column name to be use for filtering and sorting.
704
     *
705
     * @param integer $index
706
     * @param bool    $wantsAlias
707
     * @return string
708
     */
709
    protected function getColumnName($index, $wantsAlias = false)
710
    {
711
        $column = $this->request->columnName($index);
712
713
        // DataTables is using make(false)
714
        if (is_numeric($column)) {
715
            $column = $this->getColumnNameByIndex($index);
716
        }
717
718
        if (Str::contains(Str::upper($column), ' AS ')) {
719
            $column = Helper::extractColumnName($column, $wantsAlias);
720
        }
721
722
        return $column;
723
    }
724
725
    /**
726
     * Get column name by order column index.
727
     *
728
     * @param int $index
729
     * @return string
730
     */
731
    protected function getColumnNameByIndex($index)
732
    {
733
        $name = (isset($this->columns[$index]) && $this->columns[$index] != '*') ? $this->columns[$index] : $this->getPrimaryKeyName();
734
735
        return in_array($name, $this->extraColumns, true) ? $this->getPrimaryKeyName() : $name;
736
    }
737
738
    /**
739
     * If column name could not be resolved then use primary key.
740
     *
741
     * @return string
742
     */
743
    protected function getPrimaryKeyName()
744
    {
745
        return 'id';
746
    }
747
}
748