updated-packages

This commit is contained in:
RafficMohammed
2023-01-08 00:13:22 +05:30
parent 3ff7df7487
commit da241bacb6
12659 changed files with 563377 additions and 510538 deletions

View File

@@ -0,0 +1,63 @@
<?php
namespace Yajra\DataTables;
use Illuminate\Support\Collection;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
class ApiResourceDataTable extends CollectionDataTable
{
/**
* Collection object.
*
* @var \Illuminate\Http\Resources\Json\AnonymousResourceCollection
*/
public $collection;
/**
* Collection object.
*
* @var \Illuminate\Http\Resources\Json\AnonymousResourceCollection
*/
public $original;
/**
* Can the DataTable engine be created with these parameters.
*
* @param mixed $source
* @return bool
*/
public static function canCreate($source)
{
return $source instanceof AnonymousResourceCollection;
}
/**
* Factory method, create and return an instance for the DataTable engine.
*
* @param \Illuminate\Http\Resources\Json\AnonymousResourceCollection $source
* @return ApiResourceDataTable|DataTableAbstract
*/
public static function create($source)
{
return parent::create($source);
}
/**
* CollectionEngine constructor.
*
* @param \Illuminate\Http\Resources\Json\AnonymousResourceCollection $collection
*/
public function __construct(AnonymousResourceCollection $collection)
{
$this->request = app('datatables.request');
$this->config = app('datatables.config');
$this->collection = collect($collection->toArray($this->request));
$this->original = $collection;
$this->columns = array_keys($this->serialize(collect($collection->toArray($this->request))->first()));
if ($collection->resource instanceof LengthAwarePaginator) {
$this->isFilterApplied = true;
}
}
}

View File

@@ -0,0 +1,338 @@
<?php
namespace Yajra\DataTables;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Support\Collection;
use Illuminate\Contracts\Support\Arrayable;
class CollectionDataTable extends DataTableAbstract
{
/**
* Collection object.
*
* @var \Illuminate\Support\Collection
*/
public $collection;
/**
* Collection object.
*
* @var \Illuminate\Support\Collection
*/
public $original;
/**
* The offset of the first record in the full dataset.
*
* @var int
*/
private $offset = 0;
/**
* Can the DataTable engine be created with these parameters.
*
* @param mixed $source
* @return bool
*/
public static function canCreate($source)
{
return is_array($source) || $source instanceof Collection;
}
/**
* Factory method, create and return an instance for the DataTable engine.
*
* @param array|\Illuminate\Support\Collection $source
* @return CollectionDataTable|DataTableAbstract
*/
public static function create($source)
{
if (is_array($source)) {
$source = new Collection($source);
}
return parent::create($source);
}
/**
* CollectionEngine constructor.
*
* @param \Illuminate\Support\Collection $collection
*/
public function __construct(Collection $collection)
{
$this->request = app('datatables.request');
$this->config = app('datatables.config');
$this->collection = $collection;
$this->original = $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;
}
/**
* Count results.
*
* @return int
*/
public function count()
{
return $this->collection->count() > $this->totalRecords ? $this->totalRecords : $this->collection->count();
}
/**
* Perform column search.
*
* @return void
*/
public function columnSearch()
{
$columns = $this->request->get('columns', []);
for ($i = 0, $c = count($columns); $i < $c; $i++) {
$column = $this->getColumnName($i);
if (! $this->request->isColumnSearchable($i) || $this->isBlacklisted($column)) {
continue;
}
$this->isFilterApplied = true;
$regex = $this->request->isRegex($i);
$keyword = $this->request->columnKeyword($i);
$this->collection = $this->collection->filter(
function ($row) use ($column, $keyword, $regex) {
$data = $this->serialize($row);
$value = Arr::get($data, $column);
if ($this->config->isCaseInsensitive()) {
if ($regex) {
return preg_match('/' . $keyword . '/i', $value) == 1;
}
return strpos(Str::lower($value), Str::lower($keyword)) !== false;
}
if ($regex) {
return preg_match('/' . $keyword . '/', $value) == 1;
}
return strpos($value, $keyword) !== false;
}
);
}
}
/**
* Perform pagination.
*
* @return void
*/
public function paging()
{
$this->collection = $this->collection->slice(
$this->request->input('start') - $this->offset,
(int) $this->request->input('length') > 0 ? $this->request->input('length') : 10
);
}
/**
* Organizes works.
*
* @param bool $mDataSupport
* @return \Illuminate\Http\JsonResponse
*/
public function make($mDataSupport = true)
{
try {
$this->totalRecords = $this->totalCount();
if ($this->totalRecords) {
$results = $this->results();
$processed = $this->processResults($results, $mDataSupport);
$output = $this->transform($results, $processed);
$this->collection = collect($output);
$this->ordering();
$this->filterRecords();
$this->paginate();
$this->revertIndexColumn($mDataSupport);
}
return $this->render($this->collection->values()->all());
} catch (\Exception $exception) {
return $this->errorResponse($exception);
}
}
/**
* Count total items.
*
* @return int
*/
public function totalCount()
{
return $this->totalRecords ? $this->totalRecords : $this->collection->count();
}
/**
* Get results.
*
* @return mixed
*/
public function results()
{
return $this->collection->all();
}
/**
* Revert transformed DT_RowIndex back to it's original values.
*
* @param bool $mDataSupport
*/
private function revertIndexColumn($mDataSupport)
{
if ($this->columnDef['index']) {
$index = $mDataSupport ? config('datatables.index_column', 'DT_RowIndex') : 0;
$start = (int) $this->request->input('start');
$this->collection->transform(function ($data) use ($index, &$start) {
$data[$index] = ++$start;
return $data;
});
}
}
/**
* Perform global search for the given keyword.
*
* @param string $keyword
*/
protected function globalSearch($keyword)
{
$keyword = $this->config->isCaseInsensitive() ? Str::lower($keyword) : $keyword;
$this->collection = $this->collection->filter(function ($row) use ($keyword) {
$this->isFilterApplied = true;
$data = $this->serialize($row);
foreach ($this->request->searchableColumnIndex() as $index) {
$column = $this->getColumnName($index);
$value = Arr::get($data, $column);
if (! $value || is_array($value)) {
if (! is_numeric($value)) {
continue;
}
$value = (string) $value;
}
$value = $this->config->isCaseInsensitive() ? Str::lower($value) : $value;
if (Str::contains($value, $keyword)) {
return true;
}
}
return false;
});
}
/**
* Perform default query orderBy clause.
*/
protected function defaultOrdering()
{
$criteria = $this->request->orderableColumns();
if (! empty($criteria)) {
$sorter = $this->getSorter($criteria);
$this->collection = $this->collection
->map(function ($data) {
return Arr::dot($data);
})
->sort($sorter)
->map(function ($data) {
foreach ($data as $key => $value) {
unset($data[$key]);
Arr::set($data, $key, $value);
}
return $data;
});
}
}
/**
* Get array sorter closure.
*
* @param array $criteria
* @return \Closure
*/
protected function getSorter(array $criteria)
{
$sorter = function ($a, $b) use ($criteria) {
foreach ($criteria as $orderable) {
$column = $this->getColumnName($orderable['column']);
$direction = $orderable['direction'];
if ($direction === 'desc') {
$first = $b;
$second = $a;
} else {
$first = $a;
$second = $b;
}
if ($this->config->isCaseInsensitive()) {
$cmp = strnatcasecmp($first[$column], $second[$column]);
} else {
$cmp = strnatcmp($first[$column], $second[$column]);
}
if ($cmp != 0) {
return $cmp;
}
}
// all elements were equal
return 0;
};
return $sorter;
}
/**
* Resolve callback parameter instance.
*
* @return $this
*/
protected function resolveCallbackParameter()
{
return $this;
}
/**
* Define the offset of the first item of the collection with respect to
* the FULL dataset the collection was sliced from. It effectively allows the
* collection to be "pre-sliced".
*
* @param int $offset
* @return $this
*/
public function setOffset(int $offset)
{
$this->offset = $offset;
return $this;
}
}

View File

