Completed
Push — master ( 2af423...733e70 )
by Arjay
02:34
created

BaseEngine::wildcardLikeString()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 9
nc 4
nop 2
dl 0
loc 15
rs 9.2
c 0
b 0
f 0
1
<?php
2
3
namespace Yajra\Datatables\Engines;
4
5
use Illuminate\Http\JsonResponse;
6
use Illuminate\Support\Facades\Config;
7
use Illuminate\Support\Str;
8
use League\Fractal\Resource\Collection;
9
use Yajra\Datatables\Contracts\DataTableEngineContract;
10
use Yajra\Datatables\Helper;
11
use Yajra\Datatables\Processors\DataProcessor;
12
13
/**
14
 * Class BaseEngine.
15
 *
16
 * @package Yajra\Datatables\Engines
17
 * @author  Arjay Angeles <[email protected]>
18
 */
19
abstract class BaseEngine implements DataTableEngineContract
20
{
21
    /**
22
     * Datatables Request object.
23
     *
24
     * @var \Yajra\Datatables\Request
25
     */
26
    public $request;
27
28
    /**
29
     * Database connection used.
30
     *
31
     * @var \Illuminate\Database\Connection
32
     */
33
    protected $connection;
34
35
    /**
36
     * Builder object.
37
     *
38
     * @var \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder
39
     */
40
    protected $query;
41
42
    /**
43
     * Query builder object.
44
     *
45
     * @var \Illuminate\Database\Query\Builder
46
     */
47
    protected $builder;
48
49
    /**
50
     * Array of result columns/fields.
51
     *
52
     * @var array
53
     */
54
    protected $columns = [];
55
56
    /**
57
     * DT columns definitions container (add/edit/remove/filter/order/escape).
58
     *
59
     * @var array
60
     */
61
    protected $columnDef = [
62
        'index'     => false,
63
        'append'    => [],
64
        'edit'      => [],
65
        'excess'    => ['rn', 'row_num'],
66
        'filter'    => [],
67
        'order'     => [],
68
        'escape'    => [],
69
        'blacklist' => ['password', 'remember_token'],
70
        'whitelist' => '*',
71
    ];
72
73
    /**
74
     * Query type.
75
     *
76
     * @var string
77
     */
78
    protected $query_type;
79
80
    /**
81
     * Extra/Added columns.
82
     *
83
     * @var array
84
     */
85
    protected $extraColumns = [];
86
87
    /**
88
     * Total records.
89
     *
90
     * @var int
91
     */
92
    protected $totalRecords = 0;
93
94
    /**
95
     * Total filtered records.
96
     *
97
     * @var int
98
     */
99
    protected $filteredRecords = 0;
100
101
    /**
102
     * Auto-filter flag.
103
     *
104
     * @var bool
105
     */
106
    protected $autoFilter = true;
107
108
    /**
109
     * Callback to override global search.
110
     *
111
     * @var \Closure
112
     */
113
    protected $filterCallback;
114
115
    /**
116
     * Parameters to passed on filterCallback.
117
     *
118
     * @var mixed
119
     */
120
    protected $filterCallbackParameters;
121
122
    /**
123
     * DT row templates container.
124
     *
125
     * @var array
126
     */
127
    protected $templates = [
128
        'DT_RowId'    => '',
129
        'DT_RowClass' => '',
130
        'DT_RowData'  => [],
131
        'DT_RowAttr'  => [],
132
    ];
133
134
    /**
135
     * Output transformer.
136
     *
137
     * @var \League\Fractal\TransformerAbstract
138
     */
139
    protected $transformer = null;
140
141
    /**
142
     * Database prefix
143
     *
144
     * @var string
145
     */
146
    protected $prefix;
147
148
    /**
149
     * Database driver used.
150
     *
151
     * @var string
152
     */
153
    protected $database;
154
155
    /**
156
     * [internal] Track if any filter was applied for at least one column
157
     *
158
     * @var boolean
159
     */
160
    protected $isFilterApplied = false;
161
162
    /**
163
     * Fractal serializer class.
164
     *
165
     * @var string|null
166
     */
167
    protected $serializer = null;
168
169
    /**
170
     * Custom ordering callback.
171
     *
172
     * @var \Closure
173
     */
174
    protected $orderCallback;
175
176
    /**
177
     * Array of data to append on json response.
178
     *
179
     * @var array
180
     */
181
    private $appends = [];
182
183
    /**
184
     * Setup search keyword.
185
     *
186
     * @param  string $value
187
     * @return string
188
     */
189
    public function setupKeyword($value)
190
    {
191
        if ($this->isSmartSearch()) {
192
            $keyword = '%' . $value . '%';
193
            if ($this->isWildcard()) {
194
                $keyword = $this->wildcardLikeString($value);
195
            }
196
            // remove escaping slash added on js script request
197
            $keyword = str_replace('\\', '%', $keyword);
198
199
            return $keyword;
200
        }
201
202
        return $value;
203
    }
204
205
    /**
206
     * Check if DataTables uses smart search.
207
     *
208
     * @return bool
209
     */
210
    protected function isSmartSearch()
211
    {
212
        return Config::get('datatables.search.smart', true);
213
    }
214
215
    /**
216
     * Get config use wild card status.
217
     *
218
     * @return bool
219
     */
220
    public function isWildcard()
221
    {
222
        return Config::get('datatables.search.use_wildcards', false);
223
    }
224
225
    /**
226
     * Adds % wildcards to the given string.
227
     *
228
     * @param string $str
229
     * @param bool $lowercase
230
     * @return string
231
     */
232
    public function wildcardLikeString($str, $lowercase = true)
233
    {
234
        $wild   = '%';
235
        $length = Str::length($str);
236
        if ($length) {
237
            for ($i = 0; $i < $length; $i++) {
238
                $wild .= $str[$i] . '%';
239
            }
240
        }
241
        if ($lowercase) {
242
            $wild = Str::lower($wild);
243
        }
244
245
        return $wild;
246
    }
247
248
    /**
249
     * Will prefix column if needed.
250
     *
251
     * @param string $column
252
     * @return string
253
     */
254
    public function prefixColumn($column)
255
    {
256
        $table_names = $this->tableNames();
257
        if (count(
258
            array_filter($table_names, function ($value) use (&$column) {
259
                return strpos($column, $value . '.') === 0;
260
            })
261
        )) {
262
            // the column starts with one of the table names
263
            $column = $this->prefix . $column;
264
        }
265
266
        return $column;
267
    }
268
269
    /**
270
     * Will look through the query and all it's joins to determine the table names.
271
     *
272
     * @return array
273
     */
274
    public function tableNames()
275
    {
276
        $names          = [];
277
        $query          = $this->getQueryBuilder();
278
        $names[]        = $query->from;
279
        $joins          = $query->joins ?: [];
280
        $databasePrefix = $this->prefix;
281
        foreach ($joins as $join) {
282
            $table   = preg_split('/ as /i', $join->table);
283
            $names[] = $table[0];
284
            if (isset($table[1]) && ! empty($databasePrefix) && strpos($table[1], $databasePrefix) == 0) {
285
                $names[] = preg_replace('/^' . $databasePrefix . '/', '', $table[1]);
286
            }
287
        }
288
289
        return $names;
290
    }
291
292
    /**
293
     * Get Query Builder object.
294
     *
295
     * @param mixed $instance
296
     * @return mixed
297
     */
298
    public function getQueryBuilder($instance = null)
299
    {
300
        if (! $instance) {
301
            $instance = $this->query;
302
        }
303
304
        if ($this->isQueryBuilder()) {
305
            return $instance;
306
        }
307
308
        return $instance->getQuery();
309
    }
310
311
    /**
312
     * Check query type is a builder.
313
     *
314
     * @return bool
315
     */
316
    public function isQueryBuilder()
317
    {
318
        return $this->query_type == 'builder';
319
    }
320
321
    /**
322
     * Add column in collection.
323
     *
324
     * @param string $name
325
     * @param string|callable $content
326
     * @param bool|int $order
327
     * @return $this
328
     */
329
    public function addColumn($name, $content, $order = false)
330
    {
331
        $this->extraColumns[] = $name;
332
333
        $this->columnDef['append'][] = ['name' => $name, 'content' => $content, 'order' => $order];
334
335
        return $this;
336
    }
337
338
    /**
339
     * Add DT row index column on response.
340
     *
341
     * @return $this
342
     */
343
    public function addIndexColumn()
344
    {
345
        $this->columnDef['index'] = true;
346
347
        return $this;
348
    }
349
350
    /**
351
     * Edit column's content.
352
     *
353
     * @param string $name
354
     * @param string|callable $content
355
     * @return $this
356
     */
357
    public function editColumn($name, $content)
358
    {
359
        $this->columnDef['edit'][] = ['name' => $name, 'content' => $content];
360
361
        return $this;
362
    }
363
364
    /**
365
     * Remove column from collection.
366
     *
367
     * @return $this
368
     */
369
    public function removeColumn()
370
    {
371
        $names                     = func_get_args();
372
        $this->columnDef['excess'] = array_merge($this->columnDef['excess'], $names);
373
374
        return $this;
375
    }
376
377
    /**
378
     * Declare columns to escape values.
379
     *
380
     * @param string|array $columns
381
     * @return $this
382
     */
383
    public function escapeColumns($columns = '*')
384
    {
385
        $this->columnDef['escape'] = $columns;
386
387
        return $this;
388
    }
389
390
    /**
391
     * Allows previous API calls where the methods were snake_case.
392
     * Will convert a camelCase API call to a snake_case call.
393
     * Allow query builder method to be used by the engine.
394
     *
395
     * @param  string $name
396
     * @param  array $arguments
397
     * @return mixed
398
     */
399
    public function __call($name, $arguments)
400
    {
401
        $name = Str::camel(Str::lower($name));
402
        if (method_exists($this, $name)) {
403
            return call_user_func_array([$this, $name], $arguments);
404
        } elseif (method_exists($this->getQueryBuilder(), $name)) {
405
            call_user_func_array([$this->getQueryBuilder(), $name], $arguments);
406
        } else {
407
            trigger_error('Call to undefined method ' . __CLASS__ . '::' . $name . '()', E_USER_ERROR);
408
        }
409
410
        return $this;
411
    }
412
413
    /**
414
     * Sets DT_RowClass template.
415
     * result: <tr class="output_from_your_template">.
416
     *
417
     * @param string|callable $content
418
     * @return $this
419
     */
420
    public function setRowClass($content)
421
    {
422
        $this->templates['DT_RowClass'] = $content;
423
424
        return $this;
425
    }
426
427
    /**
428
     * Sets DT_RowId template.
429
     * result: <tr id="output_from_your_template">.
430
     *
431
     * @param string|callable $content
432
     * @return $this
433
     */
434
    public function setRowId($content)
435
    {
436
        $this->templates['DT_RowId'] = $content;
437
438
        return $this;
439
    }
440
441
    /**
442
     * Set DT_RowData templates.
443
     *
444
     * @param array $data
445
     * @return $this
446
     */
447
    public function setRowData(array $data)
448
    {
449
        $this->templates['DT_RowData'] = $data;
450
451
        return $this;
452
    }
453
454
    /**
455
     * Add DT_RowData template.
456
     *
457
     * @param string $key
458
     * @param string|callable $value
459
     * @return $this
460
     */
461
    public function addRowData($key, $value)
462
    {
463
        $this->templates['DT_RowData'][$key] = $value;
464
465
        return $this;
466
    }
467
468
    /**
469
     * Set DT_RowAttr templates.
470
     * result: <tr attr1="attr1" attr2="attr2">.
471
     *
472
     * @param array $data
473
     * @return $this
474
     */
475
    public function setRowAttr(array $data)
476
    {
477
        $this->templates['DT_RowAttr'] = $data;
478
479
        return $this;
480
    }
481
482
    /**
483
     * Add DT_RowAttr template.
484
     *
485
     * @param string $key
486
     * @param string|callable $value
487
     * @return $this
488
     */
489
    public function addRowAttr($key, $value)
490
    {
491
        $this->templates['DT_RowAttr'][$key] = $value;
492
493
        return $this;
494
    }
495
496
    /**
497
     * Override default column filter search.
498
     *
499
     * @param string $column
500
     * @param string|Closure $method
501
     * @return $this
502
     * @internal param $mixed ...,... All the individual parameters required for specified $method
503
     * @internal string $1 Special variable that returns the requested search keyword.
504
     */
505
    public function filterColumn($column, $method)
506
    {
507
        $params                             = func_get_args();
508
        $this->columnDef['filter'][$column] = ['method' => $method, 'parameters' => array_splice($params, 2)];
509
510
        return $this;
511
    }
512
513
    /**
514
     * Override default column ordering.
515
     *
516
     * @param string $column
517
     * @param string $sql
518
     * @param array $bindings
519
     * @return $this
520
     * @internal string $1 Special variable that returns the requested order direction of the column.
521
     */
522
    public function orderColumn($column, $sql, $bindings = [])
523
    {
524
        $this->columnDef['order'][$column] = ['method' => 'orderByRaw', 'parameters' => [$sql, $bindings]];
525
526
        return $this;
527
    }
528
529
    /**
530
     * Set data output transformer.
531
     *
532
     * @param \League\Fractal\TransformerAbstract $transformer
533
     * @return $this
534
     */
535
    public function setTransformer($transformer)
536
    {
537
        $this->transformer = $transformer;
538
539
        return $this;
540
    }
541
542
    /**
543
     * Set fractal serializer class.
544
     *
545
     * @param string $serializer
546
     * @return $this
547
     */
548
    public function setSerializer($serializer)
549
    {
550
        $this->serializer = $serializer;
551
552
        return $this;
553
    }
554
555
    /**
556
     * Organizes works.
557
     *
558
     * @param bool $mDataSupport
559
     * @param bool $orderFirst
560
     * @return \Illuminate\Http\JsonResponse
561
     */
562
    public function make($mDataSupport = false, $orderFirst = false)
563
    {
564
        $this->totalRecords = $this->totalCount();
565
566
        if ($this->totalRecords) {
567
            $this->orderRecords(! $orderFirst);
568
            $this->filterRecords();
569
            $this->orderRecords($orderFirst);
570
            $this->paginate();
571
        }
572
573
        return $this->render($mDataSupport);
574
    }
575
576
    /**
577
     * Sort records.
578
     *
579
     * @param  boolean $skip
580
     * @return void
581
     */
582
    public function orderRecords($skip)
583
    {
584
        if (! $skip) {
585
            $this->ordering();
586
        }
587
    }
588
589
    /**
590
     * Perform necessary filters.
591
     *
592
     * @return void
593
     */
594
    public function filterRecords()
595
    {
596
        if ($this->autoFilter && $this->request->isSearchable()) {
597
            $this->filtering();
598
        }
599
600
        if (is_callable($this->filterCallback)) {
601
            call_user_func($this->filterCallback, $this->filterCallbackParameters);
602
        }
603
604
        $this->columnSearch();
605
        $this->filteredRecords = $this->isFilterApplied ? $this->count() : $this->totalRecords;
606
    }
607
608
    /**
609
     * Apply pagination.
610
     *
611
     * @return void
612
     */
613
    public function paginate()
614
    {
615
        if ($this->request->isPaginationable()) {
616
            $this->paging();
617
        }
618
    }
619
620
    /**
621
     * Render json response.
622
     *
623
     * @param bool $object
624
     * @return \Illuminate\Http\JsonResponse
625
     */
626
    public function render($object = false)
627
    {
628
        $output = array_merge([
629
            'draw'            => (int) $this->request['draw'],
630
            'recordsTotal'    => $this->totalRecords,
631
            'recordsFiltered' => $this->filteredRecords,
632
        ], $this->appends);
633
634
        if (isset($this->transformer)) {
635
            $fractal = app('datatables.fractal');
636
637
            if ($this->serializer) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->serializer of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
638
                $fractal->setSerializer(new $this->serializer);
639
            }
640
641
            //Get transformer reflection
642
            //Firs method parameter should be data/object to transform
643
            $reflection = new \ReflectionMethod($this->transformer, 'transform');
644
            $parameter  = $reflection->getParameters()[0];
645
646
            //If parameter is class assuming it requires object
647
            //Else just pass array by default
648
            if ($parameter->getClass()) {
649
                $resource = new Collection($this->results(), $this->createTransformer());
650
            } else {
651
                $resource = new Collection(
652
                    $this->getProcessedData($object),
653
                    $this->createTransformer()
654
                );
655
            }
656
657
            $collection     = $fractal->createData($resource)->toArray();
658
            $output['data'] = $collection['data'];
659
        } else {
660
            $output['data'] = Helper::transform($this->getProcessedData($object));
661
        }
662
663
        if ($this->isDebugging()) {
664
            $output = $this->showDebugger($output);
665
        }
666
667
        return new JsonResponse($output);
668
    }
