diff --git a/composer.json b/composer.json index 55b03c7..fef00a3 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "livecontrol/eloquent-datatable", + "name": "schoppax/eloquent-datatable", "description": "Eloquent DataTable plugin for server side ajax call handling.", "keywords": [ "eloquent", @@ -15,7 +15,7 @@ ], "require": { "php": ">=5.4.0", - "illuminate/database": "5.*" + "illuminate/database": ">=5.7.5" }, "require-dev": { "phpspec/phpspec": "~2.1" diff --git a/src/LiveControl/EloquentDataTable/DataTable.php b/src/LiveControl/EloquentDataTable/DataTable.php index b226dd3..08cfe56 100644 --- a/src/LiveControl/EloquentDataTable/DataTable.php +++ b/src/LiveControl/EloquentDataTable/DataTable.php @@ -5,6 +5,8 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Query\Expression as raw; +use Illuminate\Database\Query\Grammars\Grammar; +use Illuminate\Support\Str; use LiveControl\EloquentDataTable\VersionTransformers\Version110Transformer; use LiveControl\EloquentDataTable\VersionTransformers\VersionTransformerContract; @@ -13,6 +15,7 @@ class DataTable { protected $builder; protected $columns; + protected $orderPossibilities; protected $formatRowFunction; /** @@ -22,22 +25,29 @@ class DataTable protected $rawColumns; protected $columnNames; + protected $orderNames; protected $total = 0; protected $filtered = 0; protected $rows = []; + protected $mySqlAggFncs = [ + "AVG(","BIT_AND(","BIT_OR(","BIT_XOR(","COUNT(","GROUP_CONCAT(","JSON_ARRAYAGG(","JSON_OBJECTAGG(","MAX(", + "MIN(","STD(","STDDEV(","STDDEV_POP(","STDDEV_SAMP(","SUM(","VAR_POP(","VAR_SAMP(","VARIANCE(" + ]; + /** * @param Builder|Model $builder * @param array $columns * @param null|callable $formatRowFunction * @throws Exception */ - public function __construct($builder, $columns, $formatRowFunction = null) + public function __construct($builder, $columns, $orderPossibilities = null, $formatRowFunction = null) { $this->setBuilder($builder); $this->setColumns($columns); + $this->setOrderPossibilities($orderPossibilities); if ($formatRowFunction !== null) { $this->setFormatRowFunction($formatRowFunction); @@ -69,6 +79,20 @@ public function setColumns($columns) return $this; } + /** + * @param mixed $columns + * @return $this + */ + public function setOrderPossibilities($columns) + { + if ( $columns !== null ) { + $this->orderPossibilities = $columns; + } else { + $this->orderPossibilities = $this->columns; + } + return $this; + } + /** * @param callable $function * @return $this @@ -97,7 +121,8 @@ public function setVersionTransformer(VersionTransformerContract $versionTransfo public function make() { $this->rawColumns = $this->getRawColumns($this->columns); - $this->columnNames = $this->getColumnNames(); + $this->columnNames = $this->getColumnNames($this->columns); + $this->orderNames = $this->getColumnNames($this->orderPossibilities); $this->addSelect(); @@ -142,7 +167,7 @@ protected function count() $countStatement = $pdo->prepare('SELECT count(*) as totalCount FROM ('.$query->toSql().') subQuery'); $countStatement->execute($query->getBindings()); } - + $result = $countStatement->fetch(); return $result['totalCount']; } @@ -186,10 +211,10 @@ protected function formatRowIndexes($data) /** * @return array */ - protected function getColumnNames() + protected function getColumnNames($columns) { $names = []; - foreach ($this->columns as $index => $column) { + foreach ($columns as $index => $column) { if ($column instanceof ExpressionWithName) { $names[] = $column->getName(); continue; @@ -224,7 +249,8 @@ protected function getRawColumns($columns) protected function getRawColumnQuery($column) { if ($column instanceof ExpressionWithName) { - return $column->getExpression(); + $gramma = new Grammar(); + return $column->getExpression()->getValue($gramma); } if (is_array($column)) { @@ -276,7 +302,22 @@ protected function arrayToCamelcase($array, $inForeach = false) $result[] = $value; } - return (! $inForeach ? camel_case(implode('_', $result)) : $result); + return (! $inForeach ? Str::camel(implode('_', $result)) : $result); + } + + /** + * Checks if the query includes aggregate functions + * @return bool + */ + protected function hasAggregateFunction() + { + $query = $this->builder->toSql() ?? $this->builder->getQuery()->toSql(); + foreach($this->mySqlAggFncs as $aggFnc) { + if (stripos($query, $aggFnc) !== false) { + return true; + } + } + return false; } /** @@ -286,26 +327,31 @@ protected function arrayToCamelcase($array, $inForeach = false) protected function addFilters() { $search = static::$versionTransformer->getSearchValue(); + $regex = static::$versionTransformer->isSearchRegex(); + $aggregate = $this->hasAggregateFunction(); if ($search != '') { - $this->addAllFilter($search); + $this->addAllFilter($search, $regex, $aggregate); } - $this->addColumnFilters(); + $this->addColumnFilters($aggregate); return $this; } /** * Searches in all the columns. * @param $search + * @param $regex */ - protected function addAllFilter($search) + protected function addAllFilter($search, $regex, $aggregate) { - $this->builder = $this->builder->where( - function ($query) use ($search) { - foreach ($this->columns as $column) { - $query->orWhere( - new raw($this->getRawColumnQuery($column)), - 'like', - '%' . $search . '%' + $method = $aggregate ? 'Having' : 'Where'; + $columns = $aggregate ? $this->columnNames : $this->columns; + $this->builder = $this->builder->{strtolower($method)}( + function ($query) use ($search, $regex, $method, $columns, $aggregate) { + foreach ($columns as $column) { + $query->{"or$method"}( + new raw($aggregate ? $column : $this->getRawColumnQuery($column)), + $regex ? 'rlike' : 'like', + $regex ? $search : '%' . $search . '%' ); } } @@ -315,14 +361,17 @@ function ($query) use ($search) { /** * Add column specific filters. */ - protected function addColumnFilters() + protected function addColumnFilters($aggregate) { - foreach ($this->columns as $i => $column) { + $columns = $aggregate ? $this->columnNames : $this->columns; + foreach ($columns as $i => $column) { if (static::$versionTransformer->isColumnSearched($i)) { - $this->builder->where( - new raw($this->getRawColumnQuery($column)), - 'like', - '%' . static::$versionTransformer->getColumnSearchValue($i) . '%' + $regex = static::$versionTransformer->isColumnSearchRegex($i); + $method = $aggregate ? 'having' : 'where'; + $this->builder->{$method}( + new raw($aggregate ? $column : $this->getRawColumnQuery($column)), + $regex ? 'rlike' : 'like', + $regex ? static::$versionTransformer->getColumnSearchValue($i) : '%' . static::$versionTransformer->getColumnSearchValue($i) . '%' ); } } @@ -335,9 +384,9 @@ protected function addOrderBy() { if (static::$versionTransformer->isOrdered()) { foreach (static::$versionTransformer->getOrderedColumns() as $index => $direction) { - if (isset($this->columnNames[$index])) { + if (isset($this->orderNames[$index])) { $this->builder->orderBy( - $this->columnNames[$index], + $this->orderNames[$index], $direction ); } diff --git a/src/LiveControl/EloquentDataTable/VersionTransformers/Version109Transformer.php b/src/LiveControl/EloquentDataTable/VersionTransformers/Version109Transformer.php index e0fd923..70d4bd4 100644 --- a/src/LiveControl/EloquentDataTable/VersionTransformers/Version109Transformer.php +++ b/src/LiveControl/EloquentDataTable/VersionTransformers/Version109Transformer.php @@ -13,34 +13,43 @@ class Version109Transformer implements VersionTransformerContract ]; - public function transform($name) + public function transform($name): string { return (isset($this->translate[$name]) ? $this->translate[$name] : $name); } - public function getSearchValue() + public function isSearchRegex(): bool + { + return false; + } + public function getSearchValue(): string { if(isset($_POST['sSearch'])) return $_POST['sSearch']; return ''; } - public function isColumnSearched($i) + public function isColumnSearched($i): bool { return (isset($_POST['bSearchable_' . $i]) && $_POST['bSearchable_' . $i] == 'true' && $_POST['sSearch_' . $i] != ''); } - public function getColumnSearchValue($i) + public function isColumnSearchRegex($columnIndex): bool + { + return false; + } + + public function getColumnSearchValue($i): string { return $_POST['sSearch_' . $i]; } - public function isOrdered() + public function isOrdered(): bool { return isset($_POST['iSortCol_0']); } - public function getOrderedColumns() + public function getOrderedColumns(): array { $columns = []; for ($i = 0; $i < (int) $_POST['iSortingCols']; $i ++) { diff --git a/src/LiveControl/EloquentDataTable/VersionTransformers/Version110Transformer.php b/src/LiveControl/EloquentDataTable/VersionTransformers/Version110Transformer.php index c42b692..6249978 100644 --- a/src/LiveControl/EloquentDataTable/VersionTransformers/Version110Transformer.php +++ b/src/LiveControl/EloquentDataTable/VersionTransformers/Version110Transformer.php @@ -3,19 +3,26 @@ class Version110Transformer implements VersionTransformerContract { - public function transform($name) + public function transform($name): string { return $name; // we use the same as the requested name } - public function getSearchValue() + public function isSearchRegex(): bool + { + if(isset($_POST['search']) && isset($_POST['search']['regex'])) + return filter_var($_POST['search']['regex'], FILTER_VALIDATE_BOOLEAN); + return false; + } + + public function getSearchValue(): string { if(isset($_POST['search']) && isset($_POST['search']['value'])) return $_POST['search']['value']; return ''; } - public function isColumnSearched($columnIndex) + public function isColumnSearched($columnIndex): bool { return ( isset($_POST['columns']) @@ -30,17 +37,24 @@ public function isColumnSearched($columnIndex) ); } - public function getColumnSearchValue($columnIndex) + public function isColumnSearchRegex($columnIndex): bool + { + if (isset($_POST['columns'][$columnIndex]['search']['regex'])) + return filter_var($_POST['columns'][$columnIndex]['search']['regex'], FILTER_VALIDATE_BOOLEAN); + return false; + } + + public function getColumnSearchValue($columnIndex): string { return $_POST['columns'][$columnIndex]['search']['value']; } - public function isOrdered() + public function isOrdered(): bool { return (isset($_POST['order']) && isset($_POST['order'][0])); } - public function getOrderedColumns() + public function getOrderedColumns(): array { $columns = []; foreach($_POST['order'] as $i => $order) diff --git a/src/LiveControl/EloquentDataTable/VersionTransformers/VersionTransformerContract.php b/src/LiveControl/EloquentDataTable/VersionTransformers/VersionTransformerContract.php index e37a6de..7e495bc 100644 --- a/src/LiveControl/EloquentDataTable/VersionTransformers/VersionTransformerContract.php +++ b/src/LiveControl/EloquentDataTable/VersionTransformers/VersionTransformerContract.php @@ -3,14 +3,16 @@ interface VersionTransformerContract { - public function transform($name); + public function transform($name): string; - public function getSearchValue(); + public function isSearchRegex(): bool; + public function getSearchValue(): string; - public function isColumnSearched($columnIndex); - public function getColumnSearchValue($columnIndex); + public function isColumnSearched($columnIndex): bool; + public function isColumnSearchRegex($columnIndex): bool; + public function getColumnSearchValue($columnIndex): string; - public function isOrdered(); - public function getOrderedColumns(); + public function isOrdered(): bool; + public function getOrderedColumns(): array; } \ No newline at end of file