@@ -1,14 +1,8 @@
<?php
namespace Yajra\Datatables\Contracts;
namespace Yajra\DataTables\Contracts;
/**
* Interface DataTableEngineContract
*
* @package Yajra\Datatables\Contracts
* @author Arjay Angeles <aqangeles@gmail.com>
*/
interface DataTableEngineContract
interface DataTable
{
/**
* Get results.
@@ -20,14 +14,14 @@ interface DataTableEngineContract
/**
* Count results.
*
* @return integer
* @return int
*/
public function count();
/**
* Count total items.
*
* @return integer
* @return int
*/
public function totalCount();
@@ -35,11 +29,11 @@ interface DataTableEngineContract
* Set auto filter off and run your own filter.
* Overrides global search.
*
* @param \Closure $callback
* @param bool $globalSearch
* @param callable $callback
* @param bool $globalSearch
* @return $this
*/
public function filter(\Closure $callback, $globalSearch = false);
public function filter(callable $callback, $globalSearch = false);
/**
* Perform global search.
@@ -73,8 +67,7 @@ interface DataTableEngineContract
* Organizes works.
*
* @param bool $mDataSupport
* @param bool $orderFirst
* @return \Illuminate\Http\JsonResponse
*/
public function make($mDataSupport = false, $orderFirst = false);
public function make($mDataSupport = true);
}

View File

@@ -1,40 +0,0 @@
<?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

@@ -1,47 +0,0 @@
<?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