669
670
    /**
671
     * Get or create transformer instance.
672
     *
673
     * @return \League\Fractal\TransformerAbstract
674
     */
675
    protected function createTransformer()
676
    {
677
        if ($this->transformer instanceof \League\Fractal\TransformerAbstract) {
678
            return $this->transformer;
679
        }
680
681
        return new $this->transformer();
682
    }
683
684
    /**
685
     * Get processed data
686
     *
687
     * @param bool|false $object
688
     * @return array
689
     */
690
    private function getProcessedData($object = false)
691
    {
692
        $processor = new DataProcessor(
693
            $this->results(),
694
            $this->columnDef,
695
            $this->templates,
696
            $this->request['start']
697
        );
698
699
        return $processor->process($object);
700
    }
701
702
    /**
703
     * Check if app is in debug mode.
704
     *
705
     * @return bool
706
     */
707
    public function isDebugging()
708
    {
709
        return Config::get('app.debug', false);
710
    }
711
712
    /**
713
     * Append debug parameters on output.
714
     *
715
     * @param  array $output
716
     * @return array
717
     */
718
    public function showDebugger(array $output)
719
    {
720
        $output['queries'] = $this->connection->getQueryLog();
721
        $output['input']   = $this->request->all();
722
723
        return $output;
724
    }
