This commit is contained in:
Manish Verma
2016-12-13 18:18:25 +05:30
parent fc98add11c
commit 2d8e640e9b
2314 changed files with 97798 additions and 75664 deletions

View File

@@ -0,0 +1,488 @@
## Datatables Package for Laravel 4|5
[![Latest Stable Version](https://poser.pugx.org/yajra/laravel-datatables-oracle/v/stable.png)](https://packagist.org/packages/yajra/laravel-datatables-oracle)
[![Total Downloads](https://poser.pugx.org/yajra/laravel-datatables-oracle/downloads.png)](https://packagist.org/packages/yajra/laravel-datatables-oracle)
[![Build Status](https://travis-ci.org/yajra/laravel-datatables.png?branch=master)](https://travis-ci.org/yajra/laravel-datatables)
[![Latest Unstable Version](https://poser.pugx.org/yajra/laravel-datatables-oracle/v/unstable.svg)](https://packagist.org/packages/yajra/laravel-datatables-oracle)
[![License](https://poser.pugx.org/yajra/laravel-datatables-oracle/license.svg)](https://packagist.org/packages/yajra/laravel-datatables-oracle)
##Change Log
### v6.17.0 - 2016-08-10
- Add setter and getter for table attributes.
- PR #688, credits to @ssipos90.
### v6.16.1 - 2016-08-03
- Add orderColumns api helper. PR #679
- Fix double percent sign in query. PR #678
### v6.16.0 - 2016-07-19
- Allow global search to work with custom filter callback. #644
- Transformer instance is kept, otherwise new instance is created. #649
- Remove unused import and fix cs.
### v6.15.2 - 2016-07-14
- Fix the issue where a record is being deleted by LDT when column name used is delete.
- Fix helper doc blocks.
- Fix #565 and #640.
- Allow travis-ci failure on HHVM. Mostly due to execution timestamp.
### v6.15.1 - 2016-07-13
- Decouple fractal for better integration. #636
- Fractal manager instance can be accessed via app('datatables.fractal').
### v6.15.0 - 2016-07-05
- Add support for snappy pdf via config.
- Add laravel-snappy on suggested packages.
- Add table stripes style.
- Fix printing template when value is an array.
- Fix issue when exporting pdf and data can't be decoded.
- ErrorException in DataTransformer.php line 67: strip_tags() expects parameter 1 to be string, array given
### v6.14.0 - 2016-07-01
- Add model option to dataTable command. #620
- Make generator namespace configurable.
- Add filename in generator with timestamps.
### v6.13.1 - 2016-06-23
- Add option to manually set the total records.
-New method: ->setTotalRecords(int total)
- Addresses count queries performance issue like #578.
### v6.13.0 - 2016-06-23
- Add Auto-Index Column. #597
- Add method 'addIndexColumn()' to compliment PR#597.
- Note: addIndexColumn can be used without the builder.
- Add config file descriptions for reference.
### v6.12.0 - 2016-06-23
- Add support for ajax data function. #613
- Update dataTable service query return docblock and stub.
### v6.11.5 - 2016-06-01
- Fix HasOne relation which uses different methods to get foreign and other key. #585
###v6.11.4 - 2016-05-30
- Remove media screen to fix styles when printing. #583
###v6.11.3 - 2016-05-20
- Add export button collection. #568
- Fix default print preview view path. #569
###v6.11.2 - 2016-05-18
- Add CAST for Firebird #552.
###v6.11.1 - 2016-05-11
- Use Str class helper instead of strlen for better unicode support.
- Change method from private to protected as requested on #544.
###v6.11.0 - 2016-04-30
- Patch phpdoc to fix #531. PR #534
- Fix eager loading ordering for belongsToMany relationship. Fix #461, PR #490
- Add support for responsive extension. Fix #526, PR #533
- Add option to create a table footer from builder defined via column def. PR #471
###v6.10.1 - 2016-03-22
- Fix eager loading column search. PR #469.
- Fix issue #443.
###v6.10.0 - 2016-03-19
- Add feature to enable/disable smart search via config or during runtime. Fix #423
- See PR #452 for details.
###v6.9.4 - 2016-03-18
- Use full namespace in app() helper.
- PR #465, credits to @ligne13.
###v6.9.3 - 2016-03-17
- Adds an option to pass parameters to column render.
- Allows passing instance of Column into Builder columns.
- Fix security issue as reported in #460.
- Credits to @vladkucherov for this changes.
###v6.9.2 - 2016-03-16
- Pull-up isOracleSQL and fix condition using oci8.
###v6.9.1 - 2016-03-11
- Add default array value when getting columns. Fix #448
###v6.9.0 - 2016-03-11
- Re-implement facade.
- Add blacklist and whitelist feature.
- Fix string casting for object values.
- Add missing doc block for getSearchKeyword.
- Fix eloquent engine missing parent constructor.
- Add/Update class doc blocks.
###v6.8.0 - 2016-03-11
- Added Closure support for filterColumn method.
- PR #440. Credits to @codewizz.
###v6.7.3 - 2016-03-02
- Fix eager load multiple column sorting where other columns are being ignored when join statement already exists.
- Refactor redundant else order by statement.
- Call eager loads only when required when filtering and ordering.
- Extract eager loaded column join statement handler.
###v6.7.2 - 2016-02-28
- Fix collection engine sorting and sorting function.
- Fix #413 and #415.
###v6.7.1 - 2016-02-26
- Fix multiple column sorting when using eager loaded models. Fix #410
###v6.7.0 - 2016-02-26
- Add support for sorting on eager loaded models.
- PR #409 - Credits to @ikerasLT.
###v6.6.1 - 2016-02-20
- Fix eager loading search (SQLSTATE[21000]: Cardinality violation:). Issue #403.
###v6.6.0 - 2016-02-20
- Add totalCount on contact and remove excess new line.
- Remove unnecessary abstract function on BaseEngine since we have a contract.
- Remove engine implementation of contract since BaseEngine already requires it.
- Improve column name detection for filtering and sorting.
- Dynamically determine if oracle depending on connection used.
- Automatic detection of primary key when using Eloquent engine.
- Use primary key when column name could not be resolve.
- Update DataTable service doc blocks and refactor render method.
###v6.5.1 - 2016-02-19
- Fix ordering column name detection. Issue #339.
- Refactor Builder parameterize method.
###v6.5.0 - 2016-02-18
- Add support for DataTables valid callbacks.
- Fix issue #387 & #401.
###v6.4.5 - 2016-02-18
- Allow edit columns for nested arrays. PR #399 - credits to @ramilexe
- Fix flag for case insensitive search. PR #400 - credits to @ansient
###v6.4.4 - 2016-02-13
- Fix filtering in nested columns of Collections. PR #392
###v6.4.3 - 2016-02-11
- Random cs and doc block fix.
- Code refactoring to reduce complexity.
###v6.4.2 - 2016-02-11
- Change how regex code is generated after a column search. #358
- Fix addColumn fails when order falls at end of array #386
###v6.4.1 - 2016-02-10
- Fix nested eager loaded relations and column name.
###v6.4.0 - 2016-02-10
- Add feature to support global search on eager loaded models.
- PR #381. Credits to @ikerasLT.
- Fix implementation conflicts when using builder and join statements.
- Fix cs and doc blocks.
###v6.3.2 - 2016-02-04
- Add order by and group by on count sql optimization exceptions.
- Date will now be added on each released version using Y-m-d format.
###v6.3.1
- Fix artisan datatables:make service stub.
###v6.3.0
- Add option to override default ordering via `->order(\Closure $callback)` method.
- Add editor config.
- Add some new features docs.
- Remove Laravel 4.2 documentation on 6.0 branch.
###v6.2.4
- Add git attributes.
###v6.2.3
- Add setter/getter for filename.
- Add html_entity_decode when exporting file.
- Decode column title when exporting.
###v6.2.2
- Extract data transformation task to own class.
- Refactor duplicate response mapping code.
- Increase scrutinizer score.
###v6.2.1
- Fix data when exporting with html tags.
- Add filename method in stub.
- Fix some doc blocks.
- Scrutinizer refactoring.
###v6.2.0
- Enhance printing function to match what is displayed in UI.
- Enhance export function to match what is displayed in UI.
- Enhance datatables service stub.
- Address issue #310.
- Add option to set column as exportable and/or printable.
- Action and checkbox column is not exportable but printable by default.
###v6.1.3
- Fix logical bug with totalRecords and filteredRecords. Fix #333
###v6.1.2
- Fix possible conflict with Laravel helpers.php file auto-loading. Fix #330.
- Update dataTable service class stub.
###v6.1.1
- Fix ordering when using basic array response. Fix #322.
###v6.1.0
- Add support for Lumen.
- Fixes #317, #318.
###v6.0.0 - DataTable Service implementation.
- Provides DataTable Service.
- Provides artisan command for creating a service. php artisan datatables:make UsersDataTable
- Provides artisan command for creating a DataTable scope. php artisan datatables:scope ActiveUserScope
- Provides built-in support for server-side buttons. (Formerly TableTools).
- Available buttons are csv, excel, pdf, print.
- Built-in support for exporting to CSV, EXCEL and PDF using Laravel-Excel.
- Built-in printer friendly view or create your own by overriding printPreview() method.
- Change of namespace from yajra\Datatables to Yajra\Datatables.
- Deprecated of() method when using DataTable service.
- Automatic registration of required 3rd party providers.
- [Laravel Excel](https://github.com/Maatwebsite/Laravel-Excel)
- [Laravel Collective HTML & Forms](https://github.com/LaravelCollective/html)
- Automatic registration of Datatables facade.
- HTML Builder with javascript from template. #298 - Credits to @vladkucherov.
- HTML Builder column render now accepts a string, view or closure. #300 - Credits to @vladkucherov
- Add resource on json response by using `->with('key', 'value')` method. #277
###v5.12.5
- Get order column name from the request. Fix #307.
###v5.12.4
- Fix searching when aliasing a column. Fix #274.
###v5.12.3
- Remove checking of columns - name index and let setupColumnName method to identify the proper column name.
###v5.12.2
- Fix double prefix when using join queries. Fix #272, #273
###v5.12.1
- Fix support for PHP5.4.
###v5.12.0
- Added support for Fractal Serializer.
- Added config for default serializer.
- Note: Should be used along with setTransformer method.
- Usage:
return Datatables::of($model)
->setTransformer(ModelTransformer::class)
->setSerializer(ModelSerializer::class)
->make(true);
###v5.11.14
- Sort by a multi-line 'select as' query. PR #245
###v5.11.13
- Allow fractal v0.12 and up. Fix #237.
###v5.11.12
- Use connection grammar to wrap columns and table name.
###v5.11.11
- Parse includes in fractal. Fix #225.
###v5.11.10
- CollectionEngine: fix sorting of relation columns by using seralize (like filtering). PR #197.
###v5.11.9
- Add fix for QueryBuilder: ORDER BY *. PR #194.
###v5.11.8
- Skip search, order and pagination on empty records.
- Fix #149 and #176 empty collection error when using make(false).
- Fix credits to @gabrielwelsche.
###v5.11.7
- Fix escaping of row when using eager loading. Fix #164
- Add support for escaping rows using array dot notation when declaring escapeColumns.
Example: `->escapeColumns(['name', 'post.title'])`
###v5.11.6
- Refactor eloquent and query builder engine duplicate codes.
###v5.11.5
- Fix issues on database prefix. #161 and #162
- Fix database prefix value when using Eloquent.
###v5.11.4
- Fix Undefined offset issue when using addColumn.
- Credits to @openvast for PR #158
###v5.11.3
- Pass object or array to transformer. PR #155
###v5.11.2
- Add support for regular expressions search on column.
###v5.11.1
- Collection engine enhancement.
- Add support for filtering compound key PR #146.
- Add support for ordering using compound key.
###v5.11.0
- Add support for rendering view directly on addColumn and editColumn.
###v5.10.0
- Add LaravelDataTables on js window namespace. Issue #129. Credits to @greabock.
###v5.9.2
- Fix possible error when rendering table and overriding the attributes.
- Merge DT parameters.
###v5.9.1
- Fix default ajax value causing js data null error.
###v5.9.0
- Added escapeColumns feature to escape the values.
- Addresses XSS filtering issue #128.
###v5.8.6
- Fix DT_Row options when returning a flatten array response.
- Fix PR #126.
###v5.8.5
- Revert try-catch when compiling blade.
- Fix html builder unit test.
###v5.8.4
- Fix html builder merging of column attributes.
###v5.8.3
- Added space when setting html builder table attributes.
- Set a default data value when adding a column.
- Removed unnecessary slash when getting html builder.
- Added html builder unit test.
- Improved test coverage.
###v5.8.2
- Fix count when using DISTINCT query. Fix #125
###v5.8.1
- Fix compatiblity with PHP 5.4.
###v5.8.0
- Enhanced html builder class.
- Added function to load html builder `columns` via mixed array.
- Automatic resolution of qualified title based on field name.
- Overriding of column attributes.
- Added html builder and request object getter from main Datatables class.
- Added more unit tests.
###v5.7.0
- Added orderColumn feature.
###v5.6.1
- Make BaseEngine $request property public.
- Fix global searching when search value is zero (0).
- Refactor methods from v5.6.0.
###v5.6.0
- Re-implement filterColumn function with special variable $1.
- Fix filterColumn not getting included on OR statements within global search.
- Fix #115.
###v5.5.11
- Fix ordering for when using column alias and make(false). Fix #103.
###v5.5.10
- Fix casting specific to stdClass only. Fix #114.
###v5.5.9
- Fix ordering of collection when data is stdClass.
###v5.5.8
- Fix issue when converting object to array. Fix #108.
###v5.5.7
- Fix and enhance support when passing object variables using blade templating approach.
- Random code clean-up.
###v5.5.6
- Fix eager loading of hasOne and hasMany relationships. Issue #105.
###v5.5.5
- Fix collection engine sorting when columns is not defined
###v5.5.4
- Fix support for collection of objects
###v5.5.3
- Fix total filtered records count when overriding global search.
- Fix implementation of PR #95 on Collection Engine.
###v5.5.2
- Fix database driver detection on Eloquent Engine.
###v5.5.1
- Fix missing import of Helper class.
###v5.5.0
- Refactor classes to improve code quality.
- Implemented PR #95.
###v5.4.4
- Added column wrapper for SQLITE.
###v5.4.3
- Added column wrapper for Postgres. Bugfix #82.
###v5.4.2
- Throws Exception when using DataTable's legacy code.
- Fixed CS - PSR2.
###v5.4.1
- Fixed Builder generateScript method.
###v5.4
- Added Html Builder.
- Added magic methods to call enginges without the "using" word.
- Minor Bugfixes.
###v5.3
- Added scrutinizer.
- Code refactor/cleanup based on scrutinizers suggestions.
- Bugfix #75.
###v5.2
- Datatables can now be used via Laravel IOC container `app('datatables')`.
- Datatables Engine can now be used directly along with Laravel IOC.
- Available Engines:
- Query Builder Engine. `app('datatables')->usingQueryBuilder($builder)->make()`.
- Eloquent Engine. `app('datatables')->usingEloquent($model)->make()`.
- Collection Engine. `app('datatables')->usingCollection($collection)->make()`.
- Datatables is now more testable and works with https://github.com/laracasts/integrated.
- Bugfix #56.
###v5.1
- Added filterColumn function to override default global search in each column.
- Datatables class extending Query Builder's functionality along with global search.
- Restore queries on result when app is in debug mode.
- Added input on result when app is in debug mode.
- Force enable query log when app is in debug mode.
- Convert string search from preg_match to Str::contains to improve performance.
- Added support for having clause queries.
- Added support for `league/fractal` for transforming data API output.
###v5.0
- Strictly for Laravel 5++.
- Drop support for DT1.9 and below.
- Strict implmentation of DT1.10 script pattern.
- Added support for Collection as data source.
###v4.3.x
- Stable version for Laravel 5 with support for DT1.9.
- Collection Engine not available.
###v3.6.x
- Stable version for Laravel 4.2.
###v2.x
- Stable version for Laravel 4.0 and 4.1

View File

@@ -0,0 +1,22 @@
(The MIT License)
Copyright (c) 2013-2016 Arjay Angeles <aqangeles@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,87 @@
# jQuery DataTables API for Laravel 4|5
[![Join the chat at https://gitter.im/yajra/laravel-datatables](https://badges.gitter.im/yajra/laravel-datatables.svg)](https://gitter.im/yajra/laravel-datatables?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Laravel 4.2|5.0|5.1|5.2](https://img.shields.io/badge/Laravel-4.2|5.0|5.1|5.2-orange.svg)](http://laravel.com)
[![Latest Stable Version](https://poser.pugx.org/yajra/laravel-datatables-oracle/v/stable)](https://packagist.org/packages/yajra/laravel-datatables-oracle)
[![Build Status](https://travis-ci.org/yajra/laravel-datatables.svg?branch=master)](https://travis-ci.org/yajra/laravel-datatables)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/yajra/laravel-datatables/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/yajra/laravel-datatables/?branch=master)
[![Total Downloads](https://poser.pugx.org/yajra/laravel-datatables-oracle/downloads)](https://packagist.org/packages/yajra/laravel-datatables-oracle)
[![License](https://poser.pugx.org/yajra/laravel-datatables-oracle/license)](https://packagist.org/packages/yajra/laravel-datatables-oracle)
This package is created to handle [server-side](https://www.datatables.net/manual/server-side) works of [DataTables](http://datatables.net) jQuery Plugin via [AJAX option](https://datatables.net/reference/option/ajax) by using Eloquent ORM, Fluent Query Builder or Collection.
```php
use Yajra\Datatables\Facades\Datatables;
// Using Eloquent
return Datatables::eloquent(User::query())->make(true);
// Using Query Builder
return Datatables::queryBuilder(DB::table('users'))->make(true);
// Using Collection
return Datatables::collection(User::all())->make(true);
// Using the Engine Factory
return Datatables::of(User::query())->make(true);
return Datatables::of(DB::table('users'))->make(true);
return Datatables::of(User::all())->make(true);
```
## Requirements
- [PHP 5.5.9 or later](http://php.net/)
- [Laravel 5.0 or later](https://github.com/laravel/framework)
- [DataTables jQuery Plugin](http://datatables.net/) **Version 1.10.xx**
## Documentations
- You will find user friendly and updated documentation in the wiki here: [Laravel Datatables Wiki](https://github.com/yajra/laravel-datatables/wiki)
- You will find the API Documentation here: [Laravel Datatables API](http://yajra.github.io/laravel-datatables/api/)
- [Demo Application](http://datatables.yajrabox.com) is available for artisan's reference.
## Quick Installation
`composer require yajra/laravel-datatables-oracle:~6.0`
#### Service Provider
`Yajra\Datatables\DatatablesServiceProvider::class`
#### Facade
`Datatables` facade is automatically registered as an alias for `Yajra\Datatables\Facades\Datatables` class.
#### Configuration and Assets
`$ php artisan vendor:publish --tag=datatables`
And that's it! Start building out some awesome DataTables!
## Upgrading from v5.x to v6.x
- Change all occurrences of `yajra\Datatables` to `Yajra\Datatables`. (Use Sublime's find and replace all for faster update).
- Remove `Datatables` facade registration.
- Temporarily comment out `Yajra\Datatables\DatatablesServiceProvider`.
- Update package version on your composer.json and use `yajra/laravel-datatables-oracle: ~6.0`
- Uncomment the provider `Yajra\Datatables\DatatablesServiceProvider`.
## Debugging Mode
To enable debugging mode, just set `APP_DEBUG=true` and the package will include the queries and inputs used when processing the table.
**IMPORTANT:** Please make sure that APP_DEBUG is set to false when your app is on production.
## Contributing
Please see [CONTRIBUTING](https://github.com/yajra/laravel-datatables/blob/master/.github/CONTRIBUTING.md) for details.
## Security
If you discover any security related issues, please email [aqangeles@gmail.com](mailto:aqangeles@gmail.com) instead of using the issue tracker.
## Credits
- [Arjay Angeles](https://github.com/yajra)
- [bllim/laravel4-datatables-package](https://github.com/bllim/laravel4-datatables-package)
- [All Contributors](https://github.com/yajra/laravel-datatables/graphs/contributors)
## License
The MIT License (MIT). Please see [License File](https://github.com/yajra/laravel-datatables/blob/master/LICENSE.md) for more information.
## Buy me a beer
<a href='https://pledgie.com/campaigns/29515'><img alt='Click here to lend your support to: Laravel Datatables and make a donation at pledgie.com !' src='https://pledgie.com/campaigns/29515.png?skin_name=chrome' border='0' ></a>

View File

@@ -0,0 +1,37 @@
{
"name": "yajra/laravel-datatables-oracle",
"description": "jQuery DataTables API for Laravel 4|5",
"keywords" : ["laravel4","laravel5","laravel","datatables","datatable","datatables jquery plugin"],
"license": "MIT",
"authors": [
{
"name": "Arjay Angeles",
"email": "aqangeles@gmail.com"
}
],
"require": {
"php": ">=5.5.9",
"illuminate/support": "~5.0",
"illuminate/database": "~5.0",
"illuminate/view": "~5.0",
"illuminate/http": "~5.0",
"illuminate/filesystem": "~5.0",
"league/fractal": "~0.12",
"laravelcollective/html": "~5.0",
"maatwebsite/excel": "^2.0",
"dompdf/dompdf": "^0.6.1"
},
"require-dev": {
"mockery/mockery": "~0.9",
"phpunit/phpunit": "~4.0"
},
"suggest": {
"barryvdh/laravel-snappy": "Allows exporting of dataTable to PDF using the print view."
},
"autoload": {
"psr-4": {
"Yajra\\Datatables\\": "src/"
}
},
"minimum-stability": "stable"
}

View File

@@ -0,0 +1,40 @@
<?php
namespace Yajra\Datatables\Contracts;
/**
* Interface DataTableButtonsContract.
*
* @package Yajra\Datatables\Contracts
* @author Arjay Angeles <aqangeles@gmail.com>
*/
interface DataTableButtonsContract
{
/**
* Export to excel file.
*
* @return mixed
*/
public function excel();
/**
* Export to CSV file.
*
* @return mixed
*/
public function csv();
/**
* Export to PDF file.
*
* @return mixed
*/
public function pdf();
/**
* Display printer friendly view.
*
* @return mixed
*/
public function printPreview();
}

View File

@@ -0,0 +1,47 @@
<?php
namespace Yajra\Datatables\Contracts;
/**
* Interface DataTableContract
*
* @package Yajra\Datatables\Contracts
* @author Arjay Angeles <aqangeles@gmail.com>
*/
interface DataTableContract
{
/**
* Render view.
*
* @param $view
* @param array $data
* @param array $mergeData
* @return \Illuminate\Http\JsonResponse|\Illuminate\View\View
*/
public function render($view, $data = [], $mergeData = []);
/**
* @return \Illuminate\Http\JsonResponse
*/
public function ajax();
/**
* @return \Yajra\Datatables\Html\Builder
*/
public function html();
/**
* @return \Yajra\Datatables\Html\Builder
*/
public function builder();
/**
* @return \Yajra\Datatables\Request
*/
public function request();
/**
* @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder|\Illuminate\Support\Collection
*/
public function query();
}

View File

@@ -0,0 +1,80 @@
<?php
namespace Yajra\Datatables\Contracts;
/**
* Interface DataTableEngineContract
*
* @package Yajra\Datatables\Contracts
* @author Arjay Angeles <aqangeles@gmail.com>
*/
interface DataTableEngineContract
{
/**
* Get results.
*
* @return mixed
*/
public function results();
/**
* Count results.
*
* @return integer
*/
public function count();
/**
* Count total items.
*
* @return integer
*/
public function totalCount();
/**
* Set auto filter off and run your own filter.
* Overrides global search.
*
* @param \Closure $callback
* @param bool $globalSearch
* @return $this
*/
public function filter(\Closure $callback, $globalSearch = false);
/**
* Perform global search.
*
* @return void
*/
public function filtering();
/**
* Perform column search.
*
* @return void
*/
public function columnSearch();
/**
* Perform pagination.
*
* @return void
*/
public function paging();
/**
* Perform sorting of columns.
*
* @return void
*/
public function ordering();
/**
* Organizes works.
*
* @param bool $mDataSupport
* @param bool $orderFirst
* @return \Illuminate\Http\JsonResponse
*/
public function make($mDataSupport = false, $orderFirst = false);
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Yajra\Datatables\Contracts;
/**
* Interface DataTableScopeContract.
*
* @package Yajra\Datatables\Contracts
* @author Arjay Angeles <aqangeles@gmail.com>
*/
interface DataTableScopeContract
{
/**
* Apply a query scope.
*
* @param \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $query
* @return mixed
*/
public function apply($query);
}

View File

@@ -0,0 +1,136 @@
<?php
namespace Yajra\Datatables;
use Illuminate\Database\Query\Builder as QueryBuilder;
use Illuminate\Support\Collection;
use Yajra\Datatables\Engines\CollectionEngine;
use Yajra\Datatables\Engines\EloquentEngine;
use Yajra\Datatables\Engines\QueryBuilderEngine;
/**
* Class Datatables.
*
* @package Yajra\Datatables
* @method EloquentEngine eloquent($builder)
* @method CollectionEngine collection(Collection $builder)
* @method QueryBuilderEngine queryBuilder(QueryBuilder $builder)
* @author Arjay Angeles <aqangeles@gmail.com>
*/
class Datatables
{
/**
* Datatables request object.
*
* @var \Yajra\Datatables\Request
*/
public $request;
/**
* Datatables builder.
*
* @var mixed
*/
public $builder;
/**
* Datatables constructor.
*
* @param \Yajra\Datatables\Request $request
*/
public function __construct(Request $request)
{
$this->request = $request->request->count() ? $request : Request::capture();
}
/**
* Gets query and returns instance of class.
*
* @param mixed $builder
* @return mixed
*/
public static function of($builder)
{
$datatables = app('Yajra\Datatables\Datatables');
$datatables->builder = $builder;
if ($builder instanceof QueryBuilder) {
$ins = $datatables->usingQueryBuilder($builder);
} else {
$ins = $builder instanceof Collection ? $datatables->usingCollection($builder) : $datatables->usingEloquent($builder);
}
return $ins;
}
/**
* Datatables using Query Builder.
*
* @param \Illuminate\Database\Query\Builder $builder
* @return \Yajra\Datatables\Engines\QueryBuilderEngine
*/
public function usingQueryBuilder(QueryBuilder $builder)
{
return new QueryBuilderEngine($builder, $this->request);
}
/**
* Datatables using Collection.
*
* @param \Illuminate\Support\Collection $builder
* @return \Yajra\Datatables\Engines\CollectionEngine
*/
public function usingCollection(Collection $builder)
{
return new CollectionEngine($builder, $this->request);
}
/**
* Datatables using Eloquent.
*
* @param mixed $builder
* @return \Yajra\Datatables\Engines\EloquentEngine
*/
public function usingEloquent($builder)
{
return new EloquentEngine($builder, $this->request);
}
/**
* Allows api call without the "using" word.
*
* @param string $name
* @param mixed $arguments
* @return $this|mixed
*/
public function __call($name, $arguments)
{
$name = 'using' . ucfirst($name);
if (method_exists($this, $name)) {
return call_user_func_array([$this, $name], $arguments);
}
return trigger_error('Call to undefined method ' . __CLASS__ . '::' . $name . '()', E_USER_ERROR);
}
/**
* Get html builder class.
*
* @return \Yajra\Datatables\Html\Builder
*/
public function getHtmlBuilder()
{
return app('Yajra\Datatables\Html\Builder');
}
/**
* Get request object.
*
* @return \Yajra\Datatables\Request|static
*/
public function getRequest()
{
return $this->request;
}
}

View File

@@ -0,0 +1,144 @@
<?php
namespace Yajra\Datatables;
use Collective\Html\HtmlServiceProvider;
use Illuminate\Support\ServiceProvider;
use League\Fractal\Manager;
use League\Fractal\Serializer\DataArraySerializer;
use Maatwebsite\Excel\ExcelServiceProvider;
use Yajra\Datatables\Generators\DataTablesMakeCommand;
use Yajra\Datatables\Generators\DataTablesScopeCommand;
/**
* Class DatatablesServiceProvider.
*
* @package Yajra\Datatables
* @author Arjay Angeles <aqangeles@gmail.com>
*/
class DatatablesServiceProvider extends ServiceProvider
{
/**
* Indicates if loading of the provider is deferred.
*
* @var bool
*/
protected $defer = false;
/**
* Bootstrap the application events.
*
* @return void
*/
public function boot()
{
$this->loadViewsFrom(__DIR__ . '/resources/views', 'datatables');
$this->publishAssets();
$this->registerCommands();
}
/**
* Publish datatables assets.
*/
protected function publishAssets()
{
$this->publishes([
__DIR__ . '/config/config.php' => config_path('datatables.php'),
], 'datatables');
$this->publishes([
__DIR__ . '/resources/assets/buttons.server-side.js' => public_path('vendor/datatables/buttons.server-side.js'),
], 'datatables');
$this->publishes([
__DIR__ . '/resources/views' => base_path('/resources/views/vendor/datatables'),
], 'datatables');
}
/**
* Register datatables commands.
*/
protected function registerCommands()
{
$this->commands(DataTablesMakeCommand::class);
$this->commands(DataTablesScopeCommand::class);
}
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
if ($this->isLumen()) {
require_once 'fallback.php';
}
$this->registerRequiredProviders();
$this->app->singleton('datatables', function () {
return new Datatables($this->app->make(Request::class));
});
$this->app->singleton('datatables.fractal', function () {
$fractal = new Manager;
$config = $this->app['config'];
$request = $this->app['request'];
$includesKey = $config->get('datatables.fractal.includes', 'include');
if ($request->get($includesKey)) {
$fractal->parseIncludes($request->get($includesKey));
}
$serializer = $config->get('datatables.fractal.serializer', DataArraySerializer::class);
$fractal->setSerializer(new $serializer);
return $fractal;
});
$this->registerAliases();
}
/**
* Check if app uses Lumen.
*
* @return bool
*/
protected function isLumen()
{
return str_contains($this->app->version(), 'Lumen');
}
/**
* Register 3rd party providers.
*/
protected function registerRequiredProviders()
{
$this->app->register(HtmlServiceProvider::class);
$this->app->register(ExcelServiceProvider::class);
}
/**
* Create aliases for the dependency.
*/
protected function registerAliases()
{
if (class_exists('Illuminate\Foundation\AliasLoader')) {
$loader = \Illuminate\Foundation\AliasLoader::getInstance();
$loader->alias('Datatables', \Yajra\Datatables\Facades\Datatables::class);
}
}
/**
* Get the services provided by the provider.
*
* @return string[]
*/
public function provides()
{
return ['datatables'];
}
}

View File

@@ -0,0 +1,967 @@
<?php
namespace Yajra\Datatables\Engines;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Str;
use League\Fractal\Resource\Collection;
use Yajra\Datatables\Contracts\DataTableEngineContract;
use Yajra\Datatables\Helper;
use Yajra\Datatables\Processors\DataProcessor;
/**
* Class BaseEngine.
*
* @package Yajra\Datatables\Engines
* @author Arjay Angeles <aqangeles@gmail.com>
*/
abstract class BaseEngine implements DataTableEngineContract
{
/**
* Datatables Request object.
*
* @var \Yajra\Datatables\Request
*/
public $request;
/**
* Database connection used.
*
* @var \Illuminate\Database\Connection
*/
protected $connection;
/**
* Builder object.
*
* @var \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder
*/
protected $query;
/**
* Query builder object.
*
* @var \Illuminate\Database\Query\Builder
*/
protected $builder;
/**
* Array of result columns/fields.
*
* @var array
*/
protected $columns = [];
/**
* DT columns definitions container (add/edit/remove/filter/order/escape).
*
* @var array
*/
protected $columnDef = [
'index' => false,
'append' => [],
'edit' => [],
'excess' => ['rn', 'row_num'],
'filter' => [],
'order' => [],
'escape' => [],
'blacklist' => ['password', 'remember_token'],
'whitelist' => '*',
];
/**
* Query type.
*
* @var string
*/
protected $query_type;
/**
* Extra/Added columns.
*
* @var array
*/
protected $extraColumns = [];
/**
* Total records.
*
* @var int
*/
protected $totalRecords = 0;
/**
* Total filtered records.
*
* @var int
*/
protected $filteredRecords = 0;
/**
* Auto-filter flag.
*
* @var bool
*/
protected $autoFilter = true;
/**
* Callback to override global search.
*
* @var \Closure
*/
protected $filterCallback;
/**
* Parameters to passed on filterCallback.
*
* @var mixed
*/
protected $filterCallbackParameters;
/**
* DT row templates container.
*
* @var array
*/
protected $templates = [
'DT_RowId' => '',
'DT_RowClass' => '',
'DT_RowData' => [],
'DT_RowAttr' => [],
];
/**
* Output transformer.
*
* @var \League\Fractal\TransformerAbstract
*/
protected $transformer = null;
/**
* Database prefix
*
* @var string
*/
protected $prefix;
/**
* Database driver used.
*
* @var string
*/
protected $database;
/**
* [internal] Track if any filter was applied for at least one column
*
* @var boolean
*/
protected $isFilterApplied = false;
/**
* Fractal serializer class.
*
* @var string|null
*/
protected $serializer = null;
/**
* Custom ordering callback.
*
* @var \Closure
*/
protected $orderCallback;
/**
* Array of data to append on json response.
*
* @var array
*/
private $appends = [];
/**
* Setup search keyword.
*
* @param string $value
* @return string
*/
public function setupKeyword($value)
{
if ($this->isSmartSearch()) {
$keyword = '%' . $value . '%';
if ($this->isWildcard()) {
$keyword = $this->wildcardLikeString($value);
}
// remove escaping slash added on js script request
$keyword = str_replace('\\', '%', $keyword);
return $keyword;
}
return $value;
}
/**
* Check if DataTables uses smart search.
*
* @return bool
*/
protected function isSmartSearch()
{
return Config::get('datatables.search.smart', true);
}
/**
* Get config use wild card status.
*
* @return bool
*/
public function isWildcard()
{
return Config::get('datatables.search.use_wildcards', false);
}
/**
* Adds % wildcards to the given string.
*
* @param string $str
* @param bool $lowercase
* @return string
*/
public function wildcardLikeString($str, $lowercase = true)
{
$wild = '%';
$length = Str::length($str);
if ($length) {
for ($i = 0; $i < $length; $i++) {
$wild .= $str[$i] . '%';
}
}
if ($lowercase) {
$wild = Str::lower($wild);
}
return $wild;
}
/**
* Will prefix column if needed.
*
* @param string $column
* @return string
*/
public function prefixColumn($column)
{
$table_names = $this->tableNames();
if (count(
array_filter($table_names, function ($value) use (&$column) {
return strpos($column, $value . '.') === 0;
})
)) {
// the column starts with one of the table names
$column = $this->prefix . $column;
}
return $column;
}
/**
* Will look through the query and all it's joins to determine the table names.
*
* @return array
*/
public function tableNames()
{
$names = [];
$query = $this->getQueryBuilder();
$names[] = $query->from;
$joins = $query->joins ?: [];
$databasePrefix = $this->prefix;
foreach ($joins as $join) {
$table = preg_split('/ as /i', $join->table);
$names[] = $table[0];
if (isset($table[1]) && ! empty($databasePrefix) && strpos($table[1], $databasePrefix) == 0) {
$names[] = preg_replace('/^' . $databasePrefix . '/', '', $table[1]);
}
}
return $names;
}
/**
* Get Query Builder object.
*
* @param mixed $instance
* @return mixed
*/
public function getQueryBuilder($instance = null)
{
if (! $instance) {
$instance = $this->query;
}
if ($this->isQueryBuilder()) {
return $instance;
}
return $instance->getQuery();
}
/**
* Check query type is a builder.
*
* @return bool
*/
public function isQueryBuilder()
{
return $this->query_type == 'builder';
}
/**
* Add column in collection.
*
* @param string $name
* @param string|callable $content
* @param bool|int $order
* @return $this
*/
public function addColumn($name, $content, $order = false)
{
$this->extraColumns[] = $name;
$this->columnDef['append'][] = ['name' => $name, 'content' => $content, 'order' => $order];
return $this;
}
/**
* Add DT row index column on response.
*
* @return $this
*/
public function addIndexColumn()
{
$this->columnDef['index'] = true;
return $this;
}
/**
* Edit column's content.
*
* @param string $name
* @param string|callable $content
* @return $this
*/
public function editColumn($name, $content)
{
$this->columnDef['edit'][] = ['name' => $name, 'content' => $content];
return $this;
}
/**
* Remove column from collection.
*
* @return $this
*/
public function removeColumn()
{
$names = func_get_args();
$this->columnDef['excess'] = array_merge($this->columnDef['excess'], $names);
return $this;
}
/**
* Declare columns to escape values.
*
* @param string|array $columns
* @return $this
*/
public function escapeColumns($columns = '*')
{
$this->columnDef['escape'] = $columns;
return $this;
}
/**
* Allows previous API calls where the methods were snake_case.
* Will convert a camelCase API call to a snake_case call.
* Allow query builder method to be used by the engine.
*
* @param string $name
* @param array $arguments
* @return mixed
*/
public function __call($name, $arguments)
{
$name = Str::camel(Str::lower($name));
if (method_exists($this, $name)) {
return call_user_func_array([$this, $name], $arguments);
} elseif (method_exists($this->getQueryBuilder(), $name)) {
call_user_func_array([$this->getQueryBuilder(), $name], $arguments);
} else {
trigger_error('Call to undefined method ' . __CLASS__ . '::' . $name . '()', E_USER_ERROR);
}
return $this;
}
/**
* Sets DT_RowClass template.
* result: <tr class="output_from_your_template">.
*
* @param string|callable $content
* @return $this
*/
public function setRowClass($content)
{
$this->templates['DT_RowClass'] = $content;
return $this;
}
/**
* Sets DT_RowId template.
* result: <tr id="output_from_your_template">.
*
* @param string|callable $content
* @return $this
*/
public function setRowId($content)
{
$this->templates['DT_RowId'] = $content;
return $this;
}
/**
* Set DT_RowData templates.
*
* @param array $data
* @return $this
*/
public function setRowData(array $data)
{
$this->templates['DT_RowData'] = $data;
return $this;
}
/**
* Add DT_RowData template.
*
* @param string $key
* @param string|callable $value
* @return $this
*/
public function addRowData($key, $value)
{
$this->templates['DT_RowData'][$key] = $value;
return $this;
}
/**
* Set DT_RowAttr templates.
* result: <tr attr1="attr1" attr2="attr2">.
*
* @param array $data
* @return $this
*/
public function setRowAttr(array $data)
{
$this->templates['DT_RowAttr'] = $data;
return $this;
}
/**
* Add DT_RowAttr template.
*
* @param string $key
* @param string|callable $value
* @return $this
*/
public function addRowAttr($key, $value)
{
$this->templates['DT_RowAttr'][$key] = $value;
return $this;
}
/**
* Override default column filter search.
*
* @param string $column
* @param string|Closure $method
* @return $this
* @internal param $mixed ...,... All the individual parameters required for specified $method
* @internal string $1 Special variable that returns the requested search keyword.
*/
public function filterColumn($column, $method)
{
$params = func_get_args();
$this->columnDef['filter'][$column] = ['method' => $method, 'parameters' => array_splice($params, 2)];
return $this;
}
/**
* Order each given columns versus the given custom sql.
*
* @param array $columns
* @param string $sql
* @param array $bindings
* @return $this
*/
public function orderColumns(array $columns, $sql, $bindings = [])
{
foreach ($columns as $column) {
$this->orderColumn($column, str_replace(':column', $column, $sql), $bindings);
}
return $this;
}
/**
* Override default column ordering.
*
* @param string $column
* @param string $sql
* @param array $bindings
* @return $this
* @internal string $1 Special variable that returns the requested order direction of the column.
*/
public function orderColumn($column, $sql, $bindings = [])
{
$this->columnDef['order'][$column] = ['method' => 'orderByRaw', 'parameters' => [$sql, $bindings]];
return $this;
}
/**
* Set data output transformer.
*
* @param \League\Fractal\TransformerAbstract $transformer
* @return $this
*/
public function setTransformer($transformer)
{
$this->transformer = $transformer;
return $this;
}
/**
* Set fractal serializer class.
*
* @param string $serializer
* @return $this
*/
public function setSerializer($serializer)
{
$this->serializer = $serializer;
return $this;
}
/**
* Organizes works.
*
* @param bool $mDataSupport
* @param bool $orderFirst
* @return \Illuminate\Http\JsonResponse
*/
public function make($mDataSupport = false, $orderFirst = false)
{
$this->totalRecords = $this->totalCount();
if ($this->totalRecords) {
$this->orderRecords(! $orderFirst);
$this->filterRecords();
$this->orderRecords($orderFirst);
$this->paginate();
}
return $this->render($mDataSupport);
}
/**
* Sort records.
*
* @param boolean $skip
* @return void
*/
public function orderRecords($skip)
{
if (! $skip) {
$this->ordering();
}
}
/**
* Perform necessary filters.
*
* @return void
*/
public function filterRecords()
{
if ($this->autoFilter && $this->request->isSearchable()) {
$this->filtering();
}
if (is_callable($this->filterCallback)) {
call_user_func($this->filterCallback, $this->filterCallbackParameters);
}
$this->columnSearch();
$this->filteredRecords = $this->isFilterApplied ? $this->count() : $this->totalRecords;
}
/**
* Apply pagination.
*
* @return void
*/
public function paginate()
{
if ($this->request->isPaginationable()) {
$this->paging();
}
}
/**
* Render json response.
*
* @param bool $object
* @return \Illuminate\Http\JsonResponse
*/
public function render($object = false)
{
$output = array_merge([
'draw' => (int) $this->request['draw'],
'recordsTotal' => $this->totalRecords,
'recordsFiltered' => $this->filteredRecords,
], $this->appends);
if (isset($this->transformer)) {
$fractal = app('datatables.fractal');
if ($this->serializer) {
$fractal->setSerializer(new $this->serializer);
}
//Get transformer reflection
//Firs method parameter should be data/object to transform
$reflection = new \ReflectionMethod($this->transformer, 'transform');
$parameter = $reflection->getParameters()[0];
//If parameter is class assuming it requires object
//Else just pass array by default
if ($parameter->getClass()) {
$resource = new Collection($this->results(), $this->createTransformer());
} else {
$resource = new Collection(
$this->getProcessedData($object),
$this->createTransformer()
);
}
$collection = $fractal->createData($resource)->toArray();
$output['data'] = $collection['data'];
} else {
$output['data'] = Helper::transform($this->getProcessedData($object));
}
if ($this->isDebugging()) {
$output = $this->showDebugger($output);
}
return new JsonResponse($output);
}
/**
* Get or create transformer instance.
*
* @return \League\Fractal\TransformerAbstract
*/
protected function createTransformer()
{
if ($this->transformer instanceof \League\Fractal\TransformerAbstract) {
return $this->transformer;
}
return new $this->transformer();
}
/**
* Get processed data
*
* @param bool|false $object
* @return array
*/
private function getProcessedData($object = false)
{
$processor = new DataProcessor(
$this->results(),
$this->columnDef,
$this->templates,
$this->request['start']
);
return $processor->process($object);
}
/**
* Check if app is in debug mode.
*
* @return bool
*/
public function isDebugging()
{
return Config::get('app.debug', false);
}
/**
* Append debug parameters on output.
*
* @param array $output
* @return array
*/
public function showDebugger(array $output)
{
$output['queries'] = $this->connection->getQueryLog();
$output['input'] = $this->request->all();
return $output;
}
/**
* Update flags to disable global search
*
* @param \Closure $callback
* @param mixed $parameters
* @param bool $autoFilter
*/
public function overrideGlobalSearch(\Closure $callback, $parameters, $autoFilter = false)
{
$this->autoFilter = $autoFilter;
$this->isFilterApplied = true;
$this->filterCallback = $callback;
$this->filterCallbackParameters = $parameters;
}
/**
* Get config is case insensitive status.
*
* @return bool
*/
public function isCaseInsensitive()
{
return Config::get('datatables.search.case_insensitive', false);
}
/**
* Append data on json response.
*
* @param mixed $key
* @param mixed $value
* @return $this
*/
public function with($key, $value = '')
{
if (is_array($key)) {
$this->appends = $key;
} elseif (is_callable($value)) {
$this->appends[$key] = value($value);
} else {
$this->appends[$key] = value($value);
}
return $this;
}
/**
* Override default ordering method with a closure callback.
*
* @param \Closure $closure
* @return $this
*/
public function order(\Closure $closure)
{
$this->orderCallback = $closure;
return $this;
}
/**
* Update list of columns that is not allowed for search/sort.
*
* @param array $blacklist
* @return $this
*/
public function blacklist(array $blacklist)
{
$this->columnDef['blacklist'] = $blacklist;
return $this;
}
/**
* Update list of columns that is not allowed for search/sort.
*
* @param string|array $whitelist
* @return $this
*/
public function whitelist($whitelist = '*')
{
$this->columnDef['whitelist'] = $whitelist;
return $this;
}
/**
* Set smart search config at runtime.
*
* @param bool $bool
* @return $this
*/
public function smart($bool = true)
{
Config::set('datatables.search.smart', $bool);
return $this;
}
/**
* Check if column is blacklisted.
*
* @param string $column
* @return bool
*/
protected function isBlacklisted($column)
{
if (in_array($column, $this->columnDef['blacklist'])) {
return true;
}
if ($this->columnDef['whitelist'] === '*' || in_array($column, $this->columnDef['whitelist'])) {
return false;
}
return true;
}
/**
* Get column name to be use for filtering and sorting.
*
* @param integer $index
* @param bool $wantsAlias
* @return string
*/
protected function getColumnName($index, $wantsAlias = false)
{
$column = $this->request->columnName($index);
// DataTables is using make(false)
if (is_numeric($column)) {
$column = $this->getColumnNameByIndex($index);
}
if (Str::contains(Str::upper($column), ' AS ')) {
$column = $this->extractColumnName($column, $wantsAlias);
}
return $column;
}
/**
* Get column name by order column index.
*
* @param int $index
* @return mixed
*/
protected function getColumnNameByIndex($index)
{
$name = isset($this->columns[$index]) && $this->columns[$index] <> '*' ? $this->columns[$index] : $this->getPrimaryKeyName();
return in_array($name, $this->extraColumns, true) ? $this->getPrimaryKeyName() : $name;
}
/**
* If column name could not be resolved then use primary key.
*
* @return string
*/
protected function getPrimaryKeyName()
{
if ($this->isEloquent()) {
return $this->query->getModel()->getKeyName();
}
return 'id';
}
/**
* Check if the engine used was eloquent.
*
* @return bool
*/
protected function isEloquent()
{
return $this->query_type === 'eloquent';
}
/**
* Get column name from string.
*
* @param string $str
* @param bool $wantsAlias
* @return string
*/
protected function extractColumnName($str, $wantsAlias)
{
$matches = explode(' as ', Str::lower($str));
if (! empty($matches)) {
if ($wantsAlias) {
return array_pop($matches);
} else {
return array_shift($matches);
}
} elseif (strpos($str, '.')) {
$array = explode('.', $str);
return array_pop($array);
}
return $str;
}
/**
* Check if the current sql language is based on oracle syntax.
*
* @return bool
*/
protected function isOracleSql()
{
return in_array($this->database, ['oracle', 'oci8']);
}
/**
* Set total records manually.
*
* @param int $total
* @return $this
*/
public function setTotalRecords($total)
{
$this->totalRecords = $total;
return $this;
}
}

View File

@@ -0,0 +1,235 @@
<?php
namespace Yajra\Datatables\Engines;
use Closure;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Yajra\Datatables\Request;
/**
* Class CollectionEngine.
*
* @package Yajra\Datatables\Engines
* @author Arjay Angeles <aqangeles@gmail.com>
*/
class CollectionEngine extends BaseEngine
{
/**
* Collection object
*
* @var \Illuminate\Support\Collection
*/
public $collection;
/**
* Collection object
*
* @var \Illuminate\Support\Collection
*/
public $original_collection;
/**
* CollectionEngine constructor.
*
* @param \Illuminate\Support\Collection $collection
* @param \Yajra\Datatables\Request $request
*/
public function __construct(Collection $collection, Request $request)
{
$this->request = $request;
$this->collection = $collection;
$this->original_collection = $collection;
$this->columns = array_keys($this->serialize($collection->first()));
}
/**
* Serialize collection
*
* @param mixed $collection
* @return mixed|null
*/
protected function serialize($collection)
{
return $collection instanceof Arrayable ? $collection->toArray() : (array) $collection;
}
/**
* Set auto filter off and run your own filter.
* Overrides global search.
*
* @param \Closure $callback
* @param bool $globalSearch
* @return $this
*/
public function filter(Closure $callback, $globalSearch = false)
{
$this->overrideGlobalSearch($callback, $this, $globalSearch);
return $this;
}
/**
* Append debug parameters on output.
*
* @param array $output
* @return array
*/
public function showDebugger(array $output)
{
$output["input"] = $this->request->all();
return $output;
}
/**
* Count total items.
*
* @return integer
*/
public function totalCount()
{
return $this->count();
}
/**
* Count results.
*
* @return integer
*/
public function count()
{
return $this->collection->count();
}
/**
* Perform sorting of columns.
*
* @return void
*/
public function ordering()
{
if ($this->orderCallback) {
call_user_func($this->orderCallback, $this);
return;
}
foreach ($this->request->orderableColumns() as $orderable) {
$column = $this->getColumnName($orderable['column']);
$this->collection = $this->collection->sortBy(
function ($row) use ($column) {
$data = $this->serialize($row);
return Arr::get($data, $column);
}
);
if ($orderable['direction'] == 'desc') {
$this->collection = $this->collection->reverse();
}
}
}
/**
* Perform global search.
*
* @return void
*/
public function filtering()
{
$columns = $this->request['columns'];
$this->collection = $this->collection->filter(
function ($row) use ($columns) {
$data = $this->serialize($row);
$this->isFilterApplied = true;
$found = [];
$keyword = $this->request->keyword();
foreach ($this->request->searchableColumnIndex() as $index) {
$column = $this->getColumnName($index);
if (! $value = Arr::get($data, $column)) {
continue;
}
if ($this->isCaseInsensitive()) {
$found[] = Str::contains(Str::lower($value), Str::lower($keyword));
} else {
$found[] = Str::contains($value, $keyword);
}
}
return in_array(true, $found);
}
);
}
/**
* Perform column search.
*
* @return void
*/
public function columnSearch()
{
$columns = $this->request->get('columns');
for ($i = 0, $c = count($columns); $i < $c; $i++) {
if ($this->request->isColumnSearchable($i)) {
$this->isFilterApplied = true;
$column = $this->getColumnName($i);
$keyword = $this->request->columnKeyword($i);
$this->collection = $this->collection->filter(
function ($row) use ($column, $keyword) {
$data = $this->serialize($row);
$value = Arr::get($data, $column);
if ($this->isCaseInsensitive()) {
return strpos(Str::lower($value), Str::lower($keyword)) !== false;
} else {
return strpos($value, $keyword) !== false;
}
}
);
}
}
}
/**
* Perform pagination.
*
* @return void
*/
public function paging()
{
$this->collection = $this->collection->slice(
$this->request['start'],
(int) $this->request['length'] > 0 ? $this->request['length'] : 10
);
}
/**
* Get results.
*
* @return mixed
*/
public function results()
{
return $this->collection->all();
}
/**
* Organizes works.
*
* @param bool $mDataSupport
* @param bool $orderFirst
* @return \Illuminate\Http\JsonResponse
*/
public function make($mDataSupport = false, $orderFirst = true)
{
return parent::make($mDataSupport, $orderFirst);
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Yajra\Datatables\Engines;
use Illuminate\Database\Eloquent\Builder;
use Yajra\Datatables\Request;
/**
* Class EloquentEngine.
*
* @package Yajra\Datatables\Engines
* @author Arjay Angeles <aqangeles@gmail.com>
*/
class EloquentEngine extends QueryBuilderEngine
{
/**
* @param mixed $model
* @param \Yajra\Datatables\Request $request
*/
public function __construct($model, Request $request)
{
$builder = $model instanceof Builder ? $model : $model->getQuery();
parent::__construct($builder->getQuery(), $request);
$this->query = $builder;
$this->query_type = 'eloquent';
}
}

View File

@@ -0,0 +1,535 @@
<?php
namespace Yajra\Datatables\Engines;
use Closure;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Str;
use Yajra\Datatables\Helper;
use Yajra\Datatables\Request;
/**
* Class QueryBuilderEngine.
*
* @package Yajra\Datatables\Engines
* @author Arjay Angeles <aqangeles@gmail.com>
*/
class QueryBuilderEngine extends BaseEngine
{
/**
* @param \Illuminate\Database\Query\Builder $builder
* @param \Yajra\Datatables\Request $request
*/
public function __construct(Builder $builder, Request $request)
{
$this->query = $builder;
$this->init($request, $builder);
}
/**
* Initialize attributes.
*
* @param \Yajra\Datatables\Request $request
* @param \Illuminate\Database\Query\Builder $builder
* @param string $type
*/
protected function init($request, $builder, $type = 'builder')
{
$this->request = $request;
$this->query_type = $type;
$this->columns = $builder->columns;
$this->connection = $builder->getConnection();
$this->prefix = $this->connection->getTablePrefix();
$this->database = $this->connection->getDriverName();
if ($this->isDebugging()) {
$this->connection->enableQueryLog();
}
}
/**
* Set auto filter off and run your own filter.
* Overrides global search
*
* @param \Closure $callback
* @param bool $globalSearch
* @return $this
*/
public function filter(Closure $callback, $globalSearch = false)
{
$this->overrideGlobalSearch($callback, $this->query, $globalSearch);
return $this;
}
/**
* Organizes works
*
* @param bool $mDataSupport
* @param bool $orderFirst
* @return \Illuminate\Http\JsonResponse
*/
public function make($mDataSupport = false, $orderFirst = false)
{
return parent::make($mDataSupport, $orderFirst);
}
/**
* Count total items.
*
* @return integer
*/
public function totalCount()
{
return $this->totalRecords ? $this->totalRecords : $this->count();
}
/**
* Counts current query.
*
* @return int
*/
public function count()
{
$myQuery = clone $this->query;
// if its a normal query ( no union, having and distinct word )
// replace the select with static text to improve performance
if (! Str::contains(Str::lower($myQuery->toSql()), ['union', 'having', 'distinct', 'order by', 'group by'])) {
$row_count = $this->wrap('row_count');
$myQuery->select($this->connection->raw("'1' as {$row_count}"));
}
return $this->connection->table($this->connection->raw('(' . $myQuery->toSql() . ') count_row_table'))
->setBindings($myQuery->getBindings())->count();
}
/**
* Wrap column with DB grammar.
*
* @param string $column
* @return string
*/
protected function wrap($column) {
return $this->connection->getQueryGrammar()->wrap($column);
}
/**
* Perform global search.
*
* @return void
*/
public function filtering()
{
$this->query->where(
function ($query) {
$globalKeyword = $this->request->keyword();
$queryBuilder = $this->getQueryBuilder($query);
foreach ($this->request->searchableColumnIndex() as $index) {
$columnName = $this->getColumnName($index);
if ($this->isBlacklisted($columnName)) {
continue;
}
// check if custom column filtering is applied
if (isset($this->columnDef['filter'][$columnName])) {
$columnDef = $this->columnDef['filter'][$columnName];
// check if global search should be applied for the specific column
$applyGlobalSearch = count($columnDef['parameters']) == 0 || end($columnDef['parameters']) !== false;
if (! $applyGlobalSearch) {
continue;
}
if ($columnDef['method'] instanceof Closure) {
$whereQuery = $queryBuilder->newQuery();
call_user_func_array($columnDef['method'], [$whereQuery, $globalKeyword]);
$queryBuilder->addNestedWhereQuery($whereQuery, 'or');
} else {
$this->compileColumnQuery(
$queryBuilder,
Helper::getOrMethod($columnDef['method']),
$columnDef['parameters'],
$columnName,
$globalKeyword
);
}
} else {
if (count(explode('.', $columnName)) > 1) {
$eagerLoads = $this->getEagerLoads();
$parts = explode('.', $columnName);
$relationColumn = array_pop($parts);
$relation = implode('.', $parts);
if (in_array($relation, $eagerLoads)) {
$this->compileRelationSearch(
$queryBuilder,
$relation,
$relationColumn,
$globalKeyword
);
} else {
$this->compileQuerySearch($queryBuilder, $columnName, $globalKeyword);
}
} else {
$this->compileQuerySearch($queryBuilder, $columnName, $globalKeyword);
}
}
$this->isFilterApplied = true;
}
}
);
}
/**
* Perform filter column on selected field.
*
* @param mixed $query
* @param string|Closure $method
* @param mixed $parameters
* @param string $column
* @param string $keyword
*/
protected function compileColumnQuery($query, $method, $parameters, $column, $keyword)
{
if (method_exists($query, $method)
&& count($parameters) <= with(new \ReflectionMethod($query, $method))->getNumberOfParameters()
) {
if (Str::contains(Str::lower($method), 'raw')
|| Str::contains(Str::lower($method), 'exists')
) {
call_user_func_array(
[$query, $method],
$this->parameterize($parameters, $keyword)
);
} else {
call_user_func_array(
[$query, $method],
$this->parameterize($column, $parameters, $keyword)
);
}
}
}
/**
* Build Query Builder Parameters.
*
* @return array
*/
protected function parameterize()
{
$args = func_get_args();
$keyword = count($args) > 2 ? $args[2] : $args[1];
$parameters = Helper::buildParameters($args);
$parameters = Helper::replacePatternWithKeyword($parameters, $keyword, '$1');
return $parameters;
}
/**
* Get eager loads keys if eloquent.
*
* @return array
*/
protected function getEagerLoads()
{
if ($this->query_type == 'eloquent') {
return array_keys($this->query->getEagerLoads());
}
return [];
}
/**
* Add relation query on global search.
*
* @param mixed $query
* @param string $relation
* @param string $column
* @param string $keyword
*/
protected function compileRelationSearch($query, $relation, $column, $keyword)
{
$myQuery = clone $this->query;
$myQuery->orWhereHas($relation, function ($builder) use ($column, $keyword, $query) {
$builder->select($this->connection->raw('count(1)'));
$this->compileQuerySearch($builder, $column, $keyword, '');
$builder = "({$builder->toSql()}) >= 1";
$query->orWhereRaw($builder, [$keyword]);
});
}
/**
* Compile query builder where clause depending on configurations.
*
* @param mixed $query
* @param string $column
* @param string $keyword
* @param string $relation
*/
protected function compileQuerySearch($query, $column, $keyword, $relation = 'or')
{
$column = strstr($column, '(') ? $this->connection->raw($column) : $column;
$column = $this->castColumn($column);
$sql = $column . ' LIKE ?';
if ($this->isCaseInsensitive()) {
$sql = 'LOWER(' . $column . ') LIKE ?';
$keyword = Str::lower($keyword);
}
if ($this->isWildcard()) {
$keyword = $this->wildcardLikeString($keyword);
}
if ($this->isSmartSearch()) {
$keyword = "%$keyword%";
}
$query->{$relation .'WhereRaw'}($sql, [$keyword]);
}
/**
* Wrap a column and cast in pgsql.
*
* @param string $column
* @return string
*/
public function castColumn($column)
{
$column = $this->wrap($column);
if ($this->database === 'pgsql') {
$column = 'CAST(' . $column . ' as TEXT)';
} elseif ($this->database === 'firebird') {
$column = 'CAST(' . $column . ' as VARCHAR(255))';
}
return $column;
}
/**
* Perform column search.
*
* @return void
*/
public function columnSearch()
{
$columns = $this->request->get('columns', []);
foreach ($columns as $index => $column) {
if (! $this->request->isColumnSearchable($index)) {
continue;
}
$column = $this->getColumnName($index);
if (isset($this->columnDef['filter'][$column])) {
$columnDef = $this->columnDef['filter'][$column];
// get a raw keyword (without wildcards)
$keyword = $this->getSearchKeyword($index, true);
$builder = $this->getQueryBuilder();
if ($columnDef['method'] instanceof Closure) {
$whereQuery = $builder->newQuery();
call_user_func_array($columnDef['method'], [$whereQuery, $keyword]);
$builder->addNestedWhereQuery($whereQuery);
} else {
$this->compileColumnQuery(
$builder,
$columnDef['method'],
$columnDef['parameters'],
$column,
$keyword
);
}
} else {
if (count(explode('.', $column)) > 1) {
$eagerLoads = $this->getEagerLoads();
$parts = explode('.', $column);
$relationColumn = array_pop($parts);
$relation = implode('.', $parts);
if (in_array($relation, $eagerLoads)) {
$column = $this->joinEagerLoadedColumn($relation, $relationColumn);
}
}
$keyword = $this->getSearchKeyword($index);
$this->compileColumnSearch($index, $column, $keyword);
}
$this->isFilterApplied = true;
}
}
/**
* Get proper keyword to use for search.
*
* @param int $i
* @param bool $raw
* @return string
*/
private function getSearchKeyword($i, $raw = false)
{
$keyword = $this->request->columnKeyword($i);
if ($raw || $this->request->isRegex($i)) {
return $keyword;
}
return $this->setupKeyword($keyword);
}
/**
* Compile queries for column search.
*
* @param int $i
* @param mixed $column
* @param string $keyword
*/
protected function compileColumnSearch($i, $column, $keyword)
{
if ($this->request->isRegex($i)) {
$column = strstr($column, '(') ? $this->connection->raw($column) : $column;
$this->regexColumnSearch($column, $keyword);
} else {
$this->compileQuerySearch($this->query, $column, $keyword, '');
}
}
/**
* Compile regex query column search.
*
* @param mixed $column
* @param string $keyword
*/
protected function regexColumnSearch($column, $keyword)
{
if ($this->isOracleSql()) {
$sql = ! $this->isCaseInsensitive() ? 'REGEXP_LIKE( ' . $column . ' , ? )' : 'REGEXP_LIKE( LOWER(' . $column . ') , ?, \'i\' )';
$this->query->whereRaw($sql, [$keyword]);
} else {
$sql = ! $this->isCaseInsensitive() ? $column . ' REGEXP ?' : 'LOWER(' . $column . ') REGEXP ?';
$this->query->whereRaw($sql, [Str::lower($keyword)]);
}
}
/**
* Perform sorting of columns.
*
* @return void
*/
public function ordering()
{
if ($this->orderCallback) {
call_user_func($this->orderCallback, $this->getQueryBuilder());
return;
}
foreach ($this->request->orderableColumns() as $orderable) {
$column = $this->getColumnName($orderable['column'], true);
if ($this->isBlacklisted($column)) {
continue;
}
if (isset($this->columnDef['order'][$column])) {
$method = $this->columnDef['order'][$column]['method'];
$parameters = $this->columnDef['order'][$column]['parameters'];
$this->compileColumnQuery(
$this->getQueryBuilder(),
$method,
$parameters,
$column,
$orderable['direction']
);
} else {
if (count(explode('.', $column)) > 1) {
$eagerLoads = $this->getEagerLoads();
$parts = explode('.', $column);
$relationColumn = array_pop($parts);
$relation = implode('.', $parts);
if (in_array($relation, $eagerLoads)) {
$column = $this->joinEagerLoadedColumn($relation, $relationColumn);
}
}
$this->getQueryBuilder()->orderBy($column, $orderable['direction']);
}
}
}
/**
* Join eager loaded relation and get the related column name.
*
* @param string $relation
* @param string $relationColumn
* @return string
*/
protected function joinEagerLoadedColumn($relation, $relationColumn)
{
$joins = [];
foreach ((array) $this->getQueryBuilder()->joins as $key => $join) {
$joins[] = $join->table;
}
$model = $this->query->getRelation($relation);
if ($model instanceof BelongsToMany) {
$pivot = $model->getTable();
$pivotPK = $model->getForeignKey();
$pivotFK = $model->getQualifiedParentKeyName();
if (! in_array($pivot, $joins)) {
$this->getQueryBuilder()->leftJoin($pivot, $pivotPK, '=', $pivotFK);
}
$related = $model->getRelated();
$table = $related->getTable();
$tablePK = $related->getForeignKey();
$tableFK = $related->getQualifiedKeyName();
if (! in_array($table, $joins)) {
$this->getQueryBuilder()->leftJoin($table, $pivot . '.' . $tablePK, '=', $tableFK);
}
} else {
$table = $model->getRelated()->getTable();
if ($model instanceof HasOne) {
$foreign = $model->getForeignKey();
$other = $model->getQualifiedParentKeyName();
} else {
$foreign = $model->getQualifiedForeignKey();
$other = $model->getQualifiedOtherKeyName();
}
if (! in_array($table, $joins)) {
$this->getQueryBuilder()->leftJoin($table, $foreign, '=', $other);
}
}
$column = $table . '.' . $relationColumn;
return $column;
}
/**
* Perform pagination
*
* @return void
*/
public function paging()
{
$this->query->skip($this->request['start'])
->take((int) $this->request['length'] > 0 ? $this->request['length'] : 10);
}
/**
* Get results
*
* @return array|static[]
*/
public function results()
{
return $this->query->get();
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Yajra\Datatables\Facades;
use Illuminate\Support\Facades\Facade;
/**
* Class Datatables.
*
* @package Yajra\Datatables\Facades
* @author Arjay Angeles <aqangeles@gmail.com>
*/
class Datatables extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return 'datatables';
}
}

View File

@@ -0,0 +1,208 @@
<?php
namespace Yajra\Datatables\Generators;
use Illuminate\Console\GeneratorCommand;
use Illuminate\Support\Str;
use Symfony\Component\Console\Input\InputOption;
/**
* Class DataTablesMakeCommand.
*
* @package Yajra\Datatables\Generators
* @author Arjay Angeles <aqangeles@gmail.com>
*/
class DataTablesMakeCommand extends GeneratorCommand
{
/**
* The console command name.
*
* @var string
*/
protected $name = 'datatables:make';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a new DataTable service class.';
/**
* The type of class being generated.
*
* @var string
*/
protected $type = 'DataTable';
/**
* The model class to be used by dataTable.
*
* @var string
*/
protected $model;
/**
* DataTable export filename.
*
* @var string
*/
protected $filename;
/**
* Build the class with the given name.
*
* @param string $name
* @return string
*/
protected function buildClass($name)
{
$stub = $this->files->get($this->getStub());
$stub = $this->replaceNamespace($stub, $name)->replaceClass($stub, $name);
return $this->replaceModelImport($stub)->replaceModel($stub)->replaceFilename($stub);
}
/**
* Get the stub file for the generator.
*
* @return string
*/
protected function getStub()
{
return __DIR__ . '/stubs/datatables.stub';
}
/**
* Replace model name.
*
* @param string $stub
* @return mixed
*/
protected function replaceModel(&$stub)
{
$model = explode('\\', $this->model);
$model = array_pop($model);
$stub = str_replace('ModelName', $model, $stub);
return $this;
}
/**
* Replace model import.
*
* @param string $stub
* @return $this
*/
protected function replaceModelImport(&$stub)
{
$stub = str_replace(
'DummyModel', str_replace('\\\\', '\\', $this->model), $stub
);
return $this;
}
/**
* Replace the filename.
*
* @param string $stub
* @return string
*/
protected function replaceFilename(&$stub)
{
$stub = str_replace(
'DummyFilename', $this->filename, $stub
);
return $stub;
}
/**
* Get the console command options.
*
* @return array
*/
protected function getOptions()
{
return [
['model', null, InputOption::VALUE_NONE, 'Use the provided name as the model.', null],
];
}
/**
* Determine if the class already exists.
*
* @param string $rawName
* @return bool
*/
protected function alreadyExists($rawName)
{
$name = $this->parseName($rawName);
$this->setModel($rawName);
$this->setFilename($rawName);
return $this->files->exists($this->getPath($name));
}
/**
* Parse the name and format according to the root namespace.
*
* @param string $name
* @return string
*/
protected function parseName($name)
{
$rootNamespace = $this->laravel->getNamespace();
if (Str::startsWith($name, $rootNamespace)) {
return $name;
}
if (Str::contains($name, '/')) {
$name = str_replace('/', '\\', $name);
}
if (! Str::contains(Str::lower($name), 'datatable')) {
$name .= 'DataTable';
}
return $this->parseName($this->getDefaultNamespace(trim($rootNamespace, '\\')) . '\\' . $name);
}
/**
* Get the default namespace for the class.
*
* @param string $rootNamespace
* @return string
*/
protected function getDefaultNamespace($rootNamespace)
{
return $rootNamespace . "\\" . $this->laravel['config']->get('datatables.namespace.base', 'DataTables');
}
/**
* Set the model to be used.
*
* @param string $name
*/
protected function setModel($name)
{
$rootNamespace = $this->laravel->getNamespace();
$modelNamespace = $this->laravel['config']->get('datatables.namespace.model');
$this->model = $this->option('model')
? $rootNamespace . "\\" . ($modelNamespace ? $modelNamespace . "\\" : "") . $name
: $rootNamespace . "\\User";
}
/**
* Set the filename for export.
*
* @param string $name
*/
protected function setFilename($name)
{
$this->filename = Str::lower(Str::plural($name));
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace Yajra\Datatables\Generators;
use Illuminate\Console\GeneratorCommand;
/**
* Class DataTablesScopeCommand.
*
* @package Yajra\Datatables\Generators
* @author Arjay Angeles <aqangeles@gmail.com>
*/
class DataTablesScopeCommand extends GeneratorCommand
{
/**
* The console command name.
*
* @var string
*/
protected $name = 'datatables:scope';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a new DataTable Scope class.';
/**
* The type of class being generated.
*
* @var string
*/
protected $type = 'DataTable Scope';
/**
* Get the default namespace for the class.
*
* @param string $rootNamespace
* @return string
*/
protected function getDefaultNamespace($rootNamespace)
{
return $rootNamespace . '\DataTables\Scopes';
}
/**
* Get the stub file for the generator.
*
* @return string
*/
protected function getStub()
{
return __DIR__ . '/stubs/scopes.stub';
}
}

View File

@@ -0,0 +1,73 @@
<?php
namespace DummyNamespace;
use DummyModel;
use Yajra\Datatables\Services\DataTable;
class DummyClass extends DataTable
{
/**
* Display ajax response.
*
* @return \Illuminate\Http\JsonResponse
*/
public function ajax()
{
return $this->datatables
->eloquent($this->query())
->addColumn('action', 'path.to.action.view')
->make(true);
}
/**
* Get the query object to be processed by dataTables.
*
* @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder|\Illuminate\Support\Collection
*/
public function query()
{
$query = ModelName::query();
return $this->applyScopes($query);
}
/**
* Optional method if you want to use html builder.
*
* @return \Yajra\Datatables\Html\Builder
*/
public function html()
{
return $this->builder()
->columns($this->getColumns())
->ajax('')
->addAction(['width' => '80px'])
->parameters($this->getBuilderParameters());
}
/**
* Get columns.
*
* @return array
*/
protected function getColumns()
{
return [
'id',
// add your columns
'created_at',
'updated_at',
];
}
/**
* Get filename for export.
*
* @return string
*/
protected function filename()
{
return 'DummyFilename_' . time();
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace DummyNamespace;
use Yajra\Datatables\Contracts\DataTableScopeContract;
class DummyClass implements DataTableScopeContract
{
/**
* Apply a query scope.
*
* @param \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $query
* @return mixed
*/
public function apply($query)
{
// return $query->where('id', 1);
}
}

View File

@@ -0,0 +1,313 @@
<?php
namespace Yajra\Datatables;
use DateTime;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Str;
use Illuminate\View\Compilers\BladeCompiler;
/**
* Class Helper.
*
* @package Yajra\Datatables
* @author Arjay Angeles <aqangeles@gmail.com>
*/
class Helper
{
/**
* Places item of extra columns into results by care of their order.
*
* @param array $item
* @param array $array
* @return array
*/
public static function includeInArray($item, $array)
{
if (self::isItemOrderInvalid($item, $array)) {
return array_merge($array, [$item['name'] => $item['content']]);
} else {
$count = 0;
$last = $array;
$first = [];
foreach ($array as $key => $value) {
if ($count == $item['order']) {
return array_merge($first, [$item['name'] => $item['content']], $last);
}
unset($last[$key]);
$first[$key] = $value;
$count++;
}
}
}
/**
* Check if item order is valid.
*
* @param array $item
* @param array $array
* @return bool
*/
protected static function isItemOrderInvalid($item, $array)
{
return $item['order'] === false || $item['order'] >= count($array);
}
/**
* Determines if content is callable or blade string, processes and returns.
*
* @param string|callable $content Pre-processed content
* @param array $data data to use with blade template
* @param mixed $param parameter to call with callable
* @return string Processed content
*/
public static function compileContent($content, array $data, $param)
{
if (is_string($content)) {
return static::compileBlade($content, static::getMixedValue($data, $param));
} elseif (is_callable($content)) {
return $content($param);
}
return $content;
}
/**
* Parses and compiles strings by using Blade Template System.
*
* @param string $str
* @param array $data
* @return string
* @throws \Exception
*/
public static function compileBlade($str, $data = [])
{
if (view()->exists($str)) {
return view($str, $data)->render();
}
$empty_filesystem_instance = new Filesystem();
$blade = new BladeCompiler($empty_filesystem_instance, 'datatables');
$parsed_string = $blade->compileString($str);
ob_start() && extract($data, EXTR_SKIP);
try {
eval('?>' . $parsed_string);
} catch (\Exception $e) {
ob_end_clean();
throw $e;
}
$str = ob_get_contents();
ob_end_clean();
return $str;
}
/**
* Get a mixed value of custom data and the parameters.
*
* @param array $data
* @param mixed $param
* @return array
*/
public static function getMixedValue(array $data, $param)
{
$param = self::castToArray($param);
foreach ($data as $key => $value) {
if (isset($param[$key])) {
$data[$key] = $param[$key];
}
}
return $data;
}
/**
* Cast the parameter into an array.
*
* @param mixed $param
* @return array
*/
public static function castToArray($param)
{
if ($param instanceof \stdClass) {
$param = (array) $param;
return $param;
}
if ($param instanceof Arrayable) {
return $param->toArray();
}
return $param;
}
/**
* Get equivalent or method of query builder.
*
* @param string $method
* @return string
*/
public static function getOrMethod($method)
{
if (! Str::contains(Str::lower($method), 'or')) {
return 'or' . ucfirst($method);
}
return $method;
}
/**
* Wrap value depending on database type.
*
* @param string $database
* @param string $value
* @return string
*/
public static function wrapDatabaseValue($database, $value)
{
$parts = explode('.', $value);
$column = '';
foreach ($parts as $key) {
$column = static::wrapDatabaseColumn($database, $key, $column);
}
return substr($column, 0, strlen($column) - 1);
}
/**
* Database column wrapper.
*
* @param string $database
* @param string $key
* @param string $column
* @return string
*/
public static function wrapDatabaseColumn($database, $key, $column)
{
switch ($database) {
case 'mysql':
$column .= '`' . str_replace('`', '``', $key) . '`' . '.';
break;
case 'sqlsrv':
$column .= '[' . str_replace(']', ']]', $key) . ']' . '.';
break;
case 'pgsql':
case 'sqlite':
$column .= '"' . str_replace('"', '""', $key) . '"' . '.';
break;
default:
$column .= $key . '.';
}
return $column;
}
/**
* Converts array object values to associative array.
*
* @param mixed $row
* @return array
*/
public static function convertToArray($row)
{
$data = $row instanceof Arrayable ? $row->toArray() : (array) $row;
foreach (array_keys($data) as $key) {
if (is_object($data[$key]) || is_array($data[$key])) {
$data[$key] = self::convertToArray($data[$key]);
}
}
return $data;
}
/**
* @param array $data
* @return array
*/
public static function transform(array $data)
{
return array_map(function ($row) {
return self::transformRow($row);
}, $data);
}
/**
* Transform row data into an array.
*
* @param mixed $row
* @return array
*/
protected static function transformRow($row)
{
foreach ($row as $key => $value) {
if ($value instanceof DateTime) {
$row[$key] = $value->format('Y-m-d H:i:s');
} else {
if (is_object($value)) {
$row[$key] = (string) $value;
} else {
$row[$key] = $value;
}
}
}
return $row;
}
/**
* Build parameters depending on # of arguments passed.
*
* @param array $args
* @return array
*/
public static function buildParameters(array $args)
{
$parameters = [];
if (count($args) > 2) {
$parameters[] = $args[0];
foreach ($args[1] as $param) {
$parameters[] = $param;
}
} else {
foreach ($args[0] as $param) {
$parameters[] = $param;
}
}
return $parameters;
}
/**
* Replace all pattern occurrences with keyword
*
* @param array $subject
* @param string $keyword
* @param string $pattern
* @return array
*/
public static function replacePatternWithKeyword(array $subject, $keyword, $pattern = '$1')
{
$parameters = [];
foreach ($subject as $param) {
if (is_array($param)) {
$parameters[] = self::replacePatternWithKeyword($param, $keyword, $pattern);
} else {
$parameters[] = str_replace($pattern, $keyword, $param);
}
}
return $parameters;
}
}

View File

@@ -0,0 +1,620 @@
<?php
namespace Yajra\Datatables\Html;
use Collective\Html\FormBuilder;
use Collective\Html\HtmlBuilder;
use Illuminate\Contracts\Config\Repository;
use Illuminate\Contracts\View\Factory;
use Illuminate\Routing\UrlGenerator;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Str;
/**
* Class Builder.
*
* @package Yajra\Datatables\Html
* @author Arjay Angeles <aqangeles@gmail.com>
*/
class Builder
{
/**
* @var Collection
*/
public $collection;
/**
* @var Repository
*/
public $config;
/**
* @var Factory
*/
public $view;
/**
* @var HtmlBuilder
*/
public $html;
/**
* @var UrlGenerator
*/
public $url;
/**
* @var FormBuilder
*/
public $form;
/**
* @var string|array
*/
protected $ajax = '';
/**
* @var array
*/
protected $tableAttributes = ['class' => 'table', 'id' => 'dataTableBuilder'];
/**
* @var string
*/
protected $template = '';
/**
* @var array
*/
protected $attributes = [];
/**
* Lists of valid DataTables Callbacks.
*
* @link https://datatables.net/reference/option/.
* @var array
*/
protected $validCallbacks = [
'createdRow',
'drawCallback',
'footerCallback',
'formatNumber',
'headerCallback',
'infoCallback',
'initComplete',
'preDrawCallback',
'rowCallback',
'stateLoadCallback',
'stateLoaded',
'stateLoadParams',
'stateSaveCallback',
'stateSaveParams',
];
/**
* @param Repository $config
* @param Factory $view
* @param HtmlBuilder $html
* @param UrlGenerator $url
* @param FormBuilder $form
*/
public function __construct(
Repository $config,
Factory $view,
HtmlBuilder $html,
UrlGenerator $url,
FormBuilder $form
) {
$this->config = $config;
$this->view = $view;
$this->html = $html;
$this->url = $url;
$this->collection = new Collection;
$this->form = $form;
}
/**
* Generate DataTable javascript.
*
* @param null $script
* @param array $attributes
* @return string
*/
public function scripts($script = null, array $attributes = ['type' => 'text/javascript'])
{
$script = $script ?: $this->generateScripts();
return '<script' . $this->html->attributes($attributes) . '>' . $script . '</script>' . PHP_EOL;
}
/**
* Get generated raw scripts.
*
* @return string
*/
public function generateScripts()
{
$args = array_merge(
$this->attributes, [
'ajax' => $this->ajax,
'columns' => $this->collection->toArray(),
]
);
$parameters = $this->parameterize($args);
return sprintf(
$this->template(),
$this->tableAttributes['id'], $parameters
);
}
/**
* Generate DataTables js parameters.
*
* @param array $attributes
* @return string
*/
public function parameterize($attributes = [])
{
$parameters = (new Parameters($attributes))->toArray();
list($ajaxDataFunction, $parameters) = $this->encodeAjaxDataFunction($parameters);
list($columnFunctions, $parameters) = $this->encodeColumnFunctions($parameters);
list($callbackFunctions, $parameters) = $this->encodeCallbackFunctions($parameters);
$json = json_encode($parameters);
$json = $this->decodeAjaxDataFunction($ajaxDataFunction, $json);
$json = $this->decodeColumnFunctions($columnFunctions, $json);
$json = $this->decodeCallbackFunctions($callbackFunctions, $json);
return $json;
}
/**
* Encode ajax data function param.
*
* @param array $parameters
* @return mixed
*/
protected function encodeAjaxDataFunction($parameters)
{
$ajaxData = '';
if (isset($parameters['ajax']['data'])) {
$ajaxData = $parameters['ajax']['data'];
$parameters['ajax']['data'] = "#ajax_data#";
}
return [$ajaxData, $parameters];
}
/**
* Encode columns render function.
*
* @param array $parameters
* @return array
*/
protected function encodeColumnFunctions(array $parameters)
{
$columnFunctions = [];
foreach ($parameters['columns'] as $i => $column) {
unset($parameters['columns'][$i]['exportable']);
unset($parameters['columns'][$i]['printable']);
unset($parameters['columns'][$i]['footer']);
if (isset($column['render'])) {
$columnFunctions[$i] = $column['render'];
$parameters['columns'][$i]['render'] = "#column_function.{$i}#";
}
}
return [$columnFunctions, $parameters];
}
/**
* Encode DataTables callbacks function.
*
* @param array $parameters
* @return array
*/
protected function encodeCallbackFunctions(array $parameters)
{
$callbackFunctions = [];
foreach ($parameters as $key => $callback) {
if (in_array($key, $this->validCallbacks)) {
$callbackFunctions[$key] = $this->compileCallback($callback);
$parameters[$key] = "#callback_function.{$key}#";
}
}
return [$callbackFunctions, $parameters];
}
/**
* Compile DataTable callback value.
*
* @param mixed $callback
* @return mixed|string
*/
private function compileCallback($callback)
{
if (is_callable($callback)) {
return value($callback);
} elseif ($this->view->exists($callback)) {
return $this->view->make($callback)->render();
}
return $callback;
}
/**
* Decode ajax data method.
*
* @param string $function
* @param string $json
* @return string
*/
protected function decodeAjaxDataFunction($function, $json)
{
return str_replace("\"#ajax_data#\"", $function, $json);
}
/**
* Decode columns render functions.
*
* @param array $columnFunctions
* @param string $json
* @return string
*/
protected function decodeColumnFunctions(array $columnFunctions, $json)
{
foreach ($columnFunctions as $i => $function) {
$json = str_replace("\"#column_function.{$i}#\"", $function, $json);
}
return $json;
}
/**
* Decode DataTables callbacks function.
*
* @param array $callbackFunctions
* @param string $json
* @return string
*/
protected function decodeCallbackFunctions(array $callbackFunctions, $json)
{
foreach ($callbackFunctions as $i => $function) {
$json = str_replace("\"#callback_function.{$i}#\"", $function, $json);
}
return $json;
}
/**
* Get javascript template to use.
*
* @return string
*/
protected function template()
{
return $this->view->make(
$this->template ?: $this->config->get('datatables.script_template', 'datatables::script')
)->render();
}
/**
* Sets HTML table attribute(s).
*
* @param string|array $attribute
* @param mixed $value
* @return $this
*/
public function setTableAttribute($attribute, $value = null)
{
if (is_array($attribute)) {
$this->setTableAttributes($attribute);
} else {
$this->tableAttributes[$attribute] = $value;
}
return $this;
}
/**
* Sets multiple HTML table attributes at once.
*
* @param array $attributes
* @return $this
*/
public function setTableAttributes(array $attributes)
{
foreach ($attributes as $attribute => $value) {
$this->setTableAttribute($attribute, $value);
}
return $this;
}
/**
* Retrieves HTML table attribute value.
*
* @param string $attribute
* @return mixed
* @throws \Exception
*/
public function getTableAttribute($attribute)
{
if (! array_key_exists($attribute, $this->tableAttributes)) {
throw new \Exception("Table attribute '{$attribute}' does not exist.");
}
return $this->tableAttributes[$attribute];
}
/**
* Add a column in collection using attributes.
*
* @param array $attributes
* @return $this
*/
public function addColumn(array $attributes)
{
$this->collection->push(new Column($attributes));
return $this;
}
/**
* Add a Column object in collection.
*
* @param \Yajra\Datatables\Html\Column $column
* @return $this
*/
public function add(Column $column)
{
$this->collection->push($column);
return $this;
}
/**
* Set datatables columns from array definition.
*
* @param array $columns
* @return $this
*/
public function columns(array $columns)
{
foreach ($columns as $key => $value) {
if (! is_a($value, Column::class)) {
if (is_array($value)) {
$attributes = array_merge(['name' => $key, 'data' => $key], $this->setTitle($key, $value));
} else {
$attributes = [
'name' => $value,
'data' => $value,
'title' => $this->getQualifiedTitle($value),
];
}
$this->collection->push(new Column($attributes));
} else {
$this->collection->push($value);
}
}
return $this;
}
/**
* Set title attribute of an array if not set.
*
* @param string $title
* @param array $attributes
* @return array
*/
public function setTitle($title, array $attributes)
{
if (! isset($attributes['title'])) {
$attributes['title'] = $this->getQualifiedTitle($title);
}
return $attributes;
}
/**
* Convert string into a readable title.
*
* @param string $title
* @return string
*/
public function getQualifiedTitle($title)
{
return Str::title(str_replace(['.', '_'], ' ', Str::snake($title)));
}
/**
* Add a checkbox column.
*
* @param array $attributes
* @return $this
*/
public function addCheckbox(array $attributes = [])
{
$attributes = array_merge([
'defaultContent' => '<input type="checkbox" ' . $this->html->attributes($attributes) . '/>',
'title' => $this->form->checkbox('', '', false, ['id' => 'dataTablesCheckbox']),
'data' => 'checkbox',
'name' => 'checkbox',
'orderable' => false,
'searchable' => false,
'exportable' => false,
'printable' => true,
'width' => '10px',
], $attributes);
$this->collection->push(new Column($attributes));
return $this;
}
/**
* Add a action column.
*
* @param array $attributes
* @return $this
*/
public function addAction(array $attributes = [])
{
$attributes = array_merge([
'defaultContent' => '',
'data' => 'action',
'name' => 'action',
'title' => 'Action',
'render' => null,
'orderable' => false,
'searchable' => false,
'exportable' => false,
'printable' => true,
'footer' => '',
], $attributes);
$this->collection->push(new Column($attributes));
return $this;
}
/**
* Add a index column.
*
* @param array $attributes
* @return $this
*/
public function addIndex(array $attributes = [])
{
$indexColumn = Config::get('datatables.index_column', 'DT_Row_Index');
$attributes = array_merge([
'defaultContent' => '',
'data' => $indexColumn,
'name' => $indexColumn,
'title' => '',
'render' => null,
'orderable' => false,
'searchable' => false,
'exportable' => false,
'printable' => true,
'footer' => '',
], $attributes);
$this->collection->push(new Column($attributes));
return $this;
}
/**
* Setup ajax parameter
*
* @param string|array $attributes
* @return $this
*/
public function ajax($attributes)
{
$this->ajax = $attributes;
return $this;
}
/**
* Generate DataTable's table html.
*
* @param array $attributes
* @param bool $drawFooter
* @return string
*/
public function table(array $attributes = [], $drawFooter = false)
{
$this->tableAttributes = array_merge($this->tableAttributes, $attributes);
$th = $this->compileTableHeaders();
$htmlAttr = $this->html->attributes($this->tableAttributes);
$tableHtml = '<table ' . $htmlAttr . '>';
$tableHtml .= '<thead><tr>' . implode('', $th) . '</tr></thead>';
if ($drawFooter) {
$tf = $this->compileTableFooter();
$tableHtml .= '<tfoot><tr>' . implode('', $tf) . '</tr></tfoot>';
}
$tableHtml .= '</table>';
return $tableHtml;
}
/**
* Compile table headers and to support responsive extension.
*
* @return array
*/
private function compileTableHeaders()
{
$th = [];
foreach ($this->collection->toArray() as $row) {
$thAttr = $this->html->attributes(
array_only($row, ['class', 'id', 'width', 'style', 'data-class', 'data-hide'])
);
$th[] = '<th ' . $thAttr . '>' . $row['title'] . '</th>';
}
return $th;
}
/**
* Compile table footer contents.
*
* @return array
*/
private function compileTableFooter()
{
$footer = [];
foreach ($this->collection->toArray() as $row) {
$footer[] = '<th>' . $row['footer'] . '</th>';
}
return $footer;
}
/**
* Configure DataTable's parameters.
*
* @param array $attributes
* @return $this
*/
public function parameters(array $attributes = [])
{
$this->attributes = array_merge($this->attributes, $attributes);
return $this;
}
/**
* Set custom javascript template.
*
* @param string $template
* @return $this
*/
public function setTemplate($template)
{
$this->template = $template;
return $this;
}
/**
* Get collection of columns.
*
* @return Collection
*/
public function getColumns()
{
return $this->collection;
}
}

View File

@@ -0,0 +1,74 @@
<?php
namespace Yajra\Datatables\Html;
use Illuminate\Support\Fluent;
/**
* Class Column.
*
* @package Yajra\Datatables\Html
* @see https://datatables.net/reference/option/ for possible columns option
* @author Arjay Angeles <aqangeles@gmail.com>
*/
class Column extends Fluent
{
/**
* @param array $attributes
*/
public function __construct($attributes = [])
{
$attributes['orderable'] = isset($attributes['orderable']) ? $attributes['orderable'] : true;
$attributes['searchable'] = isset($attributes['searchable']) ? $attributes['searchable'] : true;
$attributes['exportable'] = isset($attributes['exportable']) ? $attributes['exportable'] : true;
$attributes['printable'] = isset($attributes['printable']) ? $attributes['printable'] : true;
$attributes['footer'] = isset($attributes['footer']) ? $attributes['footer'] : '';
// Allow methods override attribute value
foreach ($attributes as $attribute => $value) {
$method = 'parse' . ucfirst(strtolower($attribute));
if (method_exists($this, $method)) {
$attributes[$attribute] = $this->$method($value);
}
}
parent::__construct($attributes);
}
/**
* Parse render attribute.
*
* @param mixed $value
* @return string|null
*/
public function parseRender($value)
{
/** @var \Illuminate\Contracts\View\Factory $view */
$view = app('view');
$parameters = [];
if (is_array($value)) {
$parameters = array_except($value, 0);
$value = $value[0];
}
if (is_callable($value)) {
return $value($parameters);
} elseif ($view->exists($value)) {
return $view->make($value)->with($parameters)->render();
}
return $value ? $this->parseRenderAsString($value) : null;
}
/**
* Display render value as is.
*
* @param mixed $value
* @return string
*/
private function parseRenderAsString($value)
{
return "function(data,type,full,meta){return $value;}";
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace Yajra\Datatables\Html;
use Illuminate\Support\Fluent;
/**
* Class Parameters.
*
* @package Yajra\Datatables\Html
* @see https://datatables.net/reference/option/ for possible columns option
* @author Arjay Angeles <aqangeles@gmail.com>
*/
class Parameters extends Fluent
{
/**
* @var array
*/
protected $attributes = [
'serverSide' => true,
'processing' => true,
'ajax' => '',
'columns' => []
];
}

View File

@@ -0,0 +1,244 @@
<?php
namespace Yajra\Datatables\Processors;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Config;
use Yajra\Datatables\Helper;
/**
* Class DataProcessor.
*
* @package Yajra\Datatables
* @author Arjay Angeles <aqangeles@gmail.com>
*/
class DataProcessor
{
/**
* @var int
*/
protected $start;
/**
* Columns to escape value.
*
* @var array
*/
protected $escapeColumns = [];
/**
* Processed data output
*
* @var array
*/
protected $output = [];
/**
* @var array
*/
protected $appendColumns = [];
/**
* @var array
*/
protected $editColumns = [];
/**
* @var array
*/
protected $excessColumns = [];
/**
* @var mixed
*/
protected $results;
/**
* @var array
*/
protected $templates;
/**
* @var bool
*/
protected $includeIndex;
/**
* @param mixed $results
* @param array $columnDef
* @param array $templates
* @param int $start
*/
public function __construct($results, array $columnDef, array $templates, $start)
{
$this->results = $results;
$this->appendColumns = $columnDef['append'];
$this->editColumns = $columnDef['edit'];
$this->excessColumns = $columnDef['excess'];
$this->escapeColumns = $columnDef['escape'];
$this->includeIndex = $columnDef['index'];
$this->templates = $templates;
$this->start = $start;
}
/**
* Process data to output on browser
*
* @param bool $object
* @return array
*/
public function process($object = false)
{
$this->output = [];
$indexColumn = Config::get('datatables.index_column', 'DT_Row_Index');
foreach ($this->results as $row) {
$data = Helper::convertToArray($row);
$value = $this->addColumns($data, $row);
$value = $this->editColumns($value, $row);
$value = $this->setupRowVariables($value, $row);
$value = $this->removeExcessColumns($value);
if ($this->includeIndex) {
$value[$indexColumn] = ++$this->start;
}
$this->output[] = $object ? $value : $this->flatten($value);
}
return $this->escapeColumns($this->output);
}
/**
* Process add columns.
*
* @param mixed $data
* @param mixed $row
* @return array
*/
protected function addColumns($data, $row)
{
foreach ($this->appendColumns as $key => $value) {
$value['content'] = Helper::compileContent($value['content'], $data, $row);
$data = Helper::includeInArray($value, $data);
}
return $data;
}
/**
* Process edit columns.
*
* @param mixed $data
* @param mixed $row
* @return array
*/
protected function editColumns($data, $row)
{
foreach ($this->editColumns as $key => $value) {
$value['content'] = Helper::compileContent($value['content'], $data, $row);
Arr::set($data, $value['name'], $value['content']);
}
return $data;
}
/**
* Setup additional DT row variables.
*
* @param mixed $data
* @param mixed $row
* @return array
*/
protected function setupRowVariables($data, $row)
{
$processor = new RowProcessor($data, $row);
return $processor
->rowValue('DT_RowId', $this->templates['DT_RowId'])
->rowValue('DT_RowClass', $this->templates['DT_RowClass'])
->rowData('DT_RowData', $this->templates['DT_RowData'])
->rowData('DT_RowAttr', $this->templates['DT_RowAttr'])
->getData();
}
/**
* Remove declared hidden columns.
*
* @param array $data
* @return array
*/
protected function removeExcessColumns(array $data)
{
foreach ($this->excessColumns as $value) {
unset($data[$value]);
}
return $data;
}
/**
* Flatten array with exceptions.
*
* @param array $array
* @return array
*/
public function flatten(array $array)
{
$return = [];
$exceptions = ['DT_RowId', 'DT_RowClass', 'DT_RowData', 'DT_RowAttr'];
foreach ($array as $key => $value) {
if (in_array($key, $exceptions)) {
$return[$key] = $value;
} else {
$return[] = $value;
}
}
return $return;
}
/**
* Escape column values as declared.
*
* @param array $output
* @return array
*/
protected function escapeColumns(array $output)
{
return array_map(function ($row) {
if ($this->escapeColumns == '*') {
$row = $this->escapeRow($row, $this->escapeColumns);
} else {
foreach ($this->escapeColumns as $key) {
if (array_get($row, $key)) {
array_set($row, $key, e(array_get($row, $key)));
}
}
}
return $row;
}, $output);
}
/**
* Escape all values of row.
*
* @param array $row
* @param string|array $escapeColumns
* @return array
*/
protected function escapeRow(array $row, $escapeColumns)
{
foreach ($row as $key => $value) {
if (is_array($value)) {
$row[$key] = $this->escapeRow($value, $escapeColumns);
} else {
$row[$key] = e($value);
}
}
return $row;
}
}

View File

@@ -0,0 +1,82 @@
<?php
namespace Yajra\Datatables\Processors;
use Illuminate\Support\Arr;
use Yajra\Datatables\Helper;
/**
* Class RowProcessor.
*
* @package Yajra\Datatables
* @author Arjay Angeles <aqangeles@gmail.com>
*/
class RowProcessor
{
/**
* @var mixed
*/
private $data;
/**
* @var mixed
*/
private $row;
/**
* @param mixed $data
* @param mixed $row
*/
public function __construct($data, $row)
{
$this->data = $data;
$this->row = $row;
}
/**
* Process DT RowId and Class value.
*
* @param string $attribute
* @param string|callable $template
* @return $this
*/
public function rowValue($attribute, $template)
{
if (! empty($template)) {
if (! is_callable($template) && Arr::get($this->data, $template)) {
$this->data[$attribute] = Arr::get($this->data, $template);
} else {
$this->data[$attribute] = Helper::compileContent($template, $this->data, $this->row);
}
}
return $this;
}
/**
* Process DT Row Data and Attr.
*
* @param string $attribute
* @param array $template
* @return $this
*/
public function rowData($attribute, array $template)
{
if (count($template)) {
$this->data[$attribute] = [];
foreach ($template as $key => $value) {
$this->data[$attribute][$key] = Helper::compileContent($value, $this->data, $this->row);
}
}
return $this;
}
/**
* @return mixed
*/
public function getData()
{
return $this->data;
}
}

View File

@@ -0,0 +1,173 @@
<?php
namespace Yajra\Datatables;
use Exception;
use Illuminate\Http\Request as IlluminateRequest;
/**
* Class Request.
*
* @property array columns
* @package Yajra\Datatables
* @author Arjay Angeles <aqangeles@gmail.com>
*/
class Request extends IlluminateRequest
{
/**
* Check if request uses legacy code
*
* @throws Exception
*/
public function checkLegacyCode()
{
if (! $this->get('draw') && $this->get('sEcho')) {
throw new Exception('DataTables legacy code is not supported! Please use DataTables 1.10++ coding convention.');
} elseif (! $this->get('draw') && ! $this->get('columns')) {
throw new Exception('Insufficient parameters');
}
}
/**
* Check if Datatables is searchable.
*
* @return bool
*/
public function isSearchable()
{
return $this->get('search')['value'] != '';
}
/**
* Get column's search value.
*
* @param integer $index
* @return string
*/
public function columnKeyword($index)
{
return $this->columns[$index]['search']['value'];
}
/**
* Check if Datatables must uses regular expressions
*
* @param integer $index
* @return string
*/
public function isRegex($index)
{
return $this->columns[$index]['search']['regex'] === 'true';
}
/**
* Get orderable columns
*
* @return array
*/
public function orderableColumns()
{
if (! $this->isOrderable()) {
return [];
}
$orderable = [];
for ($i = 0, $c = count($this->get('order')); $i < $c; $i++) {
$order_col = (int) $this->get('order')[$i]['column'];
$order_dir = $this->get('order')[$i]['dir'];
if ($this->isColumnOrderable($order_col)) {
$orderable[] = ['column' => $order_col, 'direction' => $order_dir];
}
}
return $orderable;
}
/**
* Check if Datatables ordering is enabled.
*
* @return bool
*/
public function isOrderable()
{
return $this->get('order') && count($this->get('order')) > 0;
}
/**
* Check if a column is orderable.
*
* @param integer $index
* @return bool
*/
public function isColumnOrderable($index)
{
return $this->get('columns')[$index]['orderable'] == 'true';
}
/**
* Get searchable column indexes
*
* @return array
*/
public function searchableColumnIndex()
{
$searchable = [];
for ($i = 0, $c = count($this->get('columns')); $i < $c; $i++) {
if ($this->isColumnSearchable($i, false)) {
$searchable[] = $i;
}
}
return $searchable;
}
/**
* Check if a column is searchable.
*
* @param integer $i
* @param bool $column_search
* @return bool
*/
public function isColumnSearchable($i, $column_search = true)
{
$columns = $this->get('columns');
if ($column_search) {
return $columns[$i]['searchable'] == 'true' && $columns[$i]['search']['value'] != '';
}
return $columns[$i]['searchable'] == 'true';
}
/**
* Get global search keyword
*
* @return string
*/
public function keyword()
{
return $this->get('search')['value'];
}
/**
* Get column identity from input or database.
*
* @param integer $i
* @return string
*/
public function columnName($i)
{
$column = $this->get('columns')[$i];
return isset($column['name']) && $column['name'] <> '' ? $column['name'] : $column['data'];
}
/**
* Check if Datatables allow pagination.
*
* @return bool
*/
public function isPaginationable()
{
return ! is_null($this->get('start')) && ! is_null($this->get('length')) && $this->get('length') != -1;
}
}

View File

@@ -0,0 +1,385 @@
<?php
namespace Yajra\Datatables\Services;
use Illuminate\Contracts\View\Factory;
use Illuminate\Support\Facades\Config;
use Maatwebsite\Excel\Classes\LaravelExcelWorksheet;
use Maatwebsite\Excel\Writers\LaravelExcelWriter;
use Yajra\Datatables\Contracts\DataTableButtonsContract;
use Yajra\Datatables\Contracts\DataTableContract;
use Yajra\Datatables\Contracts\DataTableScopeContract;
use Yajra\Datatables\Datatables;
use Yajra\Datatables\Transformers\DataTransformer;
/**
* Class DataTable.
*
* @package Yajra\Datatables\Services
* @author Arjay Angeles <aqangeles@gmail.com>
*/
abstract class DataTable implements DataTableContract, DataTableButtonsContract
{
/**
* @var \Yajra\Datatables\Datatables
*/
protected $datatables;
/**
* @var \Illuminate\Contracts\View\Factory
*/
protected $viewFactory;
/**
* Datatables print preview view.
*
* @var string
*/
protected $printPreview = 'datatables::print';
/**
* List of columns to be exported.
*
* @var string|array
*/
protected $exportColumns = '*';
/**
* List of columns to be printed.
*
* @var string|array
*/
protected $printColumns = '*';
/**
* Query scopes.
*
* @var \Yajra\Datatables\Contracts\DataTableScopeContract[]
*/
protected $scopes = [];
/**
* Export filename.
*
* @var string
*/
protected $filename = '';
/**
* DataTable constructor.
*
* @param \Yajra\Datatables\Datatables $datatables
* @param \Illuminate\Contracts\View\Factory $viewFactory
*/
public function __construct(Datatables $datatables, Factory $viewFactory)
{
$this->datatables = $datatables;
$this->viewFactory = $viewFactory;
}
/**
* Process dataTables needed render output.
*
* @param string $view
* @param array $data
* @param array $mergeData
* @return \Illuminate\Http\JsonResponse|\Illuminate\View\View
*/
public function render($view, $data = [], $mergeData = [])
{
if ($this->request()->ajax() && $this->request()->wantsJson()) {
return $this->ajax();
}
if ($action = $this->request()->get('action') AND in_array($action, ['print', 'csv', 'excel', 'pdf'])) {
if ($action == 'print') {
return $this->printPreview();
}
return call_user_func_array([$this, $action], []);
}
return $this->viewFactory->make($view, $data, $mergeData)->with('dataTable', $this->html());
}
/**
* Get Datatables Request instance.
*
* @return \Yajra\Datatables\Request
*/
public function request()
{
return $this->datatables->getRequest();
}
/**
* Display printable view of datatables.
*
* @return \Illuminate\Contracts\View\View
*/
public function printPreview()
{
$data = $this->getDataForPrint();
return $this->viewFactory->make($this->printPreview, compact('data'));
}
/**
* Get mapped columns versus final decorated output.
*
* @return array
*/
protected function getDataForPrint()
{
$columns = $this->printColumns();
return $this->mapResponseToColumns($columns, 'printable');
}
/**
* Get printable columns.
*
* @return array|string
*/
protected function printColumns()
{
return is_array($this->printColumns) ? $this->printColumns : $this->getColumnsFromBuilder();
}
/**
* Get columns definition from html builder.
*
* @return array
*/
protected function getColumnsFromBuilder()
{
return $this->html()->getColumns();
}
/**
* Optional method if you want to use html builder.
*
* @return \Yajra\Datatables\Html\Builder
*/
public function html()
{
return $this->builder();
}
/**
* Get Datatables Html Builder instance.
*
* @return \Yajra\Datatables\Html\Builder
*/
public function builder()
{
return $this->datatables->getHtmlBuilder();
}
/**
* Map ajax response to columns definition.
*
* @param mixed $columns
* @param string $type
* @return array
*/
protected function mapResponseToColumns($columns, $type)
{
return array_map(function ($row) use ($columns, $type) {
if ($columns) {
return (new DataTransformer())->transform($row, $columns, $type);
}
return $row;
}, $this->getAjaxResponseData());
}
/**
* Get decorated data as defined in datatables ajax response.
*
* @return array
*/
protected function getAjaxResponseData()
{
$this->datatables->getRequest()->merge(['length' => -1]);
$response = $this->ajax();
$data = $response->getData(true);
return $data['data'];
}
/**
* Export results to Excel file.
*
* @return void
*/
public function excel()
{
$this->buildExcelFile()->download('xls');
}
/**
* Build excel file and prepare for export.
*
* @return \Maatwebsite\Excel\Writers\LaravelExcelWriter
*/
protected function buildExcelFile()
{
/** @var \Maatwebsite\Excel\Excel $excel */
$excel = app('excel');
return $excel->create($this->getFilename(), function (LaravelExcelWriter $excel) {
$excel->sheet('exported-data', function (LaravelExcelWorksheet $sheet) {
$sheet->fromArray($this->getDataForExport());
});
});
}
/**
* Get export filename.
*
* @return string
*/
public function getFilename()
{
return $this->filename ?: $this->filename();
}
/**
* Set export filename.
*
* @param string $filename
* @return DataTable
*/
public function setFilename($filename)
{
$this->filename = $filename;
return $this;
}
/**
* Get filename for export.
*
* @return string
*/
protected function filename()
{
return 'export_' . time();
}
/**
* Get mapped columns versus final decorated output.
*
* @return array
*/
protected function getDataForExport()
{
$columns = $this->exportColumns();
return $this->mapResponseToColumns($columns, 'exportable');
}
/**
* Get export columns definition.
*
* @return array|string
*/
private function exportColumns()
{
return is_array($this->exportColumns) ? $this->exportColumns : $this->getColumnsFromBuilder();
}
/**
* Export results to CSV file.
*
* @return void
*/
public function csv()
{
$this->buildExcelFile()->download('csv');
}
/**
* Export results to PDF file.
*
* @return mixed
*/
public function pdf()
{
if ('snappy' == Config::get('datatables.pdf_generator', 'excel')) {
return $this->snappyPdf();
} else {
$this->buildExcelFile()->download('pdf');
}
}
/**
* PDF version of the table using print preview blade template.
*
* @return mixed
*/
public function snappyPdf()
{
$data = $this->getDataForPrint();
$snappy = app('snappy.pdf.wrapper');
$snappy->setOptions([
'no-outline' => true,
'margin-left' => '0',
'margin-right' => '0',
'margin-top' => '10mm',
'margin-bottom' => '10mm',
])->setOrientation('landscape');
return $snappy->loadView($this->printPreview, compact('data'))
->download($this->getFilename() . ".pdf");
}
/**
* Add basic array query scopes.
*
* @param \Yajra\Datatables\Contracts\DataTableScopeContract $scope
* @return $this
*/
public function addScope(DataTableScopeContract $scope)
{
$this->scopes[] = $scope;
return $this;
}
/**
* Apply query scopes.
*
* @param \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $query
* @return mixed
*/
protected function applyScopes($query)
{
foreach ($this->scopes as $scope) {
$scope->apply($query);
}
return $query;
}
/**
* Get default builder parameters.
*
* @return array
*/
protected function getBuilderParameters()
{
return [
'order' => [[0, 'desc']],
'buttons' => [
'create',
'export',
'print',
'reset',
'reload',
],
];
}
}

View File

@@ -0,0 +1,75 @@
<?php
namespace Yajra\Datatables\Transformers;
use Illuminate\Support\Collection;
/**
* Class DataTransformer.
*
* @package Yajra\Datatables\Transformers
* @author Arjay Angeles <aqangeles@gmail.com>
*/
class DataTransformer
{
/**
* Transform row data by columns definition.
*
* @param array $row
* @param mixed $columns
* @param string $type
* @return array
*/
public function transform(array $row, $columns, $type = 'printable')
{
if ($columns instanceof Collection) {
return $this->buildColumnByCollection($row, $columns, $type);
}
return array_only($row, $columns);
}
/**
* Transform row column by collection.
*
* @param array $row
* @param \Illuminate\Support\Collection $columns
* @param string $type
* @return array
*/
protected function buildColumnByCollection(array $row, Collection $columns, $type = 'printable')
{
$results = [];
foreach ($columns->all() as $column) {
if ($column[$type]) {
$title = $column['title'];
$data = array_get($row, $column['data']);
if ($type == 'exportable') {
$data = $this->decodeContent($data);
$title = $this->decodeContent($title);
}
$results[$title] = $data;
}
}
return $results;
}
/**
* Decode content to a readable text value.
*
* @param string $data
* @return string
*/
protected function decodeContent($data)
{
try {
$decoded = html_entity_decode(strip_tags($data), ENT_QUOTES, 'UTF-8');
return str_replace("\xc2\xa0", ' ', $decoded);
} catch (\Exception $e) {
return $data;
}
}
}

View File

@@ -0,0 +1,86 @@
<?php
return [
/**
* DataTables search options.
*/
'search' => [
/**
* Smart search will enclose search keyword with wildcard string "%keyword%".
* SQL: column LIKE "%keyword%"
*/
'smart' => true,
/**
* Case insensitive will search the keyword in lower case format.
* SQL: LOWER(column) LIKE LOWER(keyword)
*/
'case_insensitive' => true,
/**
* Wild card will add "%" in between every characters of the keyword.
* SQL: column LIKE "%k%e%y%w%o%r%d%"
*/
'use_wildcards' => false,
],
/**
* DataTables fractal configurations.
*/
'fractal' => [
/**
* Request key name to parse includes on fractal.
*/
'includes' => 'include',
/**
* Default fractal serializer.
*/
'serializer' => 'League\Fractal\Serializer\DataArraySerializer',
],
/**
* DataTables script view template.
*/
'script_template' => 'datatables::script',
/**
* DataTables internal index id response column name.
*/
'index_column' => 'DT_Row_Index',
/**
* Namespaces used by the generator.
*/
'namespace' => [
/**
* Base namespace/directory to create the new file.
* This is appended on default Laravel namespace.
*
* Usage: php artisan datatables:make User
* Output: App\DataTables\UserDataTable
* With Model: App\User (default model)
* Export filename: users_timestamp
*/
'base' => 'DataTables',
/**
* Base namespace/directory where your model's are located.
* This is appended on default Laravel namespace.
*
* Usage: php artisan datatables:make Post --model
* Output: App\DataTables\PostDataTable
* With Model: App\Post
* Export filename: posts_timestamp
*/
'model' => '',
],
/**
* PDF generator to be used when converting the table to pdf.
* Available generators: excel, snappy
* Snappy package: barryvdh/laravel-snappy
* Excel package: maatwebsite/excel
*/
'pdf_generator' => 'excel',
];

View File

@@ -0,0 +1,27 @@
<?php
if (! function_exists('config_path')) {
/**
* Get the configuration path.
*
* @param string $path
* @return string
*/
function config_path($path = '')
{
return app()->basePath() . '/config' . ($path ? '/' . $path : $path);
}
}
if (! function_exists('public_path')) {
/**
* Return the path to public dir
*
* @param null $path
* @return string
*/
function public_path($path = null)
{
return rtrim(app()->basePath('public/' . $path), '/');
}
}

View File

@@ -0,0 +1,111 @@
(function ($, DataTable) {
"use strict";
var _buildUrl = function(dt, action) {
var url = dt.ajax.url() || '';
var params = dt.ajax.params();
params.action = action;
return url + '?' + $.param(params);
};
DataTable.ext.buttons.excel = {
className: 'buttons-excel',
text: function (dt) {
return '<i class="fa fa-file-excel-o"></i> ' + dt.i18n('buttons.excel', 'Excel');
},
action: function (e, dt, button, config) {
var url = _buildUrl(dt, 'excel');
window.location = url;
}
};
DataTable.ext.buttons.export = {
extend: 'collection',
className: 'buttons-export',
text: function (dt) {
return '<i class="fa fa-download"></i> ' + dt.i18n('buttons.export', 'Export') + '&nbsp;<span class="caret"/>';
},
buttons: ['csv', 'excel', 'pdf']
};
DataTable.ext.buttons.csv = {
className: 'buttons-csv',
text: function (dt) {
return '<i class="fa fa-file-excel-o"></i> ' + dt.i18n('buttons.csv', 'CSV');
},
action: function (e, dt, button, config) {
var url = _buildUrl(dt, 'csv');
window.location = url;
}
};
DataTable.ext.buttons.pdf = {
className: 'buttons-pdf',
text: function (dt) {
return '<i class="fa fa-file-pdf-o"></i> ' + dt.i18n('buttons.pdf', 'PDF');
},
action: function (e, dt, button, config) {
var url = _buildUrl(dt, 'pdf');
window.location = url;
}
};
DataTable.ext.buttons.print = {
className: 'buttons-print',
text: function (dt) {
return '<i class="fa fa-print"></i> ' + dt.i18n('buttons.print', 'Print');
},
action: function (e, dt, button, config) {
var url = _buildUrl(dt, 'print');
window.location = url;
}
};
DataTable.ext.buttons.reset = {
className: 'buttons-reset',
text: function (dt) {
return '<i class="fa fa-undo"></i> ' + dt.i18n('buttons.reset', 'Reset');
},
action: function (e, dt, button, config) {
dt.search('').draw();
}
};
DataTable.ext.buttons.reload = {
className: 'buttons-reload',
text: function (dt) {
return '<i class="fa fa-refresh"></i> ' + dt.i18n('buttons.reload', 'Reload');
},
action: function (e, dt, button, config) {
dt.draw(false);
}
};
DataTable.ext.buttons.create = {
className: 'buttons-create',
text: function (dt) {
return '<i class="fa fa-plus"></i> ' + dt.i18n('buttons.create', 'Create');
},
action: function (e, dt, button, config) {
window.location = window.location.href.replace(/\/+$/, "") + '/create';
}
};
})(jQuery, jQuery.fn.dataTable);

View File

@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Print Table</title>
<meta charset="UTF-8">
<meta name=description content="">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Bootstrap CSS -->
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css">
<style>
body {margin: 20px}
</style>
</head>
<body>
<table class="table table-bordered table-condensed table-striped">
@foreach($data as $row)
@if ($row == reset($data))
<tr>
@foreach($row as $key => $value)
<th>{!! $key !!}</th>
@endforeach
</tr>
@endif
<tr>
@foreach($row as $key => $value)
@if(is_string($value) || is_numeric($value))
<td>{!! $value !!}</td>
@else
<td></td>
@endif
@endforeach
</tr>
@endforeach
</table>
</body>
</html>

View File

@@ -0,0 +1 @@
(function(window,$){window.LaravelDataTables=window.LaravelDataTables||{};window.LaravelDataTables["%1$s"]=$("#%1$s").DataTable(%2$s);})(window,jQuery);