@@ -1,20 +0,0 @@
<?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,927 @@
<?php
namespace Yajra\DataTables;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Psr\Log\LoggerInterface;
use Illuminate\Http\JsonResponse;
use Yajra\DataTables\Utilities\Helper;
use Illuminate\Support\Traits\Macroable;
use Yajra\DataTables\Contracts\DataTable;
use Illuminate\Contracts\Support\Jsonable;
use Yajra\DataTables\Exceptions\Exception;
use Illuminate\Contracts\Support\Arrayable;
use Yajra\DataTables\Processors\DataProcessor;
/**
* @method DataTableAbstract setTransformer($transformer)
* @method DataTableAbstract setSerializer($transformer)
* @property mixed transformer
* @property mixed serializer
* @see https://github.com/yajra/laravel-datatables-fractal for transformer related methods.
*/
abstract class DataTableAbstract implements DataTable, Arrayable, Jsonable
{
use Macroable;
/**
* DataTables Request object.
*
* @var \Yajra\DataTables\Utilities\Request
*/
public $request;
/**
* @var \Psr\Log\LoggerInterface
*/
protected $logger;
/**
* 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' => [],
'filter' => [],
'order' => [],
'only' => null,
'hidden' => [],
'visible' => [],
];
/**
* 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 callable
*/
protected $filterCallback;
/**
* DT row templates container.
*
* @var array
*/
protected $templates = [
'DT_RowId' => '',
'DT_RowClass' => '',
'DT_RowData' => [],
'DT_RowAttr' => [],
];
/**
* [internal] Track if any filter was applied for at least one column.
*
* @var bool
*/
protected $isFilterApplied = false;
/**
* Custom ordering callback.
*
* @var callable
*/
protected $orderCallback;
/**
* Skip paginate as needed.
*
* @var bool
*/
protected $skipPaging = false;
/**
* Array of data to append on json response.
*
* @var array
*/
protected $appends = [];
/**
* @var \Yajra\DataTables\Utilities\Config
*/
protected $config;
/**
* @var mixed
*/
protected $serializer;
/**
* Can the DataTable engine be created with these parameters.
*
* @param mixed $source
* @return bool
*/
public static function canCreate($source)
{
return false;
}
/**
* Factory method, create and return an instance for the DataTable engine.
*
* @param mixed $source
* @return DataTableAbstract
*/
public static function create($source)
{
return new static($source);
}
/**
* 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->getColumnsDefinition()['excess'], $names);
return $this;
}
/**
* Get only selected columns in response.
*
* @param array $columns
* @return $this
*/
public function only(array $columns = [])
{
$this->columnDef['only'] = $columns;
return $this;
}
/**
* Declare columns to escape values.
*
* @param string|array $columns
* @return $this
*/
public function escapeColumns($columns = '*')
{
$this->columnDef['escape'] = $columns;
return $this;
}
/**
* Add a makeHidden() to the row object.
*
* @param array $attributes
* @return $this
*/
public function makeHidden(array $attributes = [])
{
$this->columnDef['hidden'] = array_merge_recursive(Arr::get($this->columnDef, 'hidden', []), $attributes);
return $this;
}
/**
* Add a makeVisible() to the row object.
*
* @param array $attributes
* @return $this
*/
public function makeVisible(array $attributes = [])
{
$this->columnDef['visible'] = array_merge_recursive(Arr::get($this->columnDef, 'visible', []), $attributes);
return $this;
}
/**
* Set columns that should not be escaped.
* Optionally merge the defaults from config.
*
* @param array $columns
* @param bool $merge
* @return $this
*/
public function rawColumns(array $columns, $merge = false)
{
if ($merge) {
$config = $this->config->get('datatables.columns');
$this->columnDef['raw'] = array_merge($config['raw'], $columns);
} else {
$this->columnDef['raw'] = $columns;
}
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;
}
/**
* 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;
}
/**
* Add with query callback value on response.
*
* @param string $key
* @param callable $value
* @return $this
*/
public function withQuery($key, callable $value)
{
$this->appends[$key] = $value;
return $this;
}
/**
* Override default ordering method with a closure callback.
*
* @param callable $closure
* @return $this
*/
public function order(callable $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 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 $state
* @return $this
*/
public function smart($state = true)
{
$this->config->set('datatables.search.smart', $state);
return $this;
}
/**
* Set starts_with search config at runtime.
*
* @param bool $state
* @return $this
*/
public function startsWithSearch($state = true)
{
$this->config->set('datatables.search.starts_with', $state);
return $this;
}
/**
* Set total records manually.
*
* @param int $total
* @return $this
*/
public function setTotalRecords($total)
{
$this->totalRecords = $total;
return $this;
}
/**
* Set filtered records manually.
*
* @param int $total
* @return $this
*/
public function setFilteredRecords($total)
{
$this->filteredRecords = $total;
return $this;
}
/**
* Skip pagination as needed.
*
* @return $this
*/
public function skipPaging()
{
$this->skipPaging = true;
return $this;
}
/**
* Push a new column name to blacklist.
*
* @param string $column
* @return $this
*/
public function pushToBlacklist($column)
{
if (! $this->isBlacklisted($column)) {
$this->columnDef['blacklist'][] = $column;
}
return $this;
}
/**
* Check if column is blacklisted.
*
* @param string $column
* @return bool
*/
protected function isBlacklisted($column)
{
$colDef = $this->getColumnsDefinition();
if (in_array($column, $colDef['blacklist'])) {
return true;
}
if ($colDef['whitelist'] === '*' || in_array($column, $colDef['whitelist'])) {
return false;
}
return true;
}
/**
* Get columns definition.
*
* @return array
*/
protected function getColumnsDefinition()
{
$config = $this->config->get('datatables.columns');
$allowed = ['excess', 'escape', 'raw', 'blacklist', 'whitelist'];
return array_replace_recursive(Arr::only($config, $allowed), $this->columnDef);
}
/**
* Perform sorting of columns.
*/
public function ordering()
{
if ($this->orderCallback) {
return call_user_func($this->orderCallback, $this->resolveCallbackParameter());
}
return $this->defaultOrdering();
}
/**
* Resolve callback parameter instance.
*
* @return mixed
*/
abstract protected function resolveCallbackParameter();
/**
* Perform default query orderBy clause.
*/
abstract protected function defaultOrdering();
/**
* Set auto filter off and run your own filter.
* Overrides global search.
*
* @param callable $callback
* @param bool $globalSearch
* @return $this
*/
public function filter(callable $callback, $globalSearch = false)
{
$this->autoFilter = $globalSearch;
$this->isFilterApplied = true;
$this->filterCallback = $callback;
return $this;
}
/**
* Convert instance to array.
*
* @return array
*/
public function toArray()
{
return $this->make()->getData(true);
}
/**
* Convert the object to its JSON representation.
*
* @param int $options
* @return \Illuminate\Http\JsonResponse
*/
public function toJson($options = 0)
{
if ($options) {
$this->config->set('datatables.json.options', $options);
}
return $this->make();
}
/**
* Count filtered items.
*
* @return int
*/
protected function filteredCount()
{
return $this->filteredRecords ? $this->filteredRecords : $this->count();
}
/**
* Perform necessary filters.
*
* @return void
*/
protected function filterRecords()
{
if ($this->autoFilter && $this->request->isSearchable()) {
$this->filtering();
}
if (is_callable($this->filterCallback)) {
call_user_func($this->filterCallback, $this->resolveCallbackParameter());
}
$this->columnSearch();
$this->filteredRecords = $this->isFilterApplied ? $this->filteredCount() : $this->totalRecords;
}
/**
* Perform global search.
*
* @return void
*/
public function filtering()
{
$keyword = $this->request->keyword();
if ($this->config->isMultiTerm()) {
$this->smartGlobalSearch($keyword);
return;
}
$this->globalSearch($keyword);
}
/**
* Perform multi-term search by splitting keyword into
* individual words and searches for each of them.
*
* @param string $keyword
*/
protected function smartGlobalSearch($keyword)
{
collect(explode(' ', $keyword))
->reject(function ($keyword) {
return trim($keyword) === '';
})
->each(function ($keyword) {
$this->globalSearch($keyword);
});
}
/**
* Perform global search for the given keyword.
*
* @param string $keyword
*/
abstract protected function globalSearch($keyword);
/**
* Apply pagination.
*
* @return void
*/
protected function paginate()
{
if ($this->request->isPaginationable() && ! $this->skipPaging) {
$this->paging();
}
}
/**
* Transform output.
*
* @param mixed $results
* @param mixed $processed
* @return array
*/
protected function transform($results, $processed)
{
if (isset($this->transformer) && class_exists('Yajra\\DataTables\\Transformers\\FractalTransformer')) {
return app('datatables.transformer')->transform(
$results,
$this->transformer,
$this->serializer ?? null
);
}
return Helper::transform($processed);
}
/**
* Get processed data.
*
* @param mixed $results
* @param bool $object
* @return array
*/
protected function processResults($results, $object = false)
{
$processor = new DataProcessor(
$results,
$this->getColumnsDefinition(),
$this->templates,
$this->request->input('start')
);
return $processor->process($object);
}
/**
* Render json response.
*
* @param array $data
* @return \Illuminate\Http\JsonResponse
*/
protected function render(array $data)
{
$output = $this->attachAppends([
'draw' => (int) $this->request->input('draw'),
'recordsTotal' => $this->totalRecords,
'recordsFiltered' => $this->filteredRecords,
'data' => $data,
]);
if ($this->config->isDebugging()) {
$output = $this->showDebugger($output);
}
return new JsonResponse(
$output,
200,
$this->config->get('datatables.json.header', []),
$this->config->get('datatables.json.options', 0)
);
}
/**
* Attach custom with meta on response.
*
* @param array $data
* @return array
*/
protected function attachAppends(array $data)
{
return array_merge($data, $this->appends);
}
/**
* Append debug parameters on output.
*
* @param array $output
* @return array
*/
protected function showDebugger(array $output)
{
$output['input'] = $this->request->all();
return $output;
}
/**
* Return an error json response.
*
* @param \Exception $exception
* @return \Illuminate\Http\JsonResponse
* @throws \Yajra\DataTables\Exceptions\Exception
*/
protected function errorResponse(\Exception $exception)
{
$error = $this->config->get('datatables.error');
$debug = $this->config->get('app.debug');
if ($error === 'throw' || (! $error && ! $debug)) {
throw new Exception($exception->getMessage(), $code = 0, $exception);
}
$this->getLogger()->error($exception);
return new JsonResponse([
'draw' => (int) $this->request->input('draw'),
'recordsTotal' => $this->totalRecords,
'recordsFiltered' => 0,
'data' => [],
'error' => $error ? __($error) : "Exception Message:\n\n".$exception->getMessage(),
]);
}
/**
* Get monolog/logger instance.
*
* @return \Psr\Log\LoggerInterface
*/
public function getLogger()
{
$this->logger = $this->logger ?: app(LoggerInterface::class);
return $this->logger;
}
/**
* Set monolog/logger instance.
*
* @param \Psr\Log\LoggerInterface $logger
* @return $this
*/
public function setLogger(LoggerInterface $logger)
{
$this->logger = $logger;
return $this;
}
/**
* Setup search keyword.
*
* @param string $value
* @return string
*/
protected function setupKeyword($value)
{
if ($this->config->isSmartSearch()) {
$keyword = '%'.$value.'%';
if ($this->config->isWildcard()) {
$keyword = Helper::wildcardLikeString($value);
}
// remove escaping slash added on js script request
$keyword = str_replace('\\', '%', $keyword);
return $keyword;
}
return $value;
}
/**
* Get column name to be use for filtering and sorting.
*
* @param int $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 = Helper::extractColumnName($column, $wantsAlias);
}
return $column;
}
/**
* Get column name by order column index.
*
* @param int $index
* @return string
*/
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()
{
return 'id';
}
}

View File

@@ -0,0 +1,154 @@
<?php
namespace Yajra\DataTables;
use Illuminate\Support\Traits\Macroable;
class DataTables
{
use Macroable;
/**
* DataTables request object.
*
* @var \Yajra\DataTables\Utilities\Request
*/
protected $request;
/**
* HTML builder instance.
*
* @var \Yajra\DataTables\Html\Builder
*/
protected $html;
/**
* Make a DataTable instance from source.
* Alias of make for backward compatibility.
*
* @param mixed $source
* @return mixed
* @throws \Exception
*/
public static function of($source)
{
return self::make($source);
}
/**
* Make a DataTable instance from source.
*
* @param mixed $source
* @return mixed
* @throws \Exception
*/
public static function make($source)
{
$engines = config('datatables.engines');
$builders = config('datatables.builders');
$args = func_get_args();
foreach ($builders as $class => $engine) {
if ($source instanceof $class) {
return call_user_func_array([$engines[$engine], 'create'], $args);
}
}
foreach ($engines as $engine => $class) {
if (call_user_func_array([$engines[$engine], 'canCreate'], $args)) {
return call_user_func_array([$engines[$engine], 'create'], $args);
}
}
throw new \Exception('No available engine for ' . get_class($source));
}
/**
* Get request object.
*
* @return \Yajra\DataTables\Utilities\Request
*/
public function getRequest()
{
return app('datatables.request');
}
/**
* Get config instance.
*
* @return \Yajra\DataTables\Utilities\Config
*/
public function getConfig()
{
return app('datatables.config');
}
/**
* @deprecated Please use query() instead, this method will be removed in a next version.
* @param $builder
* @return QueryDataTable
*/
public function queryBuilder($builder)
{
return $this->query($builder);
}
/**
* DataTables using Query.
*
* @param \Illuminate\Database\Query\Builder|mixed $builder
* @return DataTableAbstract|QueryDataTable
*/
public function query($builder)
{
return QueryDataTable::create($builder);
}
/**
* DataTables using Eloquent Builder.
*
* @param \Illuminate\Database\Eloquent\Builder|mixed $builder
* @return DataTableAbstract|EloquentDataTable
*/
public function eloquent($builder)
{
return EloquentDataTable::create($builder);
}
/**
* DataTables using Collection.
*
* @param \Illuminate\Support\Collection|array $collection
* @return DataTableAbstract|CollectionDataTable
*/
public function collection($collection)
{
return CollectionDataTable::create($collection);
}
/**
* DataTables using Collection.
*
* @param \Illuminate\Http\Resources\Json\AnonymousResourceCollection|array $collection
* @return DataTableAbstract|ApiResourceDataTable
*/
public function resource($resource)
{
return ApiResourceDataTable::create($resource);
}
/**
* Get html builder instance.
*
* @return \Yajra\DataTables\Html\Builder
* @throws \Exception
*/
public function getHtmlBuilder()
{
if (! class_exists('\Yajra\DataTables\Html\Builder')) {
throw new \Exception('Please install yajra/laravel-datatables-html to be able to use this function.');
}
return $this->html ?: $this->html = app('datatables.html');
}
}

View File

@@ -0,0 +1,83 @@
<?php
namespace Yajra\DataTables;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Str;
use Yajra\DataTables\Utilities\Config;
use Yajra\DataTables\Utilities\Request;
class DataTablesServiceProvider extends ServiceProvider
{
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
if ($this->isLumen()) {
require_once 'lumen.php';
}
$this->setupAssets();
$this->app->alias('datatables', DataTables::class);
$this->app->singleton('datatables', function () {
return new DataTables;
});
$this->app->singleton('datatables.request', function () {
return new Request;
});
$this->app->singleton('datatables.config', Config::class);
}
/**
* Boot the instance, add macros for datatable engines.
*
* @return void
*/
public function boot()
{
$engines = (array) config('datatables.engines');
foreach ($engines as $engine => $class) {
$engine = Str::camel($engine);
if (! method_exists(DataTables::class, $engine) && ! DataTables::hasMacro($engine)) {
DataTables::macro($engine, function () use ($class) {
if (! call_user_func_array([$class, 'canCreate'], func_get_args())) {
throw new \InvalidArgumentException();
}
return call_user_func_array([$class, 'create'], func_get_args());
});
}
}
}
/**
* Setup package assets.
*
* @return void
*/
protected function setupAssets()
{
$this->mergeConfigFrom($config = __DIR__ . '/config/datatables.php', 'datatables');
if ($this->app->runningInConsole()) {
$this->publishes([$config => config_path('datatables.php')], 'datatables');
}
}
/**
* Check if app uses Lumen.
*
* @return bool
*/
protected function isLumen()
{
return Str::contains($this->app->version(), 'Lumen');
}
}