725
726
    /**
727
     * Update flags to disable global search
728
     *
729
     * @param  \Closure $callback
730
     * @param  mixed $parameters
731
     * @param  bool $autoFilter
732
     */
733
    public function overrideGlobalSearch(\Closure $callback, $parameters, $autoFilter = false)
734
    {
735
        $this->autoFilter               = $autoFilter;
736
        $this->isFilterApplied          = true;
737
        $this->filterCallback           = $callback;
738
        $this->filterCallbackParameters = $parameters;
739
    }
740
741
    /**
742
     * Get config is case insensitive status.
743
     *
744
     * @return bool
745
     */
746
    public function isCaseInsensitive()
747
    {
748
        return Config::get('datatables.search.case_insensitive', false);
749
    }
750
751
    /**
752
     * Append data on json response.
753
     *
754
     * @param mixed $key
755
     * @param mixed $value
756
     * @return $this
757
     */
758
    public function with($key, $value = '')
759
    {
760
        if (is_array($key)) {
761
            $this->appends = $key;
762
        } elseif (is_callable($value)) {
763
            $this->appends[$key] = value($value);
764
        } else {
765
            $this->appends[$key] = value($value);
766
        }
767
768
        return $this;
769
    }
770
771
    /**
772
     * Override default ordering method with a closure callback.
773
     *
774
     * @param \Closure $closure
775
     * @return $this
776
     */
