1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Yajra\DataTables; |
4
|
|
|
|
5
|
|
|
use Illuminate\Support\Str; |
6
|
|
|
use Illuminate\Database\Query\Builder; |
7
|
|
|
use Yajra\DataTables\Utilities\Helper; |
8
|
|
|
use Illuminate\Database\Query\Expression; |
9
|
|
|
use Illuminate\Database\Eloquent\Builder as EloquentBuilder; |
10
|
|
|
|
11
|
|
|
class QueryDataTable extends DataTableAbstract |
12
|
|
|
{ |
13
|
|
|
/** |
14
|
|
|
* Builder object. |
15
|
|
|
* |
16
|
|
|
* @var \Illuminate\Database\Query\Builder |
17
|
|
|
*/ |
18
|
|
|
protected $query; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* Database connection used. |
22
|
|
|
* |
23
|
|
|
* @var \Illuminate\Database\Connection |
24
|
|
|
*/ |
25
|
|
|
protected $connection; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* Flag for ordering NULLS LAST option. |
29
|
|
|
* |
30
|
|
|
* @var bool |
31
|
|
|
*/ |
32
|
|
|
protected $nullsLast = false; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* Flag to check if query preparation was already done. |
36
|
|
|
* |
37
|
|
|
* @var bool |
38
|
|
|
*/ |
39
|
|
|
protected $prepared = false; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* Query callback for custom pagination using limit without offset. |
43
|
|
|
* |
44
|
|
|
* @var callable |
45
|
|
|
*/ |
46
|
|
|
protected $limitCallback; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* Flag to skip total records count query. |
50
|
|
|
* |
51
|
|
|
* @var bool |
52
|
|
|
*/ |
53
|
|
|
protected $skipTotalRecords = false; |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* Flag to keep the select bindings. |
57
|
|
|
* |
58
|
|
|
* @var bool |
59
|
|
|
*/ |
60
|
|
|
protected $keepSelectBindings = false; |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* Can the DataTable engine be created with these parameters. |
64
|
|
|
* |
65
|
|
|
* @param mixed $source |
66
|
|
|
* @return bool |
67
|
|
|
*/ |
68
|
|
|
public static function canCreate($source) |
69
|
|
|
{ |
70
|
|
|
return $source instanceof Builder; |
|
|
|
|
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* @param \Illuminate\Database\Query\Builder $builder |
75
|
|
|
*/ |
76
|
|
|
public function __construct(Builder $builder) |
77
|
|
|
{ |
78
|
|
|
$this->query = $builder; |
79
|
|
|
$this->request = app('datatables.request'); |
80
|
|
|
$this->config = app('datatables.config'); |
81
|
|
|
$this->columns = $builder->columns; |
82
|
|
|
$this->connection = $builder->getConnection(); |
83
|
|
|
if ($this->config->isDebugging()) { |
84
|
|
|
$this->connection->enableQueryLog(); |
85
|
|
|
} |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
/** |
89
|
|
|
* Organizes works. |
90
|
|
|
* |
91
|
|
|
* @param bool $mDataSupport |
92
|
|
|
* @return \Illuminate\Http\JsonResponse |
93
|
|
|
* @throws \Exception |
94
|
|
|
*/ |
95
|
|
|
public function make($mDataSupport = true) |
96
|
|
|
{ |
97
|
|
|
try { |
98
|
|
|
$this->prepareQuery(); |
99
|
|
|
|
100
|
|
|
$results = $this->results(); |
101
|
|
|
$processed = $this->processResults($results, $mDataSupport); |
102
|
|
|
$data = $this->transform($results, $processed); |
103
|
|
|
|
104
|
|
|
return $this->render($data); |
105
|
|
|
} catch (\Exception $exception) { |
106
|
|
|
return $this->errorResponse($exception); |
107
|
|
|
} |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* Prepare query by executing count, filter, order and paginate. |
112
|
|
|
*/ |
113
|
|
|
protected function prepareQuery() |
114
|
|
|
{ |
115
|
|
|
if (! $this->prepared) { |
116
|
|
|
$this->totalRecords = $this->totalCount(); |
|
|
|
|
117
|
|
|
|
118
|
|
|
if ($this->totalRecords) { |
119
|
|
|
$this->filterRecords(); |
120
|
|
|
$this->ordering(); |
121
|
|
|
$this->paginate(); |
122
|
|
|
} |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
$this->prepared = true; |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
/** |
129
|
|
|
* Skip total records and set the recordsTotal equals to recordsFiltered. |
130
|
|
|
* This will improve the performance by skipping the total count query. |
131
|
|
|
* |
132
|
|
|
* @return $this |
133
|
|
|
*/ |
134
|
|
|
public function skipTotalRecords() |
135
|
|
|
{ |
136
|
|
|
$this->skipTotalRecords = true; |
137
|
|
|
|
138
|
|
|
return $this; |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
/** |
142
|
|
|
* Keep the select bindings. |
143
|
|
|
* |
144
|
|
|
* @return $this |
145
|
|
|
*/ |
146
|
|
|
public function keepSelectBindings() |
147
|
|
|
{ |
148
|
|
|
$this->keepSelectBindings = true; |
149
|
|
|
|
150
|
|
|
return $this; |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
/** |
154
|
|
|
* Count total items. |
155
|
|
|
* |
156
|
|
|
* @return int |
157
|
|
|
*/ |
158
|
|
|
public function totalCount() |
159
|
|
|
{ |
160
|
|
|
if ($this->skipTotalRecords) { |
161
|
|
|
return true; |
|
|
|
|
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
return $this->totalRecords ? $this->totalRecords : $this->count(); |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
/** |
168
|
|
|
* Count filtered items. |
169
|
|
|
* |
170
|
|
|
* @return int |
171
|
|
|
*/ |
172
|
|
|
protected function filteredCount() |
173
|
|
|
{ |
174
|
|
|
$this->filteredRecords = $this->filteredRecords ?: $this->count(); |
175
|
|
|
if ($this->skipTotalRecords) { |
176
|
|
|
$this->totalRecords = $this->filteredRecords; |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
return $this->filteredRecords; |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
/** |
183
|
|
|
* Counts current query. |
184
|
|
|
* |
185
|
|
|
* @return int |
186
|
|
|
*/ |
187
|
|
|
public function count() |
188
|
|
|
{ |
189
|
|
|
$builder = $this->prepareCountQuery(); |
190
|
|
|
$table = $this->connection->raw('(' . $builder->toSql() . ') count_row_table'); |
191
|
|
|
|
192
|
|
|
return $this->connection->table($table) |
193
|
|
|
->setBindings($builder->getBindings()) |
194
|
|
|
->count(); |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
/** |
198
|
|
|
* Prepare count query builder. |
199
|
|
|
* |
200
|
|
|
* @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder |
201
|
|
|
*/ |
202
|
|
|
protected function prepareCountQuery() |
203
|
|
|
{ |
204
|
|
|
$builder = clone $this->query; |
205
|
|
|
|
206
|
|
|
if (! $this->isComplexQuery($builder)) { |
207
|
|
|
$row_count = $this->wrap('row_count'); |
208
|
|
|
$builder->select($this->connection->raw("'1' as {$row_count}")); |
209
|
|
|
if (! $this->keepSelectBindings) { |
210
|
|
|
$builder->setBindings([], 'select'); |
211
|
|
|
} |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
return $builder; |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
/** |
218
|
|
|
* Check if builder query uses complex sql. |
219
|
|
|
* |
220
|
|
|
* @param \Illuminate\Database\Query\Builder $builder |
221
|
|
|
* @return bool |
222
|
|
|
*/ |
223
|
|
|
protected function isComplexQuery($builder) |
224
|
|
|
{ |
225
|
|
|
return Str::contains(Str::lower($builder->toSql()), ['union', 'having', 'distinct', 'order by', 'group by']); |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
/** |
229
|
|
|
* Wrap column with DB grammar. |
230
|
|
|
* |
231
|
|
|
* @param string $column |
232
|
|
|
* @return string |
233
|
|
|
*/ |
234
|
|
|
protected function wrap($column) |
235
|
|
|
{ |
236
|
|
|
return $this->connection->getQueryGrammar()->wrap($column); |
237
|
|
|
} |
238
|
|
|
|
239
|
|
|
/** |
240
|
|
|
* Get paginated results. |
241
|
|
|
* |
242
|
|
|
* @return \Illuminate\Support\Collection |
243
|
|
|
*/ |
244
|
|
|
public function results() |
245
|
|
|
{ |
246
|
|
|
return $this->query->get(); |
247
|
|
|
} |
248
|
|
|
|
249
|
|
|
/** |
250
|
|
|
* Get filtered, ordered and paginated query. |
251
|
|
|
* |
252
|
|
|
* @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder |
253
|
|
|
*/ |
254
|
|
|
public function getFilteredQuery() |
255
|
|
|
{ |
256
|
|
|
$this->prepareQuery(); |
257
|
|
|
|
258
|
|
|
return $this->getQuery(); |
259
|
|
|
} |
260
|
|
|
|
261
|
|
|
/** |
262
|
|
|
* Get query builder instance. |
263
|
|
|
* |
264
|
|
|
* @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder |
265
|
|
|
*/ |
266
|
|
|
public function getQuery() |
267
|
|
|
{ |
268
|
|
|
return $this->query; |
269
|
|
|
} |
270
|
|
|
|
271
|
|
|
/** |
272
|
|
|
* Perform column search. |
273
|
|
|
* |
274
|
|
|
* @return void |
275
|
|
|
*/ |
276
|
|
|
public function columnSearch() |
277
|
|
|
{ |
278
|
|
|
$columns = $this->request->columns(); |
279
|
|
|
|
280
|
|
|
foreach ($columns as $index => $column) { |
281
|
|
|
$column = $this->getColumnName($index); |
282
|
|
|
|
283
|
|
|
if (! $this->request->isColumnSearchable($index) || $this->isBlacklisted($column) && ! $this->hasFilterColumn($column)) { |
284
|
|
|
continue; |
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
if ($this->hasFilterColumn($column)) { |
288
|
|
|
$keyword = $this->getColumnSearchKeyword($index, $raw = true); |
289
|
|
|
$this->applyFilterColumn($this->getBaseQueryBuilder(), $column, $keyword); |
290
|
|
|
} else { |
291
|
|
|
$column = $this->resolveRelationColumn($column); |
292
|
|
|
$keyword = $this->getColumnSearchKeyword($index); |
293
|
|
|
$this->compileColumnSearch($index, $column, $keyword); |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
$this->isFilterApplied = true; |
297
|
|
|
} |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
/** |
301
|
|
|
* Check if column has custom filter handler. |
302
|
|
|
* |
303
|
|
|
* @param string $columnName |
304
|
|
|
* @return bool |
305
|
|
|
*/ |
306
|
|
|
public function hasFilterColumn($columnName) |
307
|
|
|
{ |
308
|
|
|
return isset($this->columnDef['filter'][$columnName]); |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
/** |
312
|
|
|
* Get column keyword to use for search. |
313
|
|
|
* |
314
|
|
|
* @param int $i |
315
|
|
|
* @param bool $raw |
316
|
|
|
* @return string |
317
|
|
|
*/ |
318
|
|
|
protected function getColumnSearchKeyword($i, $raw = false) |
319
|
|
|
{ |
320
|
|
|
$keyword = $this->request->columnKeyword($i); |
321
|
|
|
if ($raw || $this->request->isRegex($i)) { |
322
|
|
|
return $keyword; |
323
|
|
|
} |
324
|
|
|
|
325
|
|
|
return $this->setupKeyword($keyword); |
326
|
|
|
} |
327
|
|
|
|
328
|
|
|
/** |
329
|
|
|
* Apply filterColumn api search. |
330
|
|
|
* |
331
|
|
|
* @param mixed $query |
332
|
|
|
* @param string $columnName |
333
|
|
|
* @param string $keyword |
334
|
|
|
* @param string $boolean |
335
|
|
|
*/ |
336
|
|
|
protected function applyFilterColumn($query, $columnName, $keyword, $boolean = 'and') |
337
|
|
|
{ |
338
|
|
|
$query = $this->getBaseQueryBuilder($query); |
339
|
|
|
$callback = $this->columnDef['filter'][$columnName]['method']; |
340
|
|
|
|
341
|
|
|
if ($this->query instanceof EloquentBuilder) { |
|
|
|
|
342
|
|
|
$builder = $this->query->newModelInstance()->newQuery(); |
343
|
|
|
} else { |
344
|
|
|
$builder = $this->query->newQuery(); |
345
|
|
|
} |
346
|
|
|
|
347
|
|
|
$callback($builder, $keyword); |
348
|
|
|
|
349
|
|
|
$query->addNestedWhereQuery($this->getBaseQueryBuilder($builder), $boolean); |
350
|
|
|
} |
351
|
|
|
|
352
|
|
|
/** |
353
|
|
|
* Get the base query builder instance. |
354
|
|
|
* |
355
|
|
|
* @param mixed $instance |
356
|
|
|
* @return \Illuminate\Database\Query\Builder |
357
|
|
|
*/ |
358
|
|
|
protected function getBaseQueryBuilder($instance = null) |
359
|
|
|
{ |
360
|
|
|
if (! $instance) { |
361
|
|
|
$instance = $this->query; |
362
|
|
|
} |
363
|
|
|
|
364
|
|
|
if ($instance instanceof EloquentBuilder) { |
|
|
|
|
365
|
|
|
return $instance->getQuery(); |
366
|
|
|
} |
367
|
|
|
|
368
|
|
|
return $instance; |
369
|
|
|
} |
370
|
|
|
|
371
|
|
|
/** |
372
|
|
|
* Resolve the proper column name be used. |
373
|
|
|
* |
374
|
|
|
* @param string $column |
375
|
|
|
* @return string |
376
|
|
|
*/ |
377
|
|
|
protected function resolveRelationColumn($column) |
378
|
|
|
{ |
379
|
|
|
return $column; |
380
|
|
|
} |
381
|
|
|
|
382
|
|
|
/** |
383
|
|
|
* Compile queries for column search. |
384
|
|
|
* |
385
|
|
|
* @param int $i |
386
|
|
|
* @param string $column |
387
|
|
|
* @param string $keyword |
388
|
|
|
*/ |
389
|
|
|
protected function compileColumnSearch($i, $column, $keyword) |
390
|
|
|
{ |
391
|
|
|
if ($this->request->isRegex($i)) { |
392
|
|
|
$column = strstr($column, '(') ? $this->connection->raw($column) : $column; |
393
|
|
|
$this->regexColumnSearch($column, $keyword); |
394
|
|
|
} else { |
395
|
|
|
$this->compileQuerySearch($this->query, $column, $keyword, ''); |
396
|
|
|
} |
397
|
|
|
} |
398
|
|
|
|
399
|
|
|
/** |
400
|
|
|
* Compile regex query column search. |
401
|
|
|
* |
402
|
|
|
* @param mixed $column |
403
|
|
|
* @param string $keyword |
404
|
|
|
*/ |
405
|
|
|
protected function regexColumnSearch($column, $keyword) |
406
|
|
|
{ |
407
|
|
|
switch ($this->connection->getDriverName()) { |
408
|
|
|
case 'oracle': |
409
|
|
|
$sql = ! $this->config->isCaseInsensitive() |
410
|
|
|
? 'REGEXP_LIKE( ' . $column . ' , ? )' |
411
|
|
|
: 'REGEXP_LIKE( LOWER(' . $column . ') , ?, \'i\' )'; |
412
|
|
|
break; |
413
|
|
|
|
414
|
|
|
case 'pgsql': |
415
|
|
|
$column = $this->castColumn($column); |
416
|
|
|
$sql = ! $this->config->isCaseInsensitive() ? $column . ' ~ ?' : $column . ' ~* ? '; |
417
|
|
|
break; |
418
|
|
|
|
419
|
|
|
default: |
420
|
|
|
$sql = ! $this->config->isCaseInsensitive() |
421
|
|
|
? $column . ' REGEXP ?' |
422
|
|
|
: 'LOWER(' . $column . ') REGEXP ?'; |
423
|
|
|
$keyword = Str::lower($keyword); |
424
|
|
|
} |
425
|
|
|
|
426
|
|
|
$this->query->whereRaw($sql, [$keyword]); |
427
|
|
|
} |
428
|
|
|
|
429
|
|
|
/** |
430
|
|
|
* Wrap a column and cast based on database driver. |
431
|
|
|
* |
432
|
|
|
* @param string $column |
433
|
|
|
* @return string |
434
|
|
|
*/ |
435
|
|
|
protected function castColumn($column) |
436
|
|
|
{ |
437
|
|
|
switch ($this->connection->getDriverName()) { |
438
|
|
|
case 'pgsql': |
439
|
|
|
return 'CAST(' . $column . ' as TEXT)'; |
440
|
|
|
case 'firebird': |
441
|
|
|
return 'CAST(' . $column . ' as VARCHAR(255))'; |
442
|
|
|
default: |
443
|
|
|
return $column; |
444
|
|
|
} |
445
|
|
|
} |
446
|
|
|
|
447
|
|
|
/** |
448
|
|
|
* Compile query builder where clause depending on configurations. |
449
|
|
|
* |
450
|
|
|
* @param mixed $query |
451
|
|
|
* @param string $column |
452
|
|
|
* @param string $keyword |
453
|
|
|
* @param string $boolean |
454
|
|
|
*/ |
455
|
|
|
protected function compileQuerySearch($query, $column, $keyword, $boolean = 'or') |
456
|
|
|
{ |
457
|
|
|
$column = $this->addTablePrefix($query, $column); |
458
|
|
|
$column = $this->castColumn($column); |
459
|
|
|
$sql = $column . ' LIKE ?'; |
460
|
|
|
|
461
|
|
|
if ($this->config->isCaseInsensitive()) { |
462
|
|
|
$sql = 'LOWER(' . $column . ') LIKE ?'; |
463
|
|
|
} |
464
|
|
|
|
465
|
|
|
$query->{$boolean . 'WhereRaw'}($sql, [$this->prepareKeyword($keyword)]); |
466
|
|
|
} |
467
|
|
|
|
468
|
|
|
/** |
469
|
|
|
* Patch for fix about ambiguous field. |
470
|
|
|
* Ambiguous field error will appear when query use join table and search with keyword. |
471
|
|
|
* |
472
|
|
|
* @param mixed $query |
473
|
|
|
* @param string $column |
474
|
|
|
* @return string |
475
|
|
|
*/ |
476
|
|
|
protected function addTablePrefix($query, $column) |
477
|
|
|
{ |
478
|
|
|
if (strpos($column, '.') === false) { |
479
|
|
|
$q = $this->getBaseQueryBuilder($query); |
480
|
|
|
if (! $q->from instanceof Expression) { |
|
|
|
|
481
|
|
|
$column = $q->from . '.' . $column; |
482
|
|
|
} |
483
|
|
|
} |
484
|
|
|
|
485
|
|
|
return $this->wrap($column); |
486
|
|
|
} |
487
|
|
|
|
488
|
|
|
/** |
489
|
|
|
* Prepare search keyword based on configurations. |
490
|
|
|
* |
491
|
|
|
* @param string $keyword |
492
|
|
|
* @return string |
493
|
|
|
*/ |
494
|
|
|
protected function prepareKeyword($keyword) |
495
|
|
|
{ |
496
|
|
|
if ($this->config->isCaseInsensitive()) { |
497
|
|
|
$keyword = Str::lower($keyword); |
498
|
|
|
} |
499
|
|
|
|
500
|
|
|
if ($this->config->isWildcard()) { |
501
|
|
|
$keyword = Helper::wildcardLikeString($keyword); |
502
|
|
|
} |
503
|
|
|
|
504
|
|
|
if ($this->config->isSmartSearch()) { |
505
|
|
|
$keyword = "%$keyword%"; |
506
|
|
|
} |
507
|
|
|
|
508
|
|
|
return $keyword; |
509
|
|
|
} |
510
|
|
|
|
511
|
|
|
/** |
512
|
|
|
* Add custom filter handler for the give column. |
513
|
|
|
* |
514
|
|
|
* @param string $column |
515
|
|
|
* @param callable $callback |
516
|
|
|
* @return $this |
517
|
|
|
*/ |
518
|
|
|
public function filterColumn($column, callable $callback) |
519
|
|
|
{ |
520
|
|
|
$this->columnDef['filter'][$column] = ['method' => $callback]; |
521
|
|
|
|
522
|
|
|
return $this; |
523
|
|
|
} |
524
|
|
|
|
525
|
|
|
/** |
526
|
|
|
* Order each given columns versus the given custom sql. |
527
|
|
|
* |
528
|
|
|
* @param array $columns |
529
|
|
|
* @param string $sql |
530
|
|
|
* @param array $bindings |
531
|
|
|
* @return $this |
532
|
|
|
*/ |
533
|
|
|
public function orderColumns(array $columns, $sql, $bindings = []) |
534
|
|
|
{ |
535
|
|
|
foreach ($columns as $column) { |
536
|
|
|
$this->orderColumn($column, str_replace(':column', $column, $sql), $bindings); |
537
|
|
|
} |
538
|
|
|
|
539
|
|
|
return $this; |
540
|
|
|
} |
541
|
|
|
|
542
|
|
|
/** |
543
|
|
|
* Override default column ordering. |
544
|
|
|
* |
545
|
|
|
* @param string $column |
546
|
|
|
* @param string $sql |
547
|
|
|
* @param array $bindings |
548
|
|
|
* @return $this |
549
|
|
|
* @internal string $1 Special variable that returns the requested order direction of the column. |
550
|
|
|
*/ |
551
|
|
|
public function orderColumn($column, $sql, $bindings = []) |
552
|
|
|
{ |
553
|
|
|
$this->columnDef['order'][$column] = compact('sql', 'bindings'); |
554
|
|
|
|
555
|
|
|
return $this; |
556
|
|
|
} |
557
|
|
|
|
558
|
|
|
/** |
559
|
|
|
* Set datatables to do ordering with NULLS LAST option. |
560
|
|
|
* |
561
|
|
|
* @return $this |
562
|
|
|
*/ |
563
|
|
|
public function orderByNullsLast() |
564
|
|
|
{ |
565
|
|
|
$this->nullsLast = true; |
566
|
|
|
|
567
|
|
|
return $this; |
568
|
|
|
} |
569
|
|
|
|
570
|
|
|
/** |
571
|
|
|
* Paginate dataTable using limit without offset |
572
|
|
|
* with additional where clause via callback. |
573
|
|
|
* |
574
|
|
|
* @param callable $callback |
575
|
|
|
* @return $this |
576
|
|
|
*/ |
577
|
|
|
public function limit(callable $callback) |
578
|
|
|
{ |
579
|
|
|
$this->limitCallback = $callback; |
580
|
|
|
|
581
|
|
|
return $this; |
582
|
|
|
} |
583
|
|
|
|
584
|
|
|
/** |
585
|
|
|
* Perform pagination. |
586
|
|
|
* |
587
|
|
|
* @return void |
588
|
|
|
*/ |
589
|
|
|
public function paging() |
590
|
|
|
{ |
591
|
|
|
$limit = (int) $this->request->input('length') > 0 ? $this->request->input('length') : 10; |
592
|
|
|
if (is_callable($this->limitCallback)) { |
593
|
|
|
$this->query->limit($limit); |
594
|
|
|
call_user_func_array($this->limitCallback, [$this->query]); |
595
|
|
|
} else { |
596
|
|
|
$this->query->skip($this->request->input('start'))->take($limit); |
597
|
|
|
} |
598
|
|
|
} |
599
|
|
|
|
600
|
|
|
/** |
601
|
|
|
* Add column in collection. |
602
|
|
|
* |
603
|
|
|
* @param string $name |
604
|
|
|
* @param string|callable $content |
605
|
|
|
* @param bool|int $order |
606
|
|
|
* @return $this |
607
|
|
|
*/ |
608
|
|
|
public function addColumn($name, $content, $order = false) |
609
|
|
|
{ |
610
|
|
|
$this->pushToBlacklist($name); |
611
|
|
|
|
612
|
|
|
return parent::addColumn($name, $content, $order); |
613
|
|
|
} |
614
|
|
|
|
615
|
|
|
/** |
616
|
|
|
* Resolve callback parameter instance. |
617
|
|
|
* |
618
|
|
|
* @return \Illuminate\Database\Query\Builder |
619
|
|
|
*/ |
620
|
|
|
protected function resolveCallbackParameter() |
621
|
|
|
{ |
622
|
|
|
return $this->query; |
623
|
|
|
} |
624
|
|
|
|
625
|
|
|
/** |
626
|
|
|
* Perform default query orderBy clause. |
627
|
|
|
*/ |
628
|
|
|
protected function defaultOrdering() |
629
|
|
|
{ |
630
|
|
|
collect($this->request->orderableColumns()) |
631
|
|
|
->map(function ($orderable) { |
632
|
|
|
$orderable['name'] = $this->getColumnName($orderable['column'], true); |
633
|
|
|
|
634
|
|
|
return $orderable; |
635
|
|
|
}) |
636
|
|
|
->reject(function ($orderable) { |
637
|
|
|
return $this->isBlacklisted($orderable['name']) && ! $this->hasOrderColumn($orderable['name']); |
638
|
|
|
}) |
639
|
|
|
->each(function ($orderable) { |
640
|
|
|
$column = $this->resolveRelationColumn($orderable['name']); |
641
|
|
|
|
642
|
|
|
if ($this->hasOrderColumn($column)) { |
643
|
|
|
$this->applyOrderColumn($column, $orderable); |
644
|
|
|
} else { |
645
|
|
|
$nullsLastSql = $this->getNullsLastSql($column, $orderable['direction']); |
646
|
|
|
$normalSql = $this->wrap($column) . ' ' . $orderable['direction']; |
647
|
|
|
$sql = $this->nullsLast ? $nullsLastSql : $normalSql; |
648
|
|
|
$this->query->orderByRaw($sql); |
649
|
|
|
} |
650
|
|
|
}); |
651
|
|
|
} |
652
|
|
|
|
653
|
|
|
/** |
654
|
|
|
* Check if column has custom sort handler. |
655
|
|
|
* |
656
|
|
|
* @param string $column |
657
|
|
|
* @return bool |
658
|
|
|
*/ |
659
|
|
|
protected function hasOrderColumn($column) |
660
|
|
|
{ |
661
|
|
|
return isset($this->columnDef['order'][$column]); |
662
|
|
|
} |
663
|
|
|
|
664
|
|
|
/** |
665
|
|
|
* Apply orderColumn custom query. |
666
|
|
|
* |
667
|
|
|
* @param string $column |
668
|
|
|
* @param array $orderable |
669
|
|
|
*/ |
670
|
|
|
protected function applyOrderColumn($column, $orderable) |
671
|
|
|
{ |
672
|
|
|
$sql = $this->columnDef['order'][$column]['sql']; |
673
|
|
|
$sql = str_replace('$1', $orderable['direction'], $sql); |
674
|
|
|
$bindings = $this->columnDef['order'][$column]['bindings']; |
675
|
|
|
$this->query->orderByRaw($sql, $bindings); |
676
|
|
|
} |
677
|
|
|
|
678
|
|
|
/** |
679
|
|
|
* Get NULLS LAST SQL. |
680
|
|
|
* |
681
|
|
|
* @param string $column |
682
|
|
|
* @param string $direction |
683
|
|
|
* @return string |
684
|
|
|
*/ |
685
|
|
|
protected function getNullsLastSql($column, $direction) |
686
|
|
|
{ |
687
|
|
|
$sql = $this->config->get('datatables.nulls_last_sql', '%s %s NULLS LAST'); |
688
|
|
|
|
689
|
|
|
return sprintf($sql, $column, $direction); |
690
|
|
|
} |
691
|
|
|
|
692
|
|
|
/** |
693
|
|
|
* Perform global search for the given keyword. |
694
|
|
|
* |
695
|
|
|
* @param string $keyword |
696
|
|
|
*/ |
697
|
|
|
protected function globalSearch($keyword) |
698
|
|
|
{ |
699
|
|
|
$this->query->where(function ($query) use ($keyword) { |
700
|
|
|
collect($this->request->searchableColumnIndex()) |
701
|
|
|
->map(function ($index) { |
702
|
|
|
return $this->getColumnName($index); |
703
|
|
|
}) |
704
|
|
|
->reject(function ($column) { |
705
|
|
|
return $this->isBlacklisted($column) && ! $this->hasFilterColumn($column); |
706
|
|
|
}) |
707
|
|
|
->each(function ($column) use ($keyword, $query) { |
708
|
|
|
if ($this->hasFilterColumn($column)) { |
709
|
|
|
$this->applyFilterColumn($query, $column, $keyword, 'or'); |
710
|
|
|
} else { |
711
|
|
|
$this->compileQuerySearch($query, $column, $keyword); |
712
|
|
|
} |
713
|
|
|
|
714
|
|
|
$this->isFilterApplied = true; |
715
|
|
|
}); |
716
|
|
|
}); |
717
|
|
|
} |
718
|
|
|
|
719
|
|
|
/** |
720
|
|
|
* Append debug parameters on output. |
721
|
|
|
* |
722
|
|
|
* @param array $output |
723
|
|
|
* @return array |
724
|
|
|
*/ |
725
|
|
|
protected function showDebugger(array $output) |
726
|
|
|
{ |
727
|
|
|
$query_log = $this->connection->getQueryLog(); |
728
|
|
|
array_walk_recursive($query_log, function (&$item, $key) { |
|
|
|
|
729
|
|
|
$item = utf8_encode($item); |
730
|
|
|
}); |
731
|
|
|
|
732
|
|
|
$output['queries'] = $query_log; |
733
|
|
|
$output['input'] = $this->request->all(); |
734
|
|
|
|
735
|
|
|
return $output; |
736
|
|
|
} |
737
|
|
|
|
738
|
|
|
/** |
739
|
|
|
* Attach custom with meta on response. |
740
|
|
|
* |
741
|
|
|
* @param array $data |
742
|
|
|
* @return array |
743
|
|
|
*/ |
744
|
|
|
protected function attachAppends(array $data) |
745
|
|
|
{ |
746
|
|
|
$appends = []; |
747
|
|
|
foreach ($this->appends as $key => $value) { |
748
|
|
|
if (is_callable($value)) { |
749
|
|
|
$appends[$key] = value($value($this->getFilteredQuery())); |
750
|
|
|
} else { |
751
|
|
|
$appends[$key] = $value; |
752
|
|
|
} |
753
|
|
|
} |
754
|
|
|
|
755
|
|
|
return array_merge($data, $appends); |
756
|
|
|
} |
757
|
|
|
} |
758
|
|
|
|
This error could be the result of:
1. Missing dependencies
PHP Analyzer uses your
composer.json
file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects thecomposer.json
to be in the root folder of your repository.Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the
require
orrequire-dev
section?2. Missing use statement
PHP does not complain about undefined classes in
ìnstanceof
checks. For example, the following PHP code will work perfectly fine:If you have not tested against this specific condition, such errors might go unnoticed.