View File

@@ -1,136 +0,0 @@
<?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

@@ -1,144 +0,0 @@
<?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,218 @@
<?php
namespace Yajra\DataTables;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasOneOrMany;
use Illuminate\Database\Eloquent\Relations\HasOneThrough;
use Illuminate\Database\Eloquent\Relations\Relation;
use Yajra\DataTables\Exceptions\Exception;
class EloquentDataTable extends QueryDataTable
{
/**
* @var \Illuminate\Database\Eloquent\Builder
*/
protected $query;
/**
* Can the DataTable engine be created with these parameters.
*
* @param mixed $source
* @return bool
*/
public static function canCreate($source)
{
return $source instanceof Builder || $source instanceof Relation;
}
/**
* EloquentEngine constructor.
*
* @param mixed $model
*/
public function __construct($model)
{
$builder = $model instanceof Builder ? $model : $model->getQuery();
parent::__construct($builder->getQuery());
$this->query = $builder;
}
/**
* Add columns in collection.
*
* @param array $names
* @param bool|int $order
* @return $this
*/
public function addColumns(array $names, $order = false)
{
foreach ($names as $name => $attribute) {
if (is_int($name)) {
$name = $attribute;
}
$this->addColumn($name, function ($model) use ($attribute) {
return $model->getAttribute($attribute);
}, is_int($order) ? $order++ : $order);
}
return $this;
}
/**
* If column name could not be resolved then use primary key.
*
* @return string
*/
protected function getPrimaryKeyName()
{
return $this->query->getModel()->getKeyName();
}
/**
* Compile query builder where clause depending on configurations.
*
* @param mixed $query
* @param string $columnName
* @param string $keyword
* @param string $boolean
*/
protected function compileQuerySearch($query, $columnName, $keyword, $boolean = 'or')
{
$parts = explode('.', $columnName);
$column = array_pop($parts);
$relation = implode('.', $parts);
if ($this->isNotEagerLoaded($relation)) {
return parent::compileQuerySearch($query, $columnName, $keyword, $boolean);
}
$query->{$boolean . 'WhereHas'}($relation, function (Builder $query) use ($column, $keyword) {
parent::compileQuerySearch($query, $column, $keyword, '');
});
}
/**
* Resolve the proper column name be used.
*
* @param string $column
* @return string
*/
protected function resolveRelationColumn($column)
{
$parts = explode('.', $column);
$columnName = array_pop($parts);
$relation = implode('.', $parts);
if ($this->isNotEagerLoaded($relation)) {
return $column;
}
return $this->joinEagerLoadedColumn($relation, $columnName);
}
/**
* Check if a relation was not used on eager loading.
*
* @param string $relation
* @return bool
*/
protected function isNotEagerLoaded($relation)
{
return ! $relation
|| ! array_key_exists($relation, $this->query->getEagerLoads())
|| $relation === $this->query->getModel()->getTable();
}
/**
* Join eager loaded relation and get the related column name.
*
* @param string $relation
* @param string $relationColumn
* @return string
* @throws \Yajra\DataTables\Exceptions\Exception
*/
protected function joinEagerLoadedColumn($relation, $relationColumn)
{
$table = '';
$lastQuery = $this->query;
foreach (explode('.', $relation) as $eachRelation) {
$model = $lastQuery->getRelation($eachRelation);
switch (true) {
case $model instanceof BelongsToMany:
$pivot = $model->getTable();
$pivotPK = $model->getExistenceCompareKey();
$pivotFK = $model->getQualifiedParentKeyName();
$this->performJoin($pivot, $pivotPK, $pivotFK);
$related = $model->getRelated();
$table = $related->getTable();
$tablePK = $related->getForeignKey();
$foreign = $pivot . '.' . $tablePK;
$other = $related->getQualifiedKeyName();
$lastQuery->addSelect($table . '.' . $relationColumn);
$this->performJoin($table, $foreign, $other);
break;
case $model instanceof HasOneThrough:
$pivot = explode('.', $model->getQualifiedParentKeyName())[0]; // extract pivot table from key
$pivotPK = $pivot . '.' . $model->getLocalKeyName();
$pivotFK = $model->getQualifiedLocalKeyName();
$this->performJoin($pivot, $pivotPK, $pivotFK);
$related = $model->getRelated();
$table = $related->getTable();
$tablePK = $related->getForeignKey();
$foreign = $pivot . '.' . $tablePK;
$other = $related->getQualifiedKeyName();
break;
case $model instanceof HasOneOrMany:
$table = $model->getRelated()->getTable();
$foreign = $model->getQualifiedForeignKeyName();
$other = $model->getQualifiedParentKeyName();
break;
case $model instanceof BelongsTo:
$table = $model->getRelated()->getTable();
$foreign = $model->getQualifiedForeignKeyName();
$other = $model->getQualifiedOwnerKeyName();
break;
default:
throw new Exception('Relation ' . get_class($model) . ' is not yet supported.');
}
$this->performJoin($table, $foreign, $other);
$lastQuery = $model->getQuery();
}
return $table . '.' . $relationColumn;
}
/**
* Perform join query.
*
* @param string $table
* @param string $foreign
* @param string $other
* @param string $type
*/
protected function performJoin($table, $foreign, $other, $type = 'left')
{
$joins = [];
foreach ((array) $this->getBaseQueryBuilder()->joins as $key => $join) {
$joins[] = $join->table;
}
if (! in_array($table, $joins)) {
$this->getBaseQueryBuilder()->join($table, $foreign, '=', $other, $type);
}
}
}

View File

@@ -1,967 +0,0 @@
<?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

@@ -1,235 +0,0 @@
<?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

@@ -1,28 +0,0 @@
<?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

@@ -1,535 +0,0 @@
<?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,7 @@
<?php
namespace Yajra\DataTables\Exceptions;
class Exception extends \Exception
{
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Yajra\DataTables\Facades;
use Illuminate\Support\Facades\Facade;
/**
* @mixin \Yajra\DataTables\DataTables
* @method static \Yajra\DataTables\EloquentDatatable eloquent($builder)
* @method static \Yajra\DataTables\QueryDataTable query($builder)
* @method static \Yajra\DataTables\CollectionDataTable collection($collection)
*
* @see \Yajra\DataTables\DataTables
*/
class DataTables extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return 'datatables';
}
}

View File

@@ -1,24 +0,0 @@
<?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

@@ -1,208 +0,0 @@
<?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

@@ -1,56 +0,0 @@
<?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

@@ -1,73 +0,0 @@
<?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

@@ -1,19 +0,0 @@
<?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

@@ -1,620 +0,0 @@
<?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

@@ -1,74 +0,0 @@
<?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

@@ -1,25 +0,0 @@
<?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