777
    public function order(\Closure $closure)
778
    {
779
        $this->orderCallback = $closure;
780
781
        return $this;
782
    }
783
784
    /**
785
     * Update list of columns that is not allowed for search/sort.
786
     *
787
     * @param  array $blacklist
788
     * @return $this
789
     */
790
    public function blacklist(array $blacklist)
791
    {
792
        $this->columnDef['blacklist'] = $blacklist;
793
794
        return $this;
795
    }
796
797
    /**
798
     * Update list of columns that is not allowed for search/sort.
799
     *
800
     * @param  string|array $whitelist
801
     * @return $this
802
     */
803
    public function whitelist($whitelist = '*')
804
    {
805
        $this->columnDef['whitelist'] = $whitelist;
806
807
        return $this;
808
    }
809
810
    /**
811
     * Set smart search config at runtime.
812
     *
813
     * @param bool $bool
814
     * @return $this
815
     */
816
    public function smart($bool = true)
817
    {
818
        Config::set('datatables.search.smart', $bool);
819
820
        return $this;
821
    }
822
823
    /**
824
     * Check if column is blacklisted.
825
     *
826
     * @param string $column
827
     * @return bool
828
     */
829
    protected function isBlacklisted($column)
830
    {
831
        if (in_array($column, $this->columnDef['blacklist'])) {
832
            return true;
833
        }
834
835
        if ($this->columnDef['whitelist'] === '*' || in_array($column, $this->columnDef['whitelist'])) {
836
            return false;
837
        }
838
839
        return true;
840
    }
