├── .gitignore
├── .travis.yml
├── README.md
├── composer.json
├── phpunit.xml
├── public
└── .gitkeep
├── src
├── Bllim
│ └── Datatables
│ │ ├── Datatables.php
│ │ ├── DatatablesServiceProvider.php
│ │ └── Facade
│ │ └── Datatables.php
├── config
│ ├── .gitkeep
│ └── config.php
├── lang
│ └── .gitkeep
├── migrations
│ └── .gitkeep
└── views
│ └── .gitkeep
└── tests
├── .gitkeep
├── Datatables.php
└── bootstrap
├── autoload.php
├── paths.php
└── start.php
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 | composer.phar
3 | composer.lock
4 | .DS_Store
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 5.3
5 | - 5.4
6 |
7 | before_script:
8 | - curl -s http://getcomposer.org/installer | php
9 | - php composer.phar install --dev
10 |
11 | script: phpunit
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Datatables Bundle for Laravel 4
2 |
3 | ###WARNING
4 |
5 | Do not use this package directly, use [Bllim](https://github.com/bllim/laravel4-datatables-package) instead.
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pragmarx/datatables",
3 | "description": "Server-side handler of DataTables Jquery Plugin for Laravel 4",
4 | "keywords" : ["laravel4","laravel","datatables","datatable","datatables jquery plugin"],
5 | "license": "MIT",
6 | "authors": [
7 | {
8 | "name": "Bilal Gultekin",
9 | "email": "bilal@bilal.im"
10 | }
11 | ],
12 | "require": {
13 | "php": ">=5.3.0",
14 | "illuminate/support": ">=4.0.0",
15 | "illuminate/database": ">=4.0.0",
16 | "illuminate/view": ">=4.0.0",
17 | "illuminate/filesystem": ">=4.0.0"
18 | },
19 | "require-dev": {
20 | "laravel/laravel": ">=4.0.0",
21 | "mockery/mockery": "0.7.2",
22 | "phpunit/phpunit": "3.7.*"
23 | },
24 | "autoload": {
25 | "classmap": [
26 | "src/migrations"
27 | ],
28 | "psr-0": {
29 | "Bllim\\Datatables": "src/"
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 | ./tests/
16 |
17 |
18 |
--------------------------------------------------------------------------------
/public/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antonioribeiro/laravel4-datatables-package/142631346d01c074248b80c8bc55fdfea255e23e/public/.gitkeep
--------------------------------------------------------------------------------
/src/Bllim/Datatables/Datatables.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 |
14 | use Illuminate\Support\Facades\DB;
15 | use Illuminate\Support\Facades\Input;
16 | use Illuminate\Support\Facades\Config;
17 | use Illuminate\Support\Facades\Response;
18 | use Illuminate\Support\Arr;
19 | use Illuminate\Support\Str;
20 | use Illuminate\View\Compilers\BladeCompiler;
21 | use Illuminate\Filesystem\Filesystem;
22 |
23 | class Datatables
24 | {
25 | /**
26 | * @var \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $query
27 | */
28 | public $query;
29 |
30 | /**
31 | * @var string $query_type 'eloquent' | 'fluent'
32 | */
33 | protected $query_type;
34 |
35 | protected $added_columns = array();
36 | protected $removed_columns = array();
37 | protected $edit_columns = array();
38 | protected $filter_columns = array();
39 | protected $sColumns = array();
40 |
41 | public $columns = array();
42 | public $aliased_ordered_columns = array();
43 |
44 | protected $count_all = 0;
45 | protected $display_all = 0;
46 |
47 | protected $result_object;
48 | protected $result_array = array();
49 | protected $result_array_return = array();
50 |
51 | protected $input = array();
52 | protected $mDataSupport; //previous support included only returning columns as object with key names
53 | protected $dataFullSupport; //new support that better implements dot notation without reliance on name column
54 |
55 | protected $index_column;
56 | protected $row_class_tmpl = null;
57 | protected $row_data_tmpls = array();
58 |
59 |
60 | /**
61 | * Read Input into $this->input according to jquery.dataTables.js version
62 | *
63 | */
64 | public function __construct()
65 | {
66 |
67 | $this->setData($this->processData(Input::get()));
68 |
69 | return $this;
70 | }
71 |
72 | /**
73 | * Will take an input array and return the formatted dataTables data as an array
74 | *
75 | * @param array $input
76 | *
77 | * @return array
78 | */
79 | public function processData($input = [])
80 | {
81 | $formatted_input = [];
82 |
83 | if (isset($input['draw'])) {
84 | // DT version 1.10+
85 |
86 | $input['version'] = '1.10';
87 |
88 | $formatted_input = $input;
89 |
90 | } else {
91 | // DT version < 1.10
92 |
93 | $formatted_input['version'] = '1.9';
94 |
95 | $formatted_input['draw'] = Arr::get($input, 'sEcho', '');
96 | $formatted_input['start'] = Arr::get($input, 'iDisplayStart', 0);
97 | $formatted_input['length'] = Arr::get($input, 'iDisplayLength', 10);
98 | $formatted_input['search'] = array(
99 | 'value' => Arr::get($input, 'sSearch', ''),
100 | 'regex' => Arr::get($input, 'bRegex', ''),
101 | );
102 | $formatted_input['_'] = Arr::get($input, '_', '');
103 |
104 | $columns = explode(',', Arr::get($input, 'sColumns', ''));
105 | $formatted_input['columns'] = array();
106 | for ($i = 0; $i < Arr::get($input, 'iColumns', 0); $i++) {
107 | $arr = array();
108 | $arr['name'] = isset($columns[$i]) ? $columns[$i] : '';
109 | $arr['data'] = Arr::get($input, 'mDataProp_' . $i, '');
110 | $arr['searchable'] = Arr::get($input, 'bSearchable_' . $i, '');
111 | $arr['search'] = array();
112 | $arr['search']['value'] = Arr::get($input, 'sSearch_' . $i, '');
113 | $arr['search']['regex'] = Arr::get($input, 'bRegex_' . $i, '');
114 | $arr['orderable'] = Arr::get($input, 'bSortable_' . $i, '');
115 | $formatted_input['columns'][] = $arr;
116 | }
117 |
118 | $formatted_input['order'] = array();
119 | for ($i = 0; $i < Arr::get($input, 'iSortingCols', 0); $i++) {
120 | $arr = array();
121 | $arr['column'] = Arr::get($input, 'iSortCol_' . $i, '');
122 | $arr['dir'] = Arr::get($input, 'sSortDir_' . $i, '');
123 | $formatted_input['order'][] = $arr;
124 | }
125 | }
126 |
127 | return $formatted_input;
128 | }
129 |
130 | /**
131 | * @return array $this->input
132 | */
133 | public function getData()
134 | {
135 | return $this->input;
136 | }
137 |
138 | /**
139 | * Sets input data.
140 | * Can be used when not wanting to use default Input data.
141 | *
142 | * @param array $data
143 | */
144 | public function setData($data)
145 | {
146 | $this->input = $data;
147 | }
148 |
149 | /**
150 | * Gets query and returns instance of class
151 | *
152 | * @param \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $query
153 | * @param null $dataFullSupport
154 | *
155 | * @return Datatables
156 | */
157 | public static function of($query, $dataFullSupport = null)
158 | {
159 | $ins = new static;
160 | $ins->dataFullSupport = ($dataFullSupport) ?: Config::get('datatables::dataFullSupport', false);
161 | $ins->saveQuery($query);
162 |
163 | return $ins;
164 | }
165 |
166 | /**
167 | * Organizes works
168 | *
169 | * @param bool $mDataSupport
170 | * @param bool $raw
171 | *
172 | * @return array|json
173 | */
174 | public function make($mDataSupport = false, $raw = false)
175 | {
176 | $this->mDataSupport = $mDataSupport;
177 | $this->createAliasedOrderedColumns();
178 | $this->prepareQuery();
179 | $this->getResult();
180 | $this->modifyColumns();
181 | $this->regulateArray();
182 |
183 | return $this->output($raw);
184 | }
185 |
186 | /**
187 | * Gets results from prepared query
188 | *
189 | * @return null
190 | */
191 | protected function getResult()
192 | {
193 | if ($this->query_type == 'eloquent') {
194 | $this->result_object = $this->query->get();
195 | $this->result_array = $this->result_object->toArray();
196 | } else {
197 | $this->result_object = $this->query->get();
198 | $this->result_array = array_map(function ($object) {
199 | return (array)$object;
200 | }, $this->result_object);
201 | }
202 |
203 | if ($this->dataFullSupport) {
204 | $walk = function ($value, $key, $prefix = null) use (&$walk, &$result_array) {
205 | $key = (!is_null($prefix)) ? ($prefix . "." . $key) : $key;
206 | if (is_array($value)) {
207 | array_walk($value, $walk, $key);
208 | } else {
209 | $result_array = Arr::add($result_array, $key, $value);
210 | }
211 | };
212 |
213 | $result_array = array();
214 | array_walk($this->result_array, $walk);
215 | $this->result_array = $result_array;
216 |
217 | }
218 |
219 | }
220 |
221 | /**
222 | * Prepares variables according to Datatables parameters
223 | *
224 | * @return null
225 | */
226 | protected function prepareQuery()
227 | {
228 | $this->count('count_all'); //Total records
229 | $this->filtering();
230 | $this->count('display_all'); // Filtered records
231 | $this->paging();
232 | $this->ordering();
233 | }
234 |
235 | /**
236 | * Adds additional columns to added_columns
237 | *
238 | * @param string $name
239 | * @param string|callable $content
240 | * @param bool $order
241 | *
242 | * @return $this
243 | */
244 | public function addColumn($name, $content, $order = false)
245 | {
246 | $this->sColumns[] = $name;
247 |
248 | $this->added_columns[] = array('name' => $name, 'content' => $content, 'order' => $order);
249 |
250 | return $this;
251 | }
252 |
253 | /**
254 | * Adds column names to edit_columns
255 | *
256 | * @param string $name
257 | * @param string|callable $content
258 | *
259 | * @return $this
260 | */
261 | public function editColumn($name, $content)
262 | {
263 | $this->edit_columns[] = array('name' => $name, 'content' => $content);
264 |
265 | return $this;
266 | }
267 |
268 |
269 | /**
270 | * This will remove the columns from the returned data. It will also cause it to skip any filters for those removed columns.
271 | * Adds a list of columns to removed_columns
272 | *
273 | * @params strings ...,... As many individual string parameters matching column names
274 | *
275 | * @return $this
276 | */
277 | public function removeColumn()
278 | {
279 | $names = func_get_args();
280 | $this->removed_columns = array_merge($this->removed_columns, $names);
281 |
282 | return $this;
283 | }
284 |
285 | /**
286 | * The filtered columns will add query sql options for the specified columns
287 | * Adds column filter to filter_columns
288 | *
289 | * @param string $column
290 | * @param string $method
291 | * @param mixed ...,... All the individual parameters required for specified $method
292 | *
293 | * @return $this
294 | */
295 | public function filterColumn($column, $method)
296 | {
297 | $params = func_get_args();
298 | $this->filter_columns[$column] = array('method' => $method, 'parameters' => array_splice($params, 2));
299 |
300 | return $this;
301 | }
302 |
303 |
304 | /**
305 | * Sets the DT_RowID for the DataTables index column (as used to set, e.g., id of the
tags) to the named column
306 | * If the index matches a column, then that column value will be set as the id of th
.
307 | * If the index doesn't, it will be parsed as either a callback or blade template and that returned value will be the
308 | * id of the
309 | *
310 | * @param string $name
311 | *
312 | * @return $this
313 | */
314 | public function setIndexColumn($name)
315 | {
316 | $this->index_column = $name;
317 |
318 | return $this;
319 | }
320 |
321 | /**
322 | * Sets DT_RowClass template
323 | * result:
324 | *
325 | * @param string|callable $content
326 | *
327 | * @return $this
328 | */
329 | public function setRowClass($content)
330 | {
331 | $this->row_class_tmpl = $content;
332 |
333 | return $this;
334 | }
335 |
336 | /**
337 | * Sets DT_RowData template for given attribute name
338 | * result: Datatables invoking $(row).data(name, output_from_your_template)
339 | *
340 | * @param string $name
341 | * @param string|callable $content
342 | *
343 | * @return $this
344 | */
345 | public function setRowData($name, $content)
346 | {
347 | $this->row_data_tmpls[$name] = $content;
348 |
349 | return $this;
350 | }
351 |
352 | /**
353 | * Saves given query and determines its type
354 | *
355 | * @param \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $query
356 | *
357 | * @return null
358 | */
359 | protected function saveQuery($query)
360 | {
361 | $this->query = $query;
362 | $this->query_type = $query instanceof \Illuminate\Database\Query\Builder ? 'fluent' : 'eloquent';
363 | if ($this->dataFullSupport) {
364 | if ($this->query_type == 'eloquent') {
365 | $this->columns = array_map(function ($column) {
366 | return trim(DB::connection()->getPdo()->quote($column['data']), "'");
367 | }, $this->input['columns']);
368 | } else {
369 | $this->columns = ($this->query->columns ?: array());
370 | }
371 | } else {
372 | $this->columns = $this->query_type == 'eloquent' ? ($this->query->getQuery()->columns ?: array()) : ($this->query->columns ?: array());
373 | }
374 | }
375 |
376 | /**
377 | * Places extra columns
378 | *
379 | * @return null
380 | */
381 | protected function modifyColumns()
382 | {
383 | foreach ($this->result_array as $rkey => &$rvalue) {
384 | foreach ($this->added_columns as $key => $value) {
385 | $value['content'] = $this->getContent($value['content'], $rvalue, $this->result_object[$rkey]);
386 |
387 | if ($this->dataFullSupport) {
388 | Arr::set($rvalue, $value['name'], $value['content']);
389 | } else {
390 | $rvalue = $this->includeInArray($value, $rvalue);
391 | }
392 | }
393 |
394 | foreach ($this->edit_columns as $key => $value) {
395 | $value['content'] = $this->getContent($value['content'], $rvalue, $this->result_object[$rkey]);
396 |
397 | if ($this->dataFullSupport) {
398 | Arr::set($rvalue, $value['name'], $value['content']);
399 | } else {
400 | $rvalue[$value['name']] = $value['content'];
401 | }
402 | }
403 | }
404 | }
405 |
406 | /**
407 | * Converts result_array number indexed array and consider excess columns
408 | *
409 | * @return null
410 | * @throws \Exception
411 | */
412 | protected function regulateArray()
413 | {
414 | foreach ($this->result_array as $key => $value) {
415 | foreach ($this->removed_columns as $remove_col_name) {
416 | if ($this->dataFullSupport) {
417 | Arr::forget($value, $remove_col_name);
418 | } else {
419 | unset($value[$remove_col_name]);
420 | }
421 | }
422 |
423 | if ($this->mDataSupport || $this->dataFullSupport) {
424 | $row = $value;
425 | } else {
426 | $row = array_values($value);
427 | }
428 |
429 | if ($this->index_column !== null) {
430 | if (array_key_exists($this->index_column, $value)) {
431 | $row['DT_RowId'] = $value[$this->index_column];
432 | } else {
433 | $row['DT_RowId'] = $this->getContent($this->index_column, $value, $this->result_object[$key]);
434 | }
435 | }
436 |
437 | if ($this->row_class_tmpl !== null) {
438 | $row['DT_RowClass'] = $this->getContent($this->row_class_tmpl, $value, $this->result_object[$key]);
439 | }
440 |
441 | if (count($this->row_data_tmpls)) {
442 | $row['DT_RowData'] = array();
443 | foreach ($this->row_data_tmpls as $tkey => $tvalue) {
444 | $row['DT_RowData'][$tkey] = $this->getContent($tvalue, $value, $this->result_object[$key]);
445 | }
446 | }
447 |
448 | $this->result_array_return[] = $row;
449 | }
450 | }
451 |
452 | /**
453 | *
454 | * Inject searched string into $1 in filter_column parameters
455 | *
456 | * @param array|callable|string|Expression &$params
457 | * @param string $value
458 | *
459 | * @return array
460 | */
461 | private function injectVariable(&$params, $value)
462 | {
463 | if (is_array($params)) {
464 | foreach ($params as $key => $param) {
465 | $params[$key] = $this->injectVariable($param, $value);
466 | }
467 |
468 | } elseif ($params instanceof \Illuminate\Database\Query\Expression) {
469 | $params = DB::raw(str_replace('$1', $value, $params));
470 |
471 | } elseif (is_callable($params)) {
472 | $params = $params($value);
473 |
474 | } elseif (is_string($params)) {
475 | $params = str_replace('$1', $value, $params);
476 | }
477 |
478 | return $params;
479 | }
480 |
481 | /**
482 | * Creates an array which contains published aliased ordered columns in sql with their index
483 | *
484 | * Creates an array of column names using column aliases where applicable.
485 | * If an added column has a particular order number, it will skip that array key #
486 | * and continue to the next. Leaves dot notation in column names alone.
487 | *
488 | * @return null
489 | */
490 | protected function createAliasedOrderedColumns()
491 | {
492 | $added_columns_indexes = array();
493 | $aliased_ordered_columns = array();
494 | $count = 0;
495 |
496 | foreach ($this->added_columns as $key => $value) {
497 | if ($value['order'] === false) {
498 | continue;
499 | }
500 | $added_columns_indexes[] = $value['order'];
501 | }
502 |
503 | for ($i = 0, $c = count($this->columns); $i < $c; $i++) {
504 |
505 | if (in_array($this->getColumnName($this->columns[$i]), $this->removed_columns)) {
506 | continue;
507 | }
508 |
509 | if (in_array($count, $added_columns_indexes)) {
510 | $count++;
511 | $i--;
512 | continue;
513 | }
514 |
515 | // previous regex #^(\S*?)\s+as\s+(\S*?)$# prevented subqueries and functions from being detected as alias
516 | preg_match('#\s+as\s+(\S*?)$#si', $this->columns[$i], $matches);
517 | $aliased_ordered_columns[$count] = empty($matches) ? $this->columns[$i] : $matches[1];
518 | $count++;
519 | }
520 |
521 | $this->aliased_ordered_columns = $aliased_ordered_columns;
522 | }
523 |
524 | /**
525 | * Determines if content is callable or blade string, processes and returns
526 | *
527 | * @param string|callable $content Pre-processed content
528 | * @param mixed $data data to use with blade template
529 | * @param mixed $param parameter to call with callable
530 | *
531 | * @return string Processed content
532 | */
533 | protected function getContent($content, $data = null, $param = null)
534 | {
535 | if (is_string($content)) {
536 | $return = $this->blader($content, $data);
537 | } elseif (is_callable($content)) {
538 | $return = $content($param);
539 | } else {
540 | $return = $content;
541 | }
542 |
543 | return $return;
544 | }
545 |
546 | /**
547 | * Parses and compiles strings by using Blade Template System
548 | *
549 | * @param string $str
550 | * @param array $data
551 | *
552 | * @return string
553 | * @throws \Exception
554 | */
555 | protected function blader($str, $data = array())
556 | {
557 | $empty_filesystem_instance = new Filesystem;
558 | $blade = new BladeCompiler($empty_filesystem_instance, 'datatables');
559 | $parsed_string = $blade->compileString($str);
560 |
561 | ob_start() and extract($data, EXTR_SKIP);
562 |
563 | try {
564 | eval('?>' . $parsed_string);
565 | }
566 | catch (\Exception $e) {
567 | ob_end_clean();
568 | throw $e;
569 | }
570 |
571 | $str = ob_get_contents();
572 | ob_end_clean();
573 |
574 | return $str;
575 | }
576 |
577 | /**
578 | * Places item of extra columns into result_array by care of their order
579 | * Only necessary if not using mData
580 | *
581 | * @param array $item
582 | * @param array $array
583 | *
584 | * @return null
585 | */
586 | protected function includeInArray($item, $array)
587 | {
588 | if ($item['order'] === false) {
589 | return array_merge($array, array($item['name'] => $item['content']));
590 | } else {
591 | $count = 0;
592 | $last = $array;
593 | $first = array();
594 |
595 | if (count($array) <= $item['order']) {
596 | return $array + array($item['name'] => $item['content']);
597 | }
598 |
599 | foreach ($array as $key => $value) {
600 | if ($count == $item['order']) {
601 | return array_merge($first, array($item['name'] => $item['content']), $last);
602 | }
603 |
604 | unset($last[$key]);
605 | $first[$key] = $value;
606 |
607 | $count++;
608 | }
609 | }
610 | }
611 |
612 | /**
613 | * Datatable paging
614 | *
615 | * @return null
616 | */
617 | protected function paging()
618 | {
619 | if (!is_null($this->input['start']) && !is_null($this->input['length']) && $this->input['length'] != -1) {
620 | $this->query->skip($this->input['start'])->take((int)$this->input['length'] > 0 ? $this->input['length'] : 10);
621 | }
622 | }
623 |
624 | /**
625 | * Datatable ordering
626 | *
627 | * @return null
628 | */
629 | protected function ordering()
630 | {
631 | if (array_key_exists('order', $this->input) && count($this->input['order']) > 0) {
632 | $columns = $this->cleanColumns($this->aliased_ordered_columns);
633 |
634 | for ($i = 0, $c = count($this->input['order']); $i < $c; $i++) {
635 | $order_col = (int)$this->input['order'][$i]['column'];
636 | if (isset($columns[$order_col])) {
637 | if ($this->input['columns'][$order_col]['orderable'] == "true") {
638 | $this->query->orderBy($columns[$order_col], $this->input['order'][$i]['dir']);
639 | }
640 | }
641 | }
642 |
643 | }
644 | }
645 |
646 | /**
647 | * @param array $cols
648 | * @param bool $use_alias weather to get the column/function or the alias
649 | *
650 | * @return array
651 | */
652 | protected function cleanColumns($cols, $use_alias = true)
653 | {
654 | $return = array();
655 | foreach ($cols as $i => $col) {
656 | preg_match('#^(.*?)\s+as\s+(\S*?)\s*$#si', $col, $matches);
657 | if (empty($matches)) {
658 | $return[$i] = $use_alias ? $this->getColumnName($col) : $col;
659 | } else {
660 | $return[$i] = $matches[$use_alias ? 2 : 1];
661 | }
662 |
663 | }
664 |
665 | return $return;
666 | }
667 |
668 | /**
669 | * Datatable filtering
670 | *
671 | * @return null
672 | */
673 | protected function filtering()
674 | {
675 |
676 | // copy of $this->columns without columns removed by remove_column
677 | $columns_not_removed = $this->columns;
678 | for ($i = 0, $c = count($columns_not_removed); $i < $c; $i++) {
679 | if (in_array($this->getColumnName($columns_not_removed[$i]), $this->removed_columns)) {
680 | unset($columns_not_removed[$i]);
681 | }
682 | }
683 |
684 | //reindex keys if columns were removed
685 | $columns_not_removed = array_values($columns_not_removed);
686 |
687 | // copy of $this->columns cleaned for database queries
688 | $column_names = $this->cleanColumns($columns_not_removed, false);
689 | $column_aliases = $this->cleanColumns($columns_not_removed, !$this->dataFullSupport);
690 |
691 | // global search
692 | if ($this->input['search']['value'] != '') {
693 | $that = $this;
694 |
695 | $this->query->where(function ($query) use (&$that, $column_aliases, $column_names) {
696 |
697 | for ($i = 0, $c = count($that->input['columns']); $i < $c; $i++) {
698 | if (isset($column_aliases[$i]) && $that->input['columns'][$i]['searchable'] == "true") {
699 |
700 | // if filter column exists for this columns then use user defined method
701 | if (isset($that->filter_columns[$column_aliases[$i]])) {
702 |
703 | $filter = $that->filter_columns[$column_aliases[$i]];
704 |
705 | // check if "or" equivalent exists for given function
706 | // and if the number of parameters given is not excess
707 | // than call the "or" equivalent
708 |
709 | $method_name = 'or' . ucfirst($filter['method']);
710 |
711 | if (method_exists($query->getQuery(), $method_name)
712 | && count($filter['parameters']) <= with(new \ReflectionMethod($query->getQuery(), $method_name))->getNumberOfParameters()
713 | ) {
714 |
715 | if (isset($filter['parameters'][1])
716 | && strtoupper(trim($filter['parameters'][1])) == "LIKE"
717 | ) {
718 | $keyword = $that->formatKeyword($that->input['search']['value']);
719 | } else {
720 | $keyword = $that->input['search']['value'];
721 | }
722 |
723 | call_user_func_array(
724 | array(
725 | $query,
726 | $method_name
727 | ),
728 | $that->injectVariable(
729 | $filter['parameters'],
730 | $keyword
731 | )
732 | );
733 | }
734 |
735 | } else {
736 | // otherwise do simple LIKE search
737 |
738 | $keyword = $that->formatKeyword($that->input['search']['value']);
739 |
740 | // Check if the database driver is PostgreSQL
741 | // If it is, cast the current column to TEXT datatype
742 | $cast_begin = null;
743 | $cast_end = null;
744 | if ($this->databaseDriver() === 'pgsql') {
745 | $cast_begin = "CAST(";
746 | $cast_end = " as TEXT)";
747 | }
748 |
749 | //there's no need to put the prefix unless the column name is prefixed with the table name.
750 | $column = $this->prefixColumn($column_names[$i]);
751 |
752 | if (Config::get('datatables::search.case_insensitive', false)) {
753 | $query->orwhere(DB::raw('LOWER(' . $cast_begin . $column . $cast_end . ')'), 'LIKE', Str::lower($keyword));
754 | } else {
755 | $query->orwhere(DB::raw($cast_begin . $column . $cast_end), 'LIKE', $keyword);
756 | }
757 | }
758 |
759 | }
760 | }
761 | });
762 |
763 | }
764 |
765 | // column search
766 | for ($i = 0, $c = count($this->input['columns']); $i < $c; $i++) {
767 | if (isset($column_aliases[$i]) && $this->input['columns'][$i]['searchable'] == "true" && $this->input['columns'][$i]['search']['value'] != '') {
768 | // if filter column exists for this columns then use user defined method
769 | if (isset($this->filter_columns[$column_aliases[$i]])) {
770 |
771 | $filter = $this->filter_columns[$column_aliases[$i]];
772 |
773 | if (isset($filter['parameters'][1])
774 | && strtoupper(trim($filter['parameters'][1])) == "LIKE"
775 | ) {
776 | $keyword = $this->formatKeyword($this->input['columns'][$i]['search']['value']);
777 | } else {
778 | $keyword = $this->input['columns'][$i]['search']['value'];
779 | }
780 |
781 |
782 | call_user_func_array(
783 | array(
784 | $this->query,
785 | $filter['method']
786 | ),
787 | $this->injectVariable(
788 | $filter['parameters'],
789 | $keyword
790 | )
791 | );
792 |
793 | } else // otherwise do simple LIKE search
794 | {
795 |
796 | $keyword = $this->formatKeyword($this->input['columns'][$i]['search']['value']);
797 |
798 | //there's no need to put the prefix unless the column name is prefixed with the table name.
799 | $column = $this->prefixColumn($column_names[$i]);
800 |
801 | if (Config::get('datatables::search.case_insensitive', false)) {
802 | $this->query->where(DB::raw('LOWER(' . $column . ')'), 'LIKE', Str::lower($keyword));
803 | } else {
804 | //note: so, when would a ( be in the columns? It will break a select if that's put in the columns
805 | //without a DB::raw. It could get there in filter columns, but it wouldn't be delt with here.
806 | //why is it searching for ( ?
807 | $col = strstr($column_names[$i], '(') ? DB::raw($column) : $column;
808 | $this->query->where($col, 'LIKE', $keyword);
809 | }
810 | }
811 | }
812 | }
813 | }
814 |
815 | /**
816 | * This will format the keyword as needed for "LIKE" based on config settings
817 | * If $value already has %, it doesn't motify and just returns the value.
818 | *
819 | * @param string $value
820 | *
821 | * @return string
822 | */
823 | public function formatKeyword($value)
824 | {
825 | if (strpos($value, '%') !== false) {
826 | return $value;
827 | }
828 |
829 | if (Config::get('datatables::search.use_wildcards', false)) {
830 | $keyword = '%' . $this->formatWildcard($value) . '%';
831 | } else {
832 | $keyword = '%' . trim($value) . '%';
833 | }
834 |
835 | return $keyword;
836 | }
837 |
838 | /**
839 | * Adds % wildcards to the given string
840 | *
841 | * @param $str
842 | * @param bool $lowercase
843 | *
844 | * @return string
845 | */
846 | public function formatWildcard($str, $lowercase = true)
847 | {
848 | if ($lowercase) {
849 | $str = lowercase($str);
850 | }
851 |
852 | return preg_replace('\s+', '%', $str);
853 | }
854 |
855 | /**
856 | * Returns current database prefix
857 | *
858 | * @return string
859 | */
860 | public function databasePrefix()
861 | {
862 | if ($this->query_type == 'eloquent') {
863 | $query = $this->query->getQuery();
864 | } else {
865 | $query = $this->query;
866 | }
867 | return $query->getGrammar()->getTablePrefix();
868 | //return Config::get('database.connections.' . Config::get('database.default') . '.prefix', '');
869 | }
870 |
871 | /**
872 | * Returns current database driver
873 | */
874 | protected function databaseDriver()
875 | {
876 | if ($this->query_type == 'eloquent') {
877 | $query = $this->query->getQuery();
878 | } else {
879 | $query = $this->query;
880 | }
881 | return $query->getConnection()->getDriverName();
882 | }
883 |
884 | /**
885 | * Will prefix column if needed
886 | *
887 | * @param string $column
888 | * @return string
889 | */
890 | protected function prefixColumn($column)
891 | {
892 | // $query = ($this->query_type == 'eloquent') ? $this->query->getQuery() : $this->query;
893 | // return $query->getGrammar()->wrap($column);
894 |
895 | $table_names = $this->tableNames();
896 | if (count(array_filter($table_names, function($value) use (&$column) { return strpos($column, $value.".") === 0; }))) {
897 | //the column starts with one of the table names
898 | $column = $this->databasePrefix() . $column;
899 | }
900 | return $column;
901 | }
902 |
903 | /**
904 | * Will look through the query and all it's joins to determine the table names
905 | *
906 | * @return array
907 | */
908 | protected function tableNames()
909 | {
910 | $names = [];
911 |
912 | $query = ($this->query_type == 'eloquent') ? $this->query->getQuery() : $this->query;
913 |
914 | $names[] = $query->from;
915 | $joins = $query->joins?:array();
916 | $databasePrefix = $this->databasePrefix();
917 | foreach ($joins as $join) {
918 | $table = preg_split("/ as /i", $join->table);
919 | $names[] = $table[0];
920 | if (isset($table[1]) && !empty($databasePrefix) && strpos($table[1], $databasePrefix) == 0) {
921 | $names[] = preg_replace('/^'.$databasePrefix.'/', '', $table[1]);
922 | }
923 | }
924 |
925 | return $names;
926 |
927 | }
928 |
929 | /**
930 | * Counts current query
931 | *
932 | * @param string $count variable to store to 'count_all' for iTotalRecords, 'display_all' for iTotalDisplayRecords
933 | *
934 | * @return null
935 | */
936 | protected function count($count = 'count_all')
937 | {
938 |
939 | //Get columns to temp var.
940 | if ($this->query_type == 'eloquent') {
941 | $query = $this->query->getQuery();
942 | $connection = $this->query->getModel()->getConnection()->getName();
943 | } else {
944 | $query = $this->query;
945 | $connection = $query->getConnection()->getName();
946 | }
947 |
948 | // if its a normal query ( no union ) replace the select with static text to improve performance
949 | $countQuery = clone $query;
950 | if (!preg_match('/UNION/i', $countQuery->toSql())) {
951 | $countQuery->select(DB::raw("'1' as `row`"));
952 |
953 | // if query has "having" clause add select columns
954 | if ($countQuery->havings) {
955 | foreach ($countQuery->havings as $having) {
956 | if (isset($having['column'])) {
957 | $countQuery->addSelect($having['column']);
958 | } else {
959 | // search filter_columns for query string to get column name from an array key
960 | $found = false;
961 | foreach ($this->filter_columns as $column => $filter) {
962 | if ($filter['parameters'][0] == $having['sql']) {
963 | $found = $column;
964 | break;
965 | }
966 | }
967 | // then correct it if it's an alias and add to columns
968 | if ($found !== false) {
969 | foreach ($this->columns as $col) {
970 | $arr = preg_split('/ as /i', $col);
971 | if (isset($arr[1]) && $arr[1] == $found) {
972 | $found = $arr[0];
973 | break;
974 | }
975 | }
976 | $countQuery->addSelect($found);
977 | }
978 | }
979 | }
980 | }
981 | }
982 |
983 | // Clear the orders, since they are not relevant for count
984 | $countQuery->orders = null;
985 |
986 | $this->$count = DB::connection($connection)
987 | ->table(DB::raw('(' . $countQuery->toSql() . ') AS count_row_table'))
988 | ->setBindings($countQuery->getBindings())->count();
989 |
990 | }
991 |
992 | /**
993 | * Returns column name from .
994 | *
995 | * For processing select statement columns like $query->column data
996 | *
997 | * @param string $str
998 | *
999 | * @return string
1000 | */
1001 | protected function getColumnName($str)
1002 | {
1003 |
1004 | preg_match('#^(\S*?)\s+as\s+(\S*?)$#si', $str, $matches);
1005 |
1006 | if (!empty($matches)) {
1007 | return $matches[2];
1008 | } elseif (strpos($str, '.')) {
1009 | $array = explode('.', $str);
1010 |
1011 | return array_pop($array);
1012 | }
1013 |
1014 | return $str;
1015 | }
1016 |
1017 | /**
1018 | * Prints output
1019 | *
1020 | * @param bool $raw If raw will output array data, otherwise json
1021 | *
1022 | * @return array|json
1023 | */
1024 | protected function output($raw = false)
1025 | {
1026 | if (Arr::get($this->input, 'version') == '1.10') {
1027 |
1028 | $output = array(
1029 | "draw" => intval($this->input['draw']),
1030 | "recordsTotal" => $this->count_all,
1031 | "recordsFiltered" => $this->display_all,
1032 | "data" => $this->result_array_return,
1033 | );
1034 |
1035 | } else {
1036 |
1037 | $sColumns = array_merge_recursive($this->columns, $this->sColumns);
1038 |
1039 | $output = array(
1040 | "sEcho" => intval($this->input['draw']),
1041 | "iTotalRecords" => $this->count_all,
1042 | "iTotalDisplayRecords" => $this->display_all,
1043 | "aaData" => $this->result_array_return,
1044 | "sColumns" => $sColumns
1045 | );
1046 |
1047 | }
1048 |
1049 | if (Config::get('app.debug', false)) {
1050 | $output['aQueries'] = DB::getQueryLog();
1051 | }
1052 |
1053 | if ($raw) {
1054 | return $output;
1055 | } else {
1056 | return Response::json($output);
1057 | }
1058 | }
1059 |
1060 | /**
1061 | * originally PR #93
1062 | * Allows previous API calls where the methods were snake_case.
1063 | * Will convert a camelCase API call to a snake_case call.
1064 | */
1065 | public function __call($name, $arguments)
1066 | {
1067 | $name = Str::camel(Str::lower($name));
1068 | if (method_exists($this, $name)) {
1069 | return call_user_func_array(array($this, $name), $arguments);
1070 | } else {
1071 | trigger_error('Call to undefined method ' . __CLASS__ . '::' . $name . '()', E_USER_ERROR);
1072 | }
1073 | }
1074 | }
1075 |
--------------------------------------------------------------------------------
/src/Bllim/Datatables/DatatablesServiceProvider.php:
--------------------------------------------------------------------------------
1 | package('bllim/datatables', 'datatables', __DIR__);
15 | }
16 |
17 | /**
18 | * Register the service provider.
19 | *
20 | * @return void
21 | */
22 | public function register()
23 | {
24 | $this->app->singleton('bllim.datatables', function($app) {
25 | return new Datatables;
26 | });
27 | }
28 |
29 | /**
30 | * Register the package's component namespaces.
31 | *
32 | * @param string $package
33 | * @param string $namespace
34 | * @param string $path
35 | * @return void
36 | */
37 | public function package($package, $namespace = null, $path = null)
38 | {
39 |
40 | // Is it possible to register the config?
41 | if (method_exists($this->app['config'], 'package')) {
42 | $this->app['config']->package($package, $path.'/../../config', $namespace);
43 | } else {
44 | // Load the config for now..
45 | $config = $this->app['files']->getRequire($path .'/../../config/config.php');
46 | foreach($config as $key => $value){
47 | $this->app['config']->set($namespace.'::'.$key, $value);
48 | }
49 | }
50 |
51 | // Register view files
52 | $appView = $this->app['path']."/views/packages/{$package}";
53 | if ($this->app['files']->isDirectory($appView))
54 | {
55 | $this->app['view']->addNamespace($namespace, $appView);
56 | }
57 |
58 | $this->app['view']->addNamespace($namespace, $path.'/views');
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Bllim/Datatables/Facade/Datatables.php:
--------------------------------------------------------------------------------
1 | array(
6 | 'case_insensitive' => true,
7 | 'use_wildcards' => false,
8 | ),
9 |
10 | 'dataFullSupport' => false,
11 |
12 | );
13 |
--------------------------------------------------------------------------------
/src/lang/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antonioribeiro/laravel4-datatables-package/142631346d01c074248b80c8bc55fdfea255e23e/src/lang/.gitkeep
--------------------------------------------------------------------------------
/src/migrations/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antonioribeiro/laravel4-datatables-package/142631346d01c074248b80c8bc55fdfea255e23e/src/migrations/.gitkeep
--------------------------------------------------------------------------------
/src/views/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antonioribeiro/laravel4-datatables-package/142631346d01c074248b80c8bc55fdfea255e23e/src/views/.gitkeep
--------------------------------------------------------------------------------
/tests/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antonioribeiro/laravel4-datatables-package/142631346d01c074248b80c8bc55fdfea255e23e/tests/.gitkeep
--------------------------------------------------------------------------------
/tests/Datatables.php:
--------------------------------------------------------------------------------
1 | increments('id');
32 | $table->string('name');
33 | });
34 | Demo::insert(array('name'=>'demo datatables'));
35 | }
36 |
37 | public function tearDown()
38 | {
39 | Schema::dropIfExists('demos');
40 | }
41 |
42 | public function test_demo_count()
43 | {
44 | $this->assertEquals(1, Demo::count());
45 | }
46 |
47 | public function test_datatables_make_function()
48 | {
49 | $demo = DB::table('demos')->select('id','name');
50 | $output = Datatables::of($demo)->make();
51 | $this->assertInstanceOf('Illuminate\Http\JsonResponse', $output);
52 | }
53 |
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/tests/bootstrap/autoload.php:
--------------------------------------------------------------------------------
1 | __DIR__.'/../../vendor/laravel/laravel/app',
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Public Path
21 | |--------------------------------------------------------------------------
22 | |
23 | | The public path contains the assets for your web application, such as
24 | | your JavaScript and CSS files, and also contains the primary entry
25 | | point for web requests into these applications from the outside.
26 | |
27 | */
28 |
29 | 'public' => __DIR__.'/../../vendor/laravel/laravel/public',
30 |
31 | /*
32 | |--------------------------------------------------------------------------
33 | | Base Path
34 | |--------------------------------------------------------------------------
35 | |
36 | | The base path is the root of the Laravel installation. Most likely you
37 | | will not need to change this value. But, if for some wild reason it
38 | | is necessary you will do so here, just proceed with some caution.
39 | |
40 | */
41 |
42 | 'base' => __DIR__.'/../..',
43 |
44 | /*
45 | |--------------------------------------------------------------------------
46 | | Storage Path
47 | |--------------------------------------------------------------------------
48 | |
49 | | The storage path is used by Laravel to store cached Blade views, logs
50 | | and other pieces of information. You may modify the path here when
51 | | you want to change the location of this directory for your apps.
52 | |
53 | */
54 |
55 | 'storage' => __DIR__.'/../../vendor/laravel/laravel/app/storage',
56 |
57 | );
58 |
--------------------------------------------------------------------------------
/tests/bootstrap/start.php:
--------------------------------------------------------------------------------
1 | detectEnvironment(array(
28 |
29 | 'local' => array('your-machine-name'),
30 |
31 | ));
32 |
33 | /*
34 | |--------------------------------------------------------------------------
35 | | Bind Paths
36 | |--------------------------------------------------------------------------
37 | |
38 | | Here we are binding the paths configured in paths.php to the app. You
39 | | should not be changing these here. If you need to change these you
40 | | may do so within the paths.php file and they will be bound here.
41 | |
42 | */
43 |
44 | $app->bindInstallPaths(require __DIR__.'/paths.php');
45 |
46 | /*
47 | |--------------------------------------------------------------------------
48 | | Load The Application
49 | |--------------------------------------------------------------------------
50 | |
51 | | Here we will load this Illuminate application. We will keep this in a
52 | | separate location so we can isolate the creation of an application
53 | | from the actual running of the application with a given request.
54 | |
55 | */
56 |
57 | $framework = $app['path.base'].'/vendor/laravel/framework/src';
58 |
59 | require $framework.'/Illuminate/Foundation/start.php';
60 |
61 | /*
62 | |--------------------------------------------------------------------------
63 | | Return The Application
64 | |--------------------------------------------------------------------------
65 | |
66 | | This script returns the application instance. The instance is given to
67 | | the calling script so we can separate the building of the instances
68 | | from the actual running of the application and sending responses.
69 | |
70 | */
71 |
72 | return $app;
73 |
--------------------------------------------------------------------------------