@@ -1,17 +1,10 @@
<?php
namespace Yajra\Datatables\Processors;
namespace Yajra\DataTables\Processors;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Config;
use Yajra\Datatables\Helper;
use Yajra\DataTables\Utilities\Helper;
/**
* Class DataProcessor.
*
* @package Yajra\Datatables
* @author Arjay Angeles <aqangeles@gmail.com>
*/
class DataProcessor
{
/**
@@ -27,7 +20,7 @@ class DataProcessor
protected $escapeColumns = [];
/**
* Processed data output
* Processed data output.
*
* @var array
*/
@@ -63,11 +56,21 @@ class DataProcessor
*/
protected $includeIndex;
/**
* @var array
*/
protected $rawColumns;
/**
* @var array
*/
protected $exceptions = ['DT_RowId', 'DT_RowClass', 'DT_RowData', 'DT_RowAttr'];
/**
* @param mixed $results
* @param array $columnDef
* @param array $templates
* @param int $start
* @param int $start
*/
public function __construct($results, array $columnDef, array $templates, $start)
{
@@ -75,14 +78,18 @@ class DataProcessor
$this->appendColumns = $columnDef['append'];
$this->editColumns = $columnDef['edit'];
$this->excessColumns = $columnDef['excess'];
$this->onlyColumns = $columnDef['only'];
$this->escapeColumns = $columnDef['escape'];
$this->includeIndex = $columnDef['index'];
$this->rawColumns = $columnDef['raw'];
$this->makeHidden = $columnDef['hidden'];
$this->makeVisible = $columnDef['visible'];
$this->templates = $templates;
$this->start = $start;
}
/**
* Process data to output on browser
* Process data to output on browser.
*
* @param bool $object
* @return array
@@ -90,13 +97,14 @@ class DataProcessor
public function process($object = false)
{
$this->output = [];
$indexColumn = Config::get('datatables.index_column', 'DT_Row_Index');
$indexColumn = config('datatables.index_column', 'DT_RowIndex');
foreach ($this->results as $row) {
$data = Helper::convertToArray($row);
$data = Helper::convertToArray($row, ['hidden' => $this->makeHidden, 'visible' => $this->makeVisible]);
$value = $this->addColumns($data, $row);
$value = $this->editColumns($value, $row);
$value = $this->setupRowVariables($value, $row);
$value = $this->selectOnlyNeededColumns($value);
$value = $this->removeExcessColumns($value);
if ($this->includeIndex) {
@@ -162,6 +170,31 @@ class DataProcessor
->getData();
}
/**
* Get only needed columns.
*
* @param array $data
* @return array
*/
protected function selectOnlyNeededColumns(array $data)
{
if (is_null($this->onlyColumns)) {
return $data;
} else {
$results = [];
foreach ($this->onlyColumns as $onlyColumn) {
Arr::set($results, $onlyColumn, Arr::get($data, $onlyColumn));
}
foreach ($this->exceptions as $exception) {
if ($column = Arr::get($data, $exception)) {
Arr::set($results, $exception, $column);
}
}
return $results;
}
}
/**
* Remove declared hidden columns.
*
@@ -171,7 +204,7 @@ class DataProcessor
protected function removeExcessColumns(array $data)
{
foreach ($this->excessColumns as $value) {
unset($data[$value]);
Arr::forget($data, $value);
}
return $data;
@@ -185,11 +218,9 @@ class DataProcessor
*/
public function flatten(array $array)
{
$return = [];
$exceptions = ['DT_RowId', 'DT_RowClass', 'DT_RowData', 'DT_RowAttr'];
$return = [];
foreach ($array as $key => $value) {
if (in_array($key, $exceptions)) {
if (in_array($key, $this->exceptions)) {
$return[$key] = $value;
} else {
$return[] = $value;
@@ -209,12 +240,11 @@ class DataProcessor
{
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)));
}
$row = $this->escapeRow($row);
} elseif (is_array($this->escapeColumns)) {
$columns = array_diff($this->escapeColumns, $this->rawColumns);
foreach ($columns as $key) {
Arr::set($row, $key, e(Arr::get($row, $key)));
}
}
@@ -226,19 +256,21 @@ class DataProcessor
* Escape all values of row.
*
* @param array $row
* @param string|array $escapeColumns
* @return array
*/
protected function escapeRow(array $row, $escapeColumns)
protected function escapeRow(array $row)
{
foreach ($row as $key => $value) {
if (is_array($value)) {
$row[$key] = $this->escapeRow($value, $escapeColumns);
} else {
$row[$key] = e($value);
$arrayDot = array_filter(Arr::dot($row));
foreach ($arrayDot as $key => $value) {
if (! in_array($key, $this->rawColumns)) {
$arrayDot[$key] = e($value);
}
}
foreach ($arrayDot as $key => $value) {
Arr::set($row, $key, $value);
}
return $row;
}
}

View File

@@ -1,16 +1,10 @@
<?php
namespace Yajra\Datatables\Processors;
namespace Yajra\DataTables\Processors;
use Illuminate\Support\Arr;
use Yajra\Datatables\Helper;
use Yajra\DataTables\Utilities\Helper;
/**
* Class RowProcessor.
*
* @package Yajra\Datatables
* @author Arjay Angeles <aqangeles@gmail.com>
*/
class RowProcessor
{
/**
@@ -36,7 +30,7 @@ class RowProcessor
/**
* Process DT RowId and Class value.
*
* @param string $attribute
* @param string $attribute
* @param string|callable $template
* @return $this
*/
@@ -57,7 +51,7 @@ class RowProcessor
* Process DT Row Data and Attr.
*
* @param string $attribute
* @param array $template
* @param array $template
* @return $this
*/
public function rowData($attribute, array $template)

View File

@@ -0,0 +1,771 @@
<?php
namespace Yajra\DataTables;
use Illuminate\Support\Str;
use Illuminate\Database\Query\Builder;
use Yajra\DataTables\Utilities\Helper;
use Illuminate\Database\Query\Expression;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
class QueryDataTable extends DataTableAbstract
{
/**
* Builder object.
*
* @var \Illuminate\Database\Query\Builder
*/
protected $query;
/**
* Database connection used.
*
* @var \Illuminate\Database\Connection
*/
protected $connection;
/**
* Flag for ordering NULLS LAST option.
*
* @var bool
*/
protected $nullsLast = false;
/**
* Flag to check if query preparation was already done.
*
* @var bool
*/
protected $prepared = false;
/**
* Query callback for custom pagination using limit without offset.
*
* @var callable
*/
protected $limitCallback;
/**
* Flag to skip total records count query.
*
* @var bool
*/
protected $skipTotalRecords = false;
/**
* Flag to keep the select bindings.
*
* @var bool
*/
protected $keepSelectBindings = false;
/**
* Can the DataTable engine be created with these parameters.
*
* @param mixed $source
* @return bool
*/
public static function canCreate($source)
{
return $source instanceof Builder;
}
/**
* @param \Illuminate\Database\Query\Builder $builder
*/
public function __construct(Builder $builder)
{
$this->query = $builder;
$this->request = app('datatables.request');
$this->config = app('datatables.config');
$this->columns = $builder->columns;
$this->connection = $builder->getConnection();
if ($this->config->isDebugging()) {
$this->connection->enableQueryLog();
}
}
/**
* Organizes works.
*
* @param bool $mDataSupport
* @return \Illuminate\Http\JsonResponse
* @throws \Exception
*/
public function make($mDataSupport = true)
{
try {
$this->prepareQuery();
$results = $this->results();
$processed = $this->processResults($results, $mDataSupport);
$data = $this->transform($results, $processed);
return $this->render($data);
} catch (\Exception $exception) {
return $this->errorResponse($exception);
}
}
/**
* Prepare query by executing count, filter, order and paginate.
*/
protected function prepareQuery()
{
if (! $this->prepared) {
$this->totalRecords = $this->totalCount();
if ($this->totalRecords) {
$this->filterRecords();
$this->ordering();
$this->paginate();
}
}
$this->prepared = true;
}
/**
* Skip total records and set the recordsTotal equals to recordsFiltered.
* This will improve the performance by skipping the total count query.
*
* @return $this
*/
public function skipTotalRecords()
{
$this->skipTotalRecords = true;
return $this;
}
/**
* Keep the select bindings.
*
* @return $this
*/
public function keepSelectBindings()
{
$this->keepSelectBindings = true;
return $this;
}
/**
* Count total items.
*
* @return int
*/
public function totalCount()
{
if ($this->skipTotalRecords) {
$this->isFilterApplied = true;
return 1;
}
return $this->totalRecords ? $this->totalRecords : $this->count();
}
/**
* Count filtered items.
*
* @return int
*/
protected function filteredCount()
{
$this->filteredRecords = $this->filteredRecords ?: $this->count();
if ($this->skipTotalRecords) {
$this->totalRecords = $this->filteredRecords;
}
return $this->filteredRecords;
}
/**
* Counts current query.
*
* @return int
*/
public function count()
{
$builder = $this->prepareCountQuery();
$table = $this->connection->raw('(' . $builder->toSql() . ') count_row_table');
return $this->connection->table($table)
->setBindings($builder->getBindings())
->count();
}
/**
* Prepare count query builder.
*
* @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder
*/
protected function prepareCountQuery()
{
$builder = clone $this->query;
if (! $this->isComplexQuery($builder)) {
$row_count = $this->wrap('row_count');
$builder->select($this->connection->raw("'1' as {$row_count}"));
if (! $this->keepSelectBindings) {
$builder->setBindings([], 'select');
}
}
return $builder;
}
/**
* Check if builder query uses complex sql.
*
* @param \Illuminate\Database\Query\Builder $builder
* @return bool
*/
protected function isComplexQuery($builder)
{
return Str::contains(Str::lower($builder->toSql()), ['union', 'having', 'distinct', 'order by', 'group by']);
}
/**
* Wrap column with DB grammar.
*
* @param string $column
* @return string
*/
protected function wrap($column)
{
return $this->connection->getQueryGrammar()->wrap($column);
}
/**
* Get paginated results.
*
* @return \Illuminate\Support\Collection
*/
public function results()
{
return $this->query->get();
}
/**
* Get filtered, ordered and paginated query.
*
* @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder
*/
public function getFilteredQuery()
{
$this->prepareQuery();
return $this->getQuery();
}
/**
* Get query builder instance.
*
* @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder
*/
public function getQuery()
{
return $this->query;
}
/**
* Perform column search.
*
* @return void
*/
public function columnSearch()
{
$columns = $this->request->columns();
foreach ($columns as $index => $column) {
$column = $this->getColumnName($index);
if (! $this->request->isColumnSearchable($index) || $this->isBlacklisted($column) && ! $this->hasFilterColumn($column)) {
continue;
}
if ($this->hasFilterColumn($column)) {
$keyword = $this->getColumnSearchKeyword($index, $raw = true);
$this->applyFilterColumn($this->getBaseQueryBuilder(), $column, $keyword);
} else {
$column = $this->resolveRelationColumn($column);
$keyword = $this->getColumnSearchKeyword($index);
$this->compileColumnSearch($index, $column, $keyword);
}
$this->isFilterApplied = true;
}
}
/**
* Check if column has custom filter handler.
*
* @param string $columnName
* @return bool
*/
public function hasFilterColumn($columnName)
{
return isset($this->columnDef['filter'][$columnName]);
}
/**
* Get column keyword to use for search.
*
* @param int $i
* @param bool $raw
* @return string
*/
protected function getColumnSearchKeyword($i, $raw = false)
{
$keyword = $this->request->columnKeyword($i);
if ($raw || $this->request->isRegex($i)) {
return $keyword;
}
return $this->setupKeyword($keyword);
}
/**
* Apply filterColumn api search.
*
* @param mixed $query
* @param string $columnName
* @param string $keyword
* @param string $boolean
*/
protected function applyFilterColumn($query, $columnName, $keyword, $boolean = 'and')
{
$query = $this->getBaseQueryBuilder($query);
$callback = $this->columnDef['filter'][$columnName]['method'];
if ($this->query instanceof EloquentBuilder) {
$builder = $this->query->newModelInstance()->newQuery();
} else {
$builder = $this->query->newQuery();
}
$callback($builder, $keyword);
$query->addNestedWhereQuery($this->getBaseQueryBuilder($builder), $boolean);
}
/**
* Get the base query builder instance.
*
* @param mixed $instance
* @return \Illuminate\Database\Query\Builder
*/
protected function getBaseQueryBuilder($instance = null)
{
if (! $instance) {
$instance = $this->query;
}
if ($instance instanceof EloquentBuilder) {
return $instance->getQuery();
}
return $instance;
}
/**
* Resolve the proper column name be used.
*
* @param string $column
* @return string
*/
protected function resolveRelationColumn($column)
{
return $column;
}
/**
* Compile queries for column search.
*
* @param int $i
* @param string $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)
{
switch ($this->connection->getDriverName()) {
case 'oracle':
$sql = ! $this->config->isCaseInsensitive()
? 'REGEXP_LIKE( ' . $column . ' , ? )'
: 'REGEXP_LIKE( LOWER(' . $column . ') , ?, \'i\' )';
break;
case 'pgsql':
$column = $this->castColumn($column);
$sql = ! $this->config->isCaseInsensitive() ? $column . ' ~ ?' : $column . ' ~* ? ';
break;
default:
$sql = ! $this->config->isCaseInsensitive()
? $column . ' REGEXP ?'
: 'LOWER(' . $column . ') REGEXP ?';
$keyword = Str::lower($keyword);
}
$this->query->whereRaw($sql, [$keyword]);
}
/**
* Wrap a column and cast based on database driver.
*
* @param string $column
* @return string
*/
protected function castColumn($column)
{
switch ($this->connection->getDriverName()) {
case 'pgsql':
return 'CAST(' . $column . ' as TEXT)';
case 'firebird':
return 'CAST(' . $column . ' as VARCHAR(255))';
default:
return $column;
}
}
/**
* Compile query builder where clause depending on configurations.
*
* @param mixed $query
* @param string $column
* @param string $keyword
* @param string $boolean
*/
protected function compileQuerySearch($query, $column, $keyword, $boolean = 'or')
{
$column = $this->addTablePrefix($query, $column);
$column = $this->castColumn($column);
$sql = $column . ' LIKE ?';
if ($this->config->isCaseInsensitive()) {
$sql = 'LOWER(' . $column . ') LIKE ?';
}
$query->{$boolean . 'WhereRaw'}($sql, [$this->prepareKeyword($keyword)]);
}
/**
* Patch for fix about ambiguous field.
* Ambiguous field error will appear when query use join table and search with keyword.
*
* @param mixed $query
* @param string $column
* @return string
*/
protected function addTablePrefix($query, $column)
{
if (strpos($column, '.') === false) {
$q = $this->getBaseQueryBuilder($query);
if (! $q->from instanceof Expression) {
$column = $q->from . '.' . $column;
}
}
return $this->wrap($column);
}
/**
* Prepare search keyword based on configurations.
*
* @param string $keyword
* @return string
*/
protected function prepareKeyword($keyword)
{
if ($this->config->isStartsWithSearch()) {
return "$keyword%";
}
if ($this->config->isCaseInsensitive()) {
$keyword = Str::lower($keyword);
}
if ($this->config->isWildcard()) {
$keyword = Helper::wildcardLikeString($keyword);
}
if ($this->config->isSmartSearch()) {
$keyword = "%$keyword%";
}
return $keyword;
}
/**
* Add custom filter handler for the give column.
*
* @param string $column
* @param callable $callback
* @return $this
*/
public function filterColumn($column, callable $callback)
{
$this->columnDef['filter'][$column] = ['method' => $callback];
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] = compact('sql', 'bindings');
return $this;
}
/**
* Set datatables to do ordering with NULLS LAST option.
*
* @return $this
*/
public function orderByNullsLast()
{
$this->nullsLast = true;
return $this;
}
/**
* Paginate dataTable using limit without offset
* with additional where clause via callback.
*
* @param callable $callback
* @return $this
*/
public function limit(callable $callback)
{
$this->limitCallback = $callback;
return $this;
}
/**
* Perform pagination.
*
* @return void
*/
public function paging()
{
$limit = (int) $this->request->input('length') > 0 ? $this->request->input('length') : 10;
if (is_callable($this->limitCallback)) {
$this->query->limit($limit);
call_user_func_array($this->limitCallback, [$this->query]);
} else {
$this->query->skip($this->request->input('start'))->take($limit);
}
}
/**
* 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->pushToBlacklist($name);
return parent::addColumn($name, $content, $order);
}
/**
* Resolve callback parameter instance.
*
* @return \Illuminate\Database\Query\Builder
*/
protected function resolveCallbackParameter()
{
return $this->query;
}
/**
* Perform default query orderBy clause.
*/
protected function defaultOrdering()
{
collect($this->request->orderableColumns())
->map(function ($orderable) {
$orderable['name'] = $this->getColumnName($orderable['column'], true);
return $orderable;
})
->reject(function ($orderable) {
return $this->isBlacklisted($orderable['name']) && ! $this->hasOrderColumn($orderable['name']);
})
->each(function ($orderable) {
$column = $this->resolveRelationColumn($orderable['name']);
if ($this->hasOrderColumn($column)) {
$this->applyOrderColumn($column, $orderable);
} else {
$nullsLastSql = $this->getNullsLastSql($column, $orderable['direction']);
$normalSql = $this->wrap($column) . ' ' . $orderable['direction'];
$sql = $this->nullsLast ? $nullsLastSql : $normalSql;
$this->query->orderByRaw($sql);
}
});
}
/**
* Check if column has custom sort handler.
*
* @param string $column
* @return bool
*/
protected function hasOrderColumn($column)
{
return isset($this->columnDef['order'][$column]);
}
/**
* Apply orderColumn custom query.
*
* @param string $column
* @param array $orderable
*/
protected function applyOrderColumn($column, $orderable)
{
$sql = $this->columnDef['order'][$column]['sql'];
if (is_callable($sql)) {
call_user_func($sql, $this->query, $orderable['direction']);
} else {
$sql = str_replace('$1', $orderable['direction'], $sql);
$bindings = $this->columnDef['order'][$column]['bindings'];
$this->query->orderByRaw($sql, $bindings);
}
}
/**
* Get NULLS LAST SQL.
*
* @param string $column
* @param string $direction
* @return string
*/
protected function getNullsLastSql($column, $direction)
{
$sql = $this->config->get('datatables.nulls_last_sql', '%s %s NULLS LAST');
return str_replace(
[':column', ':direction'],
[$column, $direction],
sprintf($sql, $column, $direction)
);
}
/**
* Perform global search for the given keyword.
*
* @param string $keyword
*/
protected function globalSearch($keyword)
{
$this->query->where(function ($query) use ($keyword) {
collect($this->request->searchableColumnIndex())
->map(function ($index) {
return $this->getColumnName($index);
})
->reject(function ($column) {
return $this->isBlacklisted($column) && ! $this->hasFilterColumn($column);
})
->each(function ($column) use ($keyword, $query) {
if ($this->hasFilterColumn($column)) {
$this->applyFilterColumn($query, $column, $keyword, 'or');
} else {
$this->compileQuerySearch($query, $column, $keyword);
}
$this->isFilterApplied = true;
});
});
}
/**
* Append debug parameters on output.
*
* @param array $output
* @return array
*/
protected function showDebugger(array $output)
{
$query_log = $this->connection->getQueryLog();
array_walk_recursive($query_log, function (&$item, $key) {
$item = utf8_encode($item);
});
$output['queries'] = $query_log;
$output['input'] = $this->request->all();
return $output;
}
/**
* Attach custom with meta on response.
*
* @param array $data
* @return array
*/
protected function attachAppends(array $data)
{
$appends = [];
foreach ($this->appends as $key => $value) {
if (is_callable($value)) {
$appends[$key] = value($value($this->getFilteredQuery()));
} else {
$appends[$key] = $value;
}
}
return array_merge($data, $appends);
}
}

View File

@@ -1,173 +0,0 @@
<?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

@@ -1,385 +0,0 @@
<?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

@@ -1,75 +0,0 @@
<?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,107 @@
<?php
namespace Yajra\DataTables\Utilities;
use Illuminate\Contracts\Config\Repository;
class Config
{
/**
* @var \Illuminate\Contracts\Config\Repository
*/
private $repository;
/**
* Config constructor.
*
* @param \Illuminate\Contracts\Config\Repository $repository
*/
public function __construct(Repository $repository)
{
$this->repository = $repository;
}
/**
* Check if config uses wild card search.
*
* @return bool
*/
public function isWildcard()
{
return $this->repository->get('datatables.search.use_wildcards', false);
}
/**
* Check if config uses smart search.
*
* @return bool
*/
public function isSmartSearch()
{
return $this->repository->get('datatables.search.smart', true);
}
/**
* Check if config uses case insensitive search.
*
* @return bool
*/
public function isCaseInsensitive()
{
return $this->repository->get('datatables.search.case_insensitive', false);
}
/**
* Check if app is in debug mode.
*
* @return bool
*/
public function isDebugging()
{
return $this->repository->get('app.debug', false);
}
/**
* Get the specified configuration value.
*
* @param string $key
* @param mixed $default
* @return mixed
*/
public function get($key, $default = null)
{
return $this->repository->get($key, $default);
}
/**
* Set a given configuration value.
*
* @param array|string $key
* @param mixed $value
* @return void
*/
public function set($key, $value = null)
{
$this->repository->set($key, $value);
}
/**
* Check if dataTable config uses multi-term searching.
*
* @return bool
*/
public function isMultiTerm()
{
return $this->repository->get('datatables.search.multi_term', true);
}
/**
* Check if dataTable config uses starts_with searching.
*
* @return bool
*/
public function isStartsWithSearch()
{
return $this->repository->get('datatables.search.starts_with', false);
}
}

View File

@@ -1,19 +1,12 @@
<?php
namespace Yajra\Datatables;
namespace Yajra\DataTables\Utilities;
use DateTime;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\View\Compilers\BladeCompiler;
use Illuminate\Contracts\Support\Arrayable;
/**
* Class Helper.
*
* @package Yajra\Datatables
* @author Arjay Angeles <aqangeles@gmail.com>
*/
class Helper
{
/**
@@ -27,20 +20,20 @@ class Helper
{
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++;
$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++;
}
}
@@ -59,10 +52,10 @@ class Helper
/**
* 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
* @param mixed $content Pre-processed content
* @param array $data data to use with blade template
* @param mixed $param parameter to call with callable
* @return mixed
*/
public static function compileContent($content, array $data, $param)
{
@@ -79,8 +72,8 @@ class Helper
* Parses and compiles strings by using Blade Template System.
*
* @param string $str
* @param array $data
* @return string
* @param array $data
* @return mixed
* @throws \Exception
*/
public static function compileBlade($str, $data = [])
@@ -89,19 +82,8 @@ class Helper
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;
}
eval('?>' . app('blade.compiler')->compileString($str));
$str = ob_get_contents();
ob_end_clean();
@@ -117,11 +99,13 @@ class Helper
*/
public static function getMixedValue(array $data, $param)
{
$param = self::castToArray($param);
$casted = self::castToArray($param);
$data['model'] = $param;
foreach ($data as $key => $value) {
if (isset($param[$key])) {
$data[$key] = $param[$key];
if (isset($casted[$key])) {
$data[$key] = $casted[$key];
}
}
@@ -164,68 +148,25 @@ class Helper
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
* @param array $filters
* @return array
*/
public static function convertToArray($row)
public static function convertToArray($row, $filters = [])
{
$row = method_exists($row, 'makeHidden') ? $row->makeHidden(Arr::get($filters, 'hidden', [])) : $row;
$row = method_exists($row, 'makeVisible') ? $row->makeVisible(Arr::get($filters, 'visible', [])) : $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]);
foreach ($data as &$value) {
if (is_object($value) || is_array($value)) {
$value = self::convertToArray($value);
}
unset($value);
}
return $data;
@@ -290,9 +231,9 @@ class Helper
}
/**
* Replace all pattern occurrences with keyword
* Replace all pattern occurrences with keyword.
*
* @param array $subject
* @param array $subject
* @param string $keyword
* @param string $pattern
* @return array
@@ -310,4 +251,68 @@ class Helper
return $parameters;
}
/**
* Get column name from string.
*
* @param string $str
* @param bool $wantsAlias
* @return string
*/
public static function extractColumnName($str, $wantsAlias)
{
$matches = explode(' as ', Str::lower($str));
if (! empty($matches)) {
if ($wantsAlias) {
return array_pop($matches);
}
return array_shift($matches);
} elseif (strpos($str, '.')) {
$array = explode('.', $str);
return array_pop($array);
}
return $str;
}
/**
* Adds % wildcards to the given string.
*
* @param string $str
* @param bool $lowercase
* @return string
*/
public static function wildcardLikeString($str, $lowercase = true)
{
return static::wildcardString($str, '%', $lowercase);
}
/**
* Adds wildcards to the given string.
*
* @param string $str
* @param string $wildcard
* @param bool $lowercase
* @return string
*/
public static function wildcardString($str, $wildcard, $lowercase = true)
{
$wild = $wildcard;
$chars = preg_split('//u', $str, -1, PREG_SPLIT_NO_EMPTY);
if (count($chars) > 0) {
foreach ($chars as $char) {
$wild .= $char . $wildcard;
}
}
if ($lowercase) {
$wild = Str::lower($wild);
}
return $wild;
}
}

View File

@@ -0,0 +1,236 @@
<?php
namespace Yajra\DataTables\Utilities;
/**
* @method mixed input($key, $default = null)
* @method mixed get($key, $default = null)
* @method mixed query($key, $default = null)
* @method mixed has($key)
* @method mixed merge(array $values)
* @method bool wantsJson()
* @method bool ajax()
* @method array all()
*/
class Request
{
/**
* @var \Illuminate\Http\Request
*/
protected $request;
/**
* Request constructor.
*/
public function __construct()
{
$this->request = app('request');
}
/**
* Proxy non existing method calls to request class.
*
* @param mixed $name
* @param mixed $arguments
* @return mixed
*/
public function __call($name, $arguments)
{
if (method_exists($this->request, $name)) {
return call_user_func_array([$this->request, $name], $arguments);
}
}
/**
* Get attributes from request instance.
*
* @param string $name
* @return mixed
*/
public function __get($name)
{
return $this->request->__get($name);
}
/**
* Get all columns request input.
*
* @return array
*/
public function columns()
{
return (array) $this->request->input('columns');
}
/**
* Check if DataTables is searchable.
*
* @return bool
*/
public function isSearchable()
{
return $this->request->input('search.value') != '';
}
/**
* Check if DataTables must uses regular expressions.
*
* @param int $index
* @return bool
*/
public function isRegex($index)
{
return $this->request->input("columns.$index.search.regex") === 'true';
}
/**
* Get orderable columns.
*
* @return array
*/
public function orderableColumns()
{
if (! $this->isOrderable()) {
return [];
}
$orderable = [];
for ($i = 0, $c = count($this->request->input('order')); $i < $c; $i++) {
$order_col = (int) $this->request->input("order.$i.column");
$order_dir = strtolower($this->request->input("order.$i.dir")) === 'asc' ? 'asc' : 'desc';
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->request->input('order') && count($this->request->input('order')) > 0;
}
/**
* Check if a column is orderable.
*
* @param int $index
* @return bool
*/
public function isColumnOrderable($index)
{
return $this->request->input("columns.$index.orderable", 'true') == 'true';
}
/**
* Get searchable column indexes.
*
* @return array
*/
public function searchableColumnIndex()
{
$searchable = [];
for ($i = 0, $c = count($this->request->input('columns')); $i < $c; $i++) {
if ($this->isColumnSearchable($i, false)) {
$searchable[] = $i;
}
}
return $searchable;
}
/**
* Check if a column is searchable.
*
* @param int $i
* @param bool $column_search
* @return bool
*/
public function isColumnSearchable($i, $column_search = true)
{
if ($column_search) {
return
(
$this->request->input("columns.$i.searchable", 'true') === 'true'
||
$this->request->input("columns.$i.searchable", 'true') === true
)
&& $this->columnKeyword($i) != '';
}
return
$this->request->input("columns.$i.searchable", 'true') === 'true'
||
$this->request->input("columns.$i.searchable", 'true') === true;
}
/**
* Get column's search value.
*
* @param int $index
* @return string
*/
public function columnKeyword($index)
{
$keyword = $this->request->input("columns.$index.search.value");
return $this->prepareKeyword($keyword);
}
/**
* Prepare keyword string value.
*
* @param string|array $keyword
* @return string
*/
protected function prepareKeyword($keyword)
{
if (is_array($keyword)) {
return implode(' ', $keyword);
}
return $keyword;
}
/**
* Get global search keyword.
*
* @return string
*/
public function keyword()
{
$keyword = $this->request->input('search.value');
return $this->prepareKeyword($keyword);
}
/**
* Get column identity from input or database.
*
* @param int $i
* @return string
*/
public function columnName($i)
{
$column = $this->request->input("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->request->input('start')) &&
! is_null($this->request->input('length')) &&
$this->request->input('length') != -1;
}
}

View File

@@ -1,86 +0,0 @@
<?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,122 @@
<?php
return [
/*
* DataTables search options.
*/
'search' => [
/*
* Smart search will enclose search keyword with wildcard string "%keyword%".
* SQL: column LIKE "%keyword%"
*/
'smart' => true,
/*
* Multi-term search will explode search keyword using spaces resulting into multiple term search.
*/
'multi_term' => 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,
/*
* Perform a search which starts with the given keyword.
* SQL: column LIKE "keyword%"
*/
'starts_with' => false,
],
/*
* DataTables internal index id response column name.
*/
'index_column' => 'DT_RowIndex',
/*
* List of available builders for DataTables.
* This is where you can register your custom dataTables builder.
*/
'engines' => [
'eloquent' => Yajra\DataTables\EloquentDataTable::class,
'query' => Yajra\DataTables\QueryDataTable::class,
'collection' => Yajra\DataTables\CollectionDataTable::class,
'resource' => Yajra\DataTables\ApiResourceDataTable::class,
],
/*
* DataTables accepted builder to engine mapping.
* This is where you can override which engine a builder should use
* Note, only change this if you know what you are doing!
*/
'builders' => [
//Illuminate\Database\Eloquent\Relations\Relation::class => 'eloquent',
//Illuminate\Database\Eloquent\Builder::class => 'eloquent',
//Illuminate\Database\Query\Builder::class => 'query',
//Illuminate\Support\Collection::class => 'collection',
],
/*
* Nulls last sql pattern for PostgreSQL & Oracle.
* For MySQL, use 'CASE WHEN :column IS NULL THEN 1 ELSE 0 END, :column :direction'
*/
'nulls_last_sql' => ':column :direction NULLS LAST',
/*
* User friendly message to be displayed on user if error occurs.
* Possible values:
* null - The exception message will be used on error response.
* 'throw' - Throws a \Yajra\DataTables\Exceptions\Exception. Use your custom error handler if needed.
* 'custom message' - Any friendly message to be displayed to the user. You can also use translation key.
*/
'error' => env('DATATABLES_ERROR', null),
/*
* Default columns definition of dataTable utility functions.
*/
'columns' => [
/*
* List of columns hidden/removed on json response.
*/
'excess' => ['rn', 'row_num'],
/*
* List of columns to be escaped. If set to *, all columns are escape.
* Note: You can set the value to empty array to disable XSS protection.
*/
'escape' => '*',
/*
* List of columns that are allowed to display html content.
* Note: Adding columns to list will make us available to XSS attacks.
*/
'raw' => ['action'],
/*
* List of columns are are forbidden from being searched/sorted.
*/
'blacklist' => ['password', 'remember_token'],
/*
* List of columns that are only allowed fo search/sort.
* If set to *, all columns are allowed.
*/
'whitelist' => '*',
],
/*
* JsonResponse header and options config.
*/
'json' => [
'header' => [],
'options' => 0,
],
];

View File

@@ -0,0 +1,19 @@
<?php
if (! function_exists('datatables')) {
/**
* Helper to make a new DataTable instance from source.
* Or return the factory if source is not set.
*
* @param mixed $source
* @return \Yajra\DataTables\DataTableAbstract|\Yajra\DataTables\DataTables
*/
function datatables($source = null)
{
if (is_null($source)) {
return app('datatables');
}
return app('datatables')->make($source);
}
}

View File

@@ -15,7 +15,7 @@ if (! function_exists('config_path')) {
if (! function_exists('public_path')) {
/**
* Return the path to public dir
* Return the path to public dir.
*
* @param null $path
* @return string

View File

@@ -1,111 +0,0 @@
(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

@@ -1,37 +0,0 @@
<!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

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