841
842
    /**
843
     * Get column name to be use for filtering and sorting.
844
     *
845
     * @param integer $index
846
     * @param bool $wantsAlias
847
     * @return string
848
     */
849
    protected function getColumnName($index, $wantsAlias = false)
850
    {
851
        $column = $this->request->columnName($index);
852
853
        // DataTables is using make(false)
854
        if (is_numeric($column)) {
855
            $column = $this->getColumnNameByIndex($index);
856
        }
857
858
        if (Str::contains(Str::upper($column), ' AS ')) {
859
            $column = $this->extractColumnName($column, $wantsAlias);
860
        }
861
862
        return $column;
863
    }
864
865
    /**
866
     * Get column name by order column index.
867
     *
868
     * @param int $index
869
     * @return mixed
870
     */
871
    protected function getColumnNameByIndex($index)
872
    {
873
        $name = isset($this->columns[$index]) && $this->columns[$index] <> '*' ? $this->columns[$index] : $this->getPrimaryKeyName();
874
875
        return in_array($name, $this->extraColumns, true) ? $this->getPrimaryKeyName() : $name;
876
    }
877
878
    /**
879
     * If column name could not be resolved then use primary key.
880
     *
881
     * @return string
882
     */
883
    protected function getPrimaryKeyName()
884
    {
885
        if ($this->isEloquent()) {
886
            return $this->query->getModel()->getKeyName();
0 ignored issues
show
Bug introduced by
The method getModel does only exist in Illuminate\Database\Eloquent\Builder, but not in Illuminate\Database\Query\Builder.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
887
        }
888
889
        return 'id';
890
    }
891
892
    /**
893
     * Check if the engine used was eloquent.
894
     *
895
     * @return bool
896
     */
897
    protected function isEloquent()
898
    {
899
        return $this->query_type === 'eloquent';
900
    }
901
902
    /**
903
     * Get column name from string.
904
     *
905
     * @param string $str
906
     * @param bool $wantsAlias
907
     * @return string
908
     */
909
    protected function extractColumnName($str, $wantsAlias)
910
    {
911
        $matches = explode(' as ', Str::lower($str));
912
913
        if (! empty($matches)) {
914
            if ($wantsAlias) {
915
                return array_pop($matches);
916
            } else {
917
                return array_shift($matches);
918
            }
919
        } elseif (strpos($str, '.')) {
920
            $array = explode('.', $str);
921
922
            return array_pop($array);
923
        }
924
925
        return $str;
926
    }
927
928
    /**
929
     * Check if the current sql language is based on oracle syntax.
930
     *
931
     * @return bool
932
     */
933
    protected function isOracleSql()
934
    {
935
        return in_array($this->database, ['oracle', 'oci8']);
936
    }
937
938
    /**
939
     * Set total records manually.
940
     *
941
     * @param int $total
942
     * @return $this
943
     */
944
    public function setTotalRecords($total)
945
    {
946
        $this->totalRecords = $total;
947
948
        return $this;
949
    }
950
}
951