From a4bca24a6f7e26404b04f1b4308f96e17c472b4b Mon Sep 17 00:00:00 2001 From: mzf Date: Thu, 4 Feb 2016 00:07:50 +0700 Subject: [PATCH 01/31] Added new feature: using model methods for table render with custom formatting. --- src/Adapters/AdapterInterface.php | 204 ++++++++++++++++-------------- src/Adapters/ArrayAdapter.php | 171 +++++++++++++------------ src/Adapters/QueryBuilder.php | 193 +++++++++++++++++++++------- src/Adapters/ResultSet.php | 178 +++++++++++++------------- src/DataTable.php | 133 ++++++++++--------- src/ParamsParser.php | 169 ++++++++++++++----------- 6 files changed, 606 insertions(+), 442 deletions(-) diff --git a/src/Adapters/AdapterInterface.php b/src/Adapters/AdapterInterface.php index 290f309..8a0f74a 100644 --- a/src/Adapters/AdapterInterface.php +++ b/src/Adapters/AdapterInterface.php @@ -4,112 +4,128 @@ use DataTables\ParamsParser; -abstract class AdapterInterface { - - protected $parser = null; - protected $columns = []; - protected $lentgh = 30; - - public function __construct($length) { - $this->length = $length; - } - - abstract public function getResponse(); - - public function setParser(ParamsParser $parser) { - $this->parser = $parser; - } - - public function setColumns(array $columns) { - $this->columns = $columns; - } - - public function getColumns() { - return $this->columns; - } - - public function columnExists($column) { - return in_array($column, $this->columns); - } - - public function getParser() { - return $this->parser; - } - - public function formResponse($options) { - $defaults = [ - 'total' => 0, - 'filtered' => 0, - 'data' => [] - ]; - $options += $defaults; - - $response = []; - $response['draw'] = $this->parser->getDraw(); - $response['recordsTotal'] = $options['total']; - $response['recordsFiltered'] = $options['filtered']; - - if (count($options['data'])) { - foreach($options['data'] as $item) { - if (isset($item['id'])) { - $item['DT_RowId'] = $item['id']; - } +abstract class AdapterInterface +{ - $response['data'][] = $item; - } - } else { - $response['data'] = []; - } + protected $parser = null; + protected $columns = []; + protected $lentgh = 30; - return $response; - } + public function __construct($length) + { + $this->length = $length; + } - public function sanitaze($string) { - return mb_substr($string, 0, $this->length); - } + abstract public function getResponse(); - public function bind($case, $closure) { - switch($case) { - case "global_search": - $search = $this->parser->getSearchValue(); - if (!mb_strlen($search)) return; + public function setParser(ParamsParser $parser) + { + $this->parser = $parser; + } - foreach($this->parser->getSearchableColumns() as $column) { - if (!$this->columnExists($column)) continue; - $closure($column, $this->sanitaze($search)); - } - break; - case "column_search": - $columnSearch = $this->parser->getColumnsSearch(); - if (!$columnSearch) return; - - foreach($columnSearch as $key => $column) { - if (!$this->columnExists($column['data'])) continue; - $closure($column['data'], $this->sanitaze($column['search']['value'])); - } - break; - case "order": - $order = $this->parser->getOrder(); - if (!$order) return; + public function setColumns(array $columns) + { + $this->columns = $columns; + } - $orderArray = []; + public function getColumns() + { + return $this->columns; + } - foreach($order as $orderBy) { - if (!isset($orderBy['dir']) || !isset($orderBy['column'])) continue; - $orderDir = $orderBy['dir']; + public function columnExists($column) + { + return in_array($column, $this->columns); + } - $column = $this->parser->getColumnById($orderBy['column']); - if (is_null($column) || !$this->columnExists($column)) continue; + public function getParser() + { + return $this->parser; + } - $orderArray[] = "{$column} {$orderDir}"; + public function formResponse($options) + { + $defaults = [ + 'total' => 0, + 'filtered' => 0, + 'data' => [] + ]; + $options += $defaults; + + $response = []; + $response['draw'] = $this->parser->getDraw(); + $response['recordsTotal'] = $options['total']; + $response['recordsFiltered'] = $options['filtered']; + + if (count($options['data'])) { + foreach ($options['data'] as $item) { + if (isset($item['id'])) { + $item['DT_RowId'] = $item['id']; + } + + $response['data'][] = $item; + } + } else { + $response['data'] = []; } - $closure($orderArray); - break; - default: - throw new \Exception('Unknown bind type'); + return $response; } - } + public function sanitaze($string) + { + return mb_substr($string, 0, $this->length); + } + + public function bind($case, $closure) + { + switch ($case) { + case "global_search": + $search = $this->parser->getSearchValue(); + if (!mb_strlen($search)) + return; + + foreach ($this->parser->getSearchableColumns() as $column) { + if (!$this->columnExists($column)) + continue; + $closure($column, $this->sanitaze($search)); + } + break; + case "column_search": + $columnSearch = $this->parser->getColumnsSearch(); + if (!$columnSearch) + return; + + foreach ($columnSearch as $key => $column) { + if (!$this->columnExists($column['data'])) + continue; + $closure($column['data'], $this->sanitaze($column['search']['value'])); + } + break; + case "order": + $order = $this->parser->getOrder(); + if (!$order) + return; + + $orderArray = []; + + foreach ($order as $orderBy) { + if (!isset($orderBy['dir']) || !isset($orderBy['column'])) + continue; + $orderDir = $orderBy['dir']; + + $column = $this->parser->getColumnById($orderBy['column']); + if (is_null($column) || !$this->columnExists($column)) + continue; + + $orderArray[] = "{$column} {$orderDir}"; + } + + $closure($orderArray); + break; + default: + throw new \Exception('Unknown bind type'); + } + } } diff --git a/src/Adapters/ArrayAdapter.php b/src/Adapters/ArrayAdapter.php index 3f3086c..2be492d 100644 --- a/src/Adapters/ArrayAdapter.php +++ b/src/Adapters/ArrayAdapter.php @@ -2,101 +2,106 @@ namespace DataTables\Adapters; -class ArrayAdapter extends AdapterInterface { - - protected $array = []; - protected $column = []; - protected $global = []; - protected $order = []; - - public function setArray(array $array) { - $this->array = $array; - } - - public function getResponse() { - $limit = $this->parser->getLimit(); - $offset = $this->parser->getOffset(); - $total = count($this->array); - - $this->bind('global_search', function($column, $search) { - $this->global[$column][] = $search; - }); - - $this->bind('column_search', function($column, $search) { - $this->column[$column][] = $search; - }); - - $this->bind('order', function($order) { - $this->order = $order; - }); - - if(count($this->global) || count($this->column)) { - $items = array_filter($this->array, function($item) { - $check = false; - - if (count($this->global)) { - foreach($this->global as $column=>$filters) { - foreach($filters as $search) { - $check = (strpos($item[$column], $search) !== false); - if ($check) break 2; - } - } - } else { - $check = true; - } +class ArrayAdapter extends AdapterInterface +{ - if (count($this->column) && $check) { - foreach($this->column as $column=>$filters) { - foreach($filters as $search) { - $check = (strpos($item[$column], $search) !== false); - if (!$check) break 2; - } - } - } + protected $array = []; + protected $column = []; + protected $global = []; + protected $order = []; - if ($check) { - return $item; - } - }); - } else { - $items = $this->array; + public function setArray(array $array) + { + $this->array = $array; } - $filtered = count($items); + public function getResponse() + { + $limit = $this->parser->getLimit(); + $offset = $this->parser->getOffset(); + $total = count($this->array); + + $this->bind('global_search', function($column, $search) { + $this->global[$column][] = $search; + }); + + $this->bind('column_search', function($column, $search) { + $this->column[$column][] = $search; + }); + + $this->bind('order', function($order) { + $this->order = $order; + }); + + if (count($this->global) || count($this->column)) { + $items = array_filter($this->array, function($item) { + $check = false; + + if (count($this->global)) { + foreach ($this->global as $column => $filters) { + foreach ($filters as $search) { + $check = (strpos($item[$column], $search) !== false); + if ($check) + break 2; + } + } + } else { + $check = true; + } + + if (count($this->column) && $check) { + foreach ($this->column as $column => $filters) { + foreach ($filters as $search) { + $check = (strpos($item[$column], $search) !== false); + if (!$check) + break 2; + } + } + } + + if ($check) { + return $item; + } + }); + } else { + $items = $this->array; + } - if ($this->order) { - $args = []; + $filtered = count($items); - foreach($this->order as $order) { - $tmp = []; - list($column, $dir) = explode(' ', $order); + if ($this->order) { + $args = []; - foreach($items as $key=>$item) { - $tmp[$key] = $item[$column]; - } + foreach ($this->order as $order) { + $tmp = []; + list($column, $dir) = explode(' ', $order); - $args[] = $tmp; - $args[] = ($dir == 'desc') ? SORT_DESC : SORT_ASC; - } + foreach ($items as $key => $item) { + $tmp[$key] = $item[$column]; + } + $args[] = $tmp; + $args[] = ($dir == 'desc') ? SORT_DESC : SORT_ASC; + } - $args[] = &$items; - call_user_func_array('array_multisort', $args); - } - if ($offset > 1) { - $items = array_slice($items, $offset); - } + $args[] = &$items; + call_user_func_array('array_multisort', $args); + } - if ($limit) { - $items = array_slice($items, 0, $limit); - } + if ($offset > 1) { + $items = array_slice($items, $offset); + } + + if ($limit) { + $items = array_slice($items, 0, $limit); + } - return $this->formResponse([ - 'total' => (int)$total, - 'filtered' => (int)$filtered, - 'data' => $items, - ]); - } + return $this->formResponse([ + 'total' => (int) $total, + 'filtered' => (int) $filtered, + 'data' => $items, + ]); + } } diff --git a/src/Adapters/QueryBuilder.php b/src/Adapters/QueryBuilder.php index fd394db..30cd228 100644 --- a/src/Adapters/QueryBuilder.php +++ b/src/Adapters/QueryBuilder.php @@ -1,49 +1,154 @@ builder = $builder; - } - - public function getResponse() { - $builder = new PQueryBuilder([ - 'builder' => $this->builder, - 'limit' => 1, - 'page' => 1, - ]); - - $total = $builder->getPaginate(); - - $this->bind('global_search', function($column, $search) { - $this->builder->orWhere("{$column} LIKE :key_{$column}:", ["key_{$column}" => "%{$search}%"]); - }); - - $this->bind('column_search', function($column, $search) { - $this->builder->andWhere("{$column} LIKE :key_{$column}:", ["key_{$column}" => "%{$search}%"]); - }); - - $this->bind('order', function($order) { - if (!empty($order)) { - $this->builder->orderBy(implode(', ', $order)); - } - }); - - $builder = new PQueryBuilder([ - 'builder' => $this->builder, - 'limit' => $this->parser->getLimit(), - 'page' => $this->parser->getPage(), - ]); - - $filtered = $builder->getPaginate(); - - return $this->formResponse([ - 'total' => $total->total_items, - 'filtered' => $filtered->total_items, - 'data' => $filtered->items->toArray(), - ]); - } +class QueryBuilder extends AdapterInterface +{ + + /** + * + * @var \Phalcon\Mvc\Model\Query\Builder + */ + protected $builder; + protected $originalColumns; + + public function setBuilder($builder) + { + $this->builder = $builder; + } + + public function setColumns(array $columns) + { + $this->originalColumns = $columns; + + foreach ($columns as $i => $column) { + if (is_array($column)) { + $columns[$i] = array_keys($column)[0]; + } + } + + $this->columns = $columns; + } + + public function getResponse() + { + $builder = new PQueryBuilder([ + 'builder' => $this->builder, + 'limit' => 1, + 'page' => 1, + ]); + + $total = $builder->getPaginate(); + + $this->bind('global_search', function($column, $search) { + $this->builder->orWhere("{$column} LIKE :key_{$column}:", ["key_{$column}" => "%{$search}%"]); + }); + + $this->bind('column_search', function($column, $search) { + $this->builder->andWhere("{$column} LIKE :key_{$column}:", ["key_{$column}" => "%{$search}%"]); + }); + + $this->bind('order', function($order) { + + if (!empty($order)) { + $this->builder->orderBy(implode(', ', $order)); + } + }); + + $builder = new PQueryBuilder([ + 'builder' => $this->builder, + 'limit' => $this->parser->getLimit(), + 'page' => $this->parser->getPage(), + ]); + + + /* @var $filtered \Phalcon\Mvc\Model\Resultset */ + $filtered = $builder->getPaginate(); + + /* @var $metadata \Phalcon\Mvc\Model\MetaData */ + $metadata = \Phalcon\Di::getDefault()->get('modelsMetadata'); + + $item = $filtered->items->getFirst(); + if ($item instanceof \Phalcon\Mvc\Model) { + $filtered->items->rewind(); + $columnMap = $metadata->getColumnMap($item); + $columnMap = array_combine($columnMap, $columnMap); + + $extractMethods = function ($item) { + $reflection = new \ReflectionClass($item); + $itemMethods = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC); + $itemMethods = array_map(function(\ReflectionMethod $reflectionMethod) { + return $reflectionMethod->getName(); + }, $itemMethods); + return array_combine($itemMethods, $itemMethods); + }; + + // if use array_diff we can catch error, because $this->originalColumns can have array item + $attributes = $methods = []; + foreach ($this->originalColumns as $itemColumn) { + $itemData = []; + if (is_string($itemColumn)) { + // check that it is item attribute + if (isset($columnMap[$itemColumn])) { + $attributes[] = $itemColumn; + } + } elseif (is_array($itemColumn)) { + /** + * Possible variants + * itemColumn => [methodName => [param1, param2]] - method with parameters + * itemColumn => methodName] - method without parameters + * + */ + $columnName = array_keys($itemColumn)[0]; + $methodData = $itemColumn[$columnName]; + + if (!isset($columnMap[$columnName])) { + // undefined columnName + continue; + } + $parameters = null; + if (is_array($methodData)) { + $methodName = array_keys($methodData)[0]; + $parameters = $methodData[$methodName]; + } else { + $methodName = $methodData; + } + // check that it is existed method + if (empty($itemMethods)) { + $itemMethods = $extractMethods($item); + } + + if (isset($itemMethods[$methodName])) { + $methods[$columnName] = compact('methodName', 'parameters'); + } + } + } + + $data = []; + foreach ($filtered->items as $item) { + $itemData = []; + foreach ($attributes as $attr) { + $itemData[$attr] = $item->readAttribute($attr); + } + + foreach ($methods as $columnName => $method) { + $parameters = !empty($method['parameters']) ? $method['parameters'] : null; + $itemData[$columnName] = call_user_func_array([$item, $method['methodName']], $parameters); + } + + $data[] = $itemData; + } + } else { + $data = $filtered->items->toArray(); + } + + return $this->formResponse([ + 'total' => $total->total_items, + 'filtered' => $filtered->total_items, + 'data' => $data, + ]); + } + } diff --git a/src/Adapters/ResultSet.php b/src/Adapters/ResultSet.php index 5a50e7e..713044c 100644 --- a/src/Adapters/ResultSet.php +++ b/src/Adapters/ResultSet.php @@ -1,108 +1,114 @@ parser->getLimit(); - $offset = $this->parser->getOffset(); - $total = $this->resultSet->count(); - - $this->bind('global_search', function($column, $search) { - $this->global[$column][] = $search; - }); +class ResultSet extends AdapterInterface +{ + + protected $resultSet; + protected $column = []; + protected $global = []; + protected $order = []; + + public function getResponse() + { + $limit = $this->parser->getLimit(); + $offset = $this->parser->getOffset(); + $total = $this->resultSet->count(); + + $this->bind('global_search', function($column, $search) { + $this->global[$column][] = $search; + }); + + $this->bind('column_search', function($column, $search) { + $this->column[$column][] = $search; + }); + + $this->bind('order', function($order) { + $this->order = $order; + }); + + if (count($this->global) || count($this->column)) { + $filter = $this->resultSet->filter(function($item) { + $check = false; + + if (count($this->global)) { + foreach ($this->global as $column => $filters) { + foreach ($filters as $search) { + $check = (strpos($item->$column, $search) !== false); + if ($check) + break 2; + } + } + } else { + $check = true; + } + + if (count($this->column) && $check) { + foreach ($this->column as $column => $filters) { + foreach ($filters as $search) { + $check = (strpos($item->$column, $search) !== false); + if (!$check) + break 2; + } + } + } + + if ($check) { + return $item; + } + }); + + $filtered = count($filter); + $items = array_map(function($item) { + return $item->toArray(); + }, $filter); + } else { + $filtered = $total; + $items = $this->resultSet->filter(function($item) { + return $item->toArray(); + }); + } - $this->bind('column_search', function($column, $search) { - $this->column[$column][] = $search; - }); + if ($this->order) { + $args = []; - $this->bind('order', function($order) { - $this->order = $order; - }); + foreach ($this->order as $order) { + $tmp = []; + list($column, $dir) = explode(' ', $order); - if(count($this->global) || count($this->column)) { - $filter = $this->resultSet->filter(function($item){ - $check = false; + foreach ($items as $key => $item) { + $tmp[$key] = $item[$column]; + } - if (count($this->global)) { - foreach($this->global as $column=>$filters) { - foreach($filters as $search) { - $check = (strpos($item->$column, $search) !== false); - if ($check) break 2; + $args[] = $tmp; + $args[] = ($dir == 'desc') ? SORT_DESC : SORT_ASC; } - } - } else { - $check = true; - } - if (count($this->column) && $check) { - foreach($this->column as $column=>$filters) { - foreach($filters as $search) { - $check = (strpos($item->$column, $search) !== false); - if (!$check) break 2; - } - } + $args[] = &$items; + call_user_func_array('array_multisort', $args); } - if ($check) { - return $item; + if ($offset > 1) { + $items = array_slice($items, ($offset - 1)); } - }); - - $filtered = count($filter); - $items = array_map(function($item) { - return $item->toArray(); - }, $filter); - } else { - $filtered = $total; - $items = $this->resultSet->filter(function($item) { - return $item->toArray(); - }); - } - - if ($this->order) { - $args = []; - foreach($this->order as $order) { - $tmp = []; - list($column, $dir) = explode(' ', $order); - - foreach($items as $key=>$item) { - $tmp[$key] = $item[$column]; + if ($limit) { + $items = array_slice($items, 0, $limit); } - $args[] = $tmp; - $args[] = ($dir == 'desc') ? SORT_DESC : SORT_ASC; - } - - $args[] = &$items; - call_user_func_array('array_multisort', $args); + return $this->formResponse([ + 'total' => (int) $total, + 'filtered' => (int) $filtered, + 'data' => $items, + ]); } - if ($offset > 1) { - $items = array_slice($items, ($offset - 1)); + public function setResultSet($resultSet) + { + $this->resultSet = $resultSet; } - if ($limit) { - $items = array_slice($items, 0, $limit); - } - - return $this->formResponse([ - 'total' => (int)$total, - 'filtered' => (int)$filtered, - 'data' => $items, - ]); - } - - public function setResultSet($resultSet) { - $this->resultSet = $resultSet; - } - } diff --git a/src/DataTable.php b/src/DataTable.php index ad4bdd2..42ae149 100644 --- a/src/DataTable.php +++ b/src/DataTable.php @@ -1,4 +1,5 @@ 20, - 'length' => 50, - ]; + /** + * + * @var ParamsParser + */ + public $parser; - $this->options = $options + $default; - $this->parser = new ParamsParser($this->options['limit']); - } + public function __construct($options = []) + { + $default = [ + 'limit' => 20, + 'length' => 50, + ]; - public function getParams() { - return $this->parser->getParams(); - } + $this->options = $options + $default; + $this->parser = new ParamsParser($this->options['limit']); + } - public function getResponse() { - return !empty($this->response) ? $this->response : []; - } + public function getParams() + { + return $this->parser->getParams(); + } - public function sendResponse() { - if ($this->di->has('view')) { - $this->di->get('view')->disable(); + public function getResponse() + { + return !empty($this->response) ? $this->response : []; } - $response = new Response(); - $response->setContentType('application/json', 'utf8'); - $response->setJsonContent($this->getResponse()); - $response->send(); - } + public function sendResponse() + { + if ($this->di->has('view')) { + $this->di->get('view')->disable(); + } - public function fromBuilder($builder, $columns = []) { - if (empty($columns)) { - $columns = $builder->getColumns(); - $columns = (is_array($columns)) ? $columns : array_map('trim', explode(',', $columns)); + $response = new Response(); + $response->setContentType('application/json', 'utf8'); + $response->setJsonContent($this->getResponse()); + $response->send(); } - $adapter = new QueryBuilder($this->options['length']); - $adapter->setBuilder($builder); - $adapter->setParser($this->parser); - $adapter->setColumns($columns); - $this->response = $adapter->getResponse(); + public function fromBuilder($builder, $columns = []) + { + if (empty($columns)) { + $columns = $builder->getColumns(); + $columns = (is_array($columns)) ? $columns : array_map('trim', explode(',', $columns)); + } - return $this; - } + $adapter = new QueryBuilder($this->options['length']); + $adapter->setBuilder($builder); + $adapter->setParser($this->parser); + $adapter->setColumns($columns); + $this->response = $adapter->getResponse(); - public function fromResultSet($resultSet, $columns = []) { - if(empty($columns) && $resultSet->count() > 0) { - $columns = array_keys($resultSet->getFirst()->toArray()); - $resultSet->rewind(); + + return $this; } - $adapter = new ResultSet($this->options['length']); - $adapter->setResultSet($resultSet); - $adapter->setParser($this->parser); - $adapter->setColumns($columns); - $this->response = $adapter->getResponse(); + public function fromResultSet($resultSet, $columns = []) + { + if (empty($columns) && $resultSet->count() > 0) { + $columns = array_keys($resultSet->getFirst()->toArray()); + $resultSet->rewind(); + } - return $this; - } + $adapter = new ResultSet($this->options['length']); + $adapter->setResultSet($resultSet); + $adapter->setParser($this->parser); + $adapter->setColumns($columns); + $this->response = $adapter->getResponse(); - public function fromArray($array, $columns = []) { - if(empty($columns) && count($array) > 0) { - $columns = array_keys(current($array)); + return $this; } - $adapter = new ArrayAdapter($this->options['length']); - $adapter->setArray($array); - $adapter->setParser($this->parser); - $adapter->setColumns($columns); - $this->response = $adapter->getResponse(); + public function fromArray($array, $columns = []) + { + if (empty($columns) && count($array) > 0) { + $columns = array_keys(current($array)); + } + + $adapter = new ArrayAdapter($this->options['length']); + $adapter->setArray($array); + $adapter->setParser($this->parser); + $adapter->setColumns($columns); + $this->response = $adapter->getResponse(); - return $this; - } + return $this; + } } diff --git a/src/ParamsParser.php b/src/ParamsParser.php index 36e6a07..c3d4bee 100644 --- a/src/ParamsParser.php +++ b/src/ParamsParser.php @@ -1,82 +1,99 @@ null, - 'start' => 1, - 'length' => $limit, - 'columns' => [], - 'search' => [], - 'order' => [] - ]; - - $request = $this->di->get('request'); - $requestParams = $request->isPost() ? $request->getPost() : $request->getQuery(); - $this->params = (array)$requestParams + $params; - $this->setPage(); - } - - public function getParams() { - return $this->params; - } - - public function setPage() { - $this->page = (int)(floor($this->params['start'] / $this->params['length']) + 1); - } - - public function getPage() { - return $this->page; - } - - public function getColumnsSearch() { - return array_filter(array_map(function($item) { - return (isset($item['search']['value']) && strlen($item['search']['value'])) ? $item : null; - }, $this->params['columns'])); - } - - public function getSearchableColumns() { - return array_filter(array_map(function($item) { - return (isset($item['searchable']) && $item['searchable'] === "true") ? $item['data'] : null; - }, $this->params['columns'])); - } - - public function getDraw() { - return $this->params['draw']; - } - - public function getLimit() { - return $this->params['length']; - } - - public function getOffset() { - return $this->params['start']; - } - - public function getColumns() { - return $this->params['columns']; - } - - public function getColumnById($id) { - return isset($this->params['columns'][$id]['data']) ? $this->params['columns'][$id]['data'] : null; - } - - public function getSearch() { - return $this->params['search']; - } - - public function getOrder() { - return $this->params['order']; - } - - public function getSearchValue() { - return isset($this->params['search']['value']) ? $this->params['search']['value'] : ''; - } +class ParamsParser extends Component +{ + + protected $params = []; + protected $page = 1; + + public function __construct($limit) + { + $params = [ + 'draw' => null, + 'start' => 1, + 'length' => $limit, + 'columns' => [], + 'search' => [], + 'order' => [] + ]; + + $request = $this->di->get('request'); + $requestParams = $request->isPost() ? $request->getPost() : $request->getQuery(); + $this->params = (array) $requestParams + $params; + $this->setPage(); + } + + public function getParams() + { + return $this->params; + } + + public function setPage() + { + $this->page = (int) (floor($this->params['start'] / $this->params['length']) + 1); + } + + public function getPage() + { + return $this->page; + } + + public function getColumnsSearch() + { + return array_filter(array_map(function($item) { + return (isset($item['search']['value']) && strlen($item['search']['value'])) ? $item : null; + }, $this->params['columns'])); + } + + public function getSearchableColumns() + { + return array_filter(array_map(function($item) { + return (isset($item['searchable']) && $item['searchable'] === "true") ? $item['data'] : null; + }, $this->params['columns'])); + } + + public function getDraw() + { + return $this->params['draw']; + } + + public function getLimit() + { + return $this->params['length']; + } + + public function getOffset() + { + return $this->params['start']; + } + + public function getColumns() + { + return $this->params['columns']; + } + + public function getColumnById($id) + { + return isset($this->params['columns'][$id]['data']) ? $this->params['columns'][$id]['data'] : null; + } + + public function getSearch() + { + return $this->params['search']; + } + + public function getOrder() + { + return $this->params['order']; + } + + public function getSearchValue() + { + return isset($this->params['search']['value']) ? $this->params['search']['value'] : ''; + } + } From 72bdd88489f19f609d1ff23fb24a9cfb205d7df5 Mon Sep 17 00:00:00 2001 From: mzf Date: Thu, 4 Feb 2016 11:46:54 +0700 Subject: [PATCH 02/31] Added possibility to getting values only from item method, thats not assigned to any field Example['actions' => ['tableActions' => ['param']], 'fake' => true] --- src/Adapters/QueryBuilder.php | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Adapters/QueryBuilder.php b/src/Adapters/QueryBuilder.php index 30cd228..4e38d9c 100644 --- a/src/Adapters/QueryBuilder.php +++ b/src/Adapters/QueryBuilder.php @@ -25,7 +25,16 @@ public function setColumns(array $columns) foreach ($columns as $i => $column) { if (is_array($column)) { - $columns[$i] = array_keys($column)[0]; + $columnName = array_keys($column)[0]; + // check thats not fake field + // if column faked and get values from item method we dont add column + if (!empty($column['fake'])) { + unset($columns[$i]); + } else { + $columns[$i] = $columnName; + } + } else { + $columns[$i] = $column; } } @@ -106,7 +115,7 @@ public function getResponse() if (!isset($columnMap[$columnName])) { // undefined columnName - continue; + //continue; } $parameters = null; if (is_array($methodData)) { @@ -134,7 +143,7 @@ public function getResponse() } foreach ($methods as $columnName => $method) { - $parameters = !empty($method['parameters']) ? $method['parameters'] : null; + $parameters = !empty($method['parameters']) ? $method['parameters'] : []; $itemData[$columnName] = call_user_func_array([$item, $method['methodName']], $parameters); } From 8a2b8f424edabab66cd02d0b741ca095d8dedf7d Mon Sep 17 00:00:00 2001 From: mzf Date: Tue, 9 Feb 2016 17:03:20 +0700 Subject: [PATCH 03/31] Changed a method of obtaining list of columns. --- src/Adapters/QueryBuilder.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Adapters/QueryBuilder.php b/src/Adapters/QueryBuilder.php index 4e38d9c..0959b4c 100644 --- a/src/Adapters/QueryBuilder.php +++ b/src/Adapters/QueryBuilder.php @@ -82,7 +82,8 @@ public function getResponse() $item = $filtered->items->getFirst(); if ($item instanceof \Phalcon\Mvc\Model) { $filtered->items->rewind(); - $columnMap = $metadata->getColumnMap($item); + + $columnMap = $metadata->getAttributes($item); $columnMap = array_combine($columnMap, $columnMap); $extractMethods = function ($item) { From 1341d83be7aa75eb58a0912a1237e30abb836339 Mon Sep 17 00:00:00 2001 From: mzf Date: Thu, 18 Feb 2016 11:20:51 +0700 Subject: [PATCH 04/31] Fixed error arising by search in the table that having fake field --- src/Adapters/AdapterInterface.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Adapters/AdapterInterface.php b/src/Adapters/AdapterInterface.php index 8a0f74a..784841d 100644 --- a/src/Adapters/AdapterInterface.php +++ b/src/Adapters/AdapterInterface.php @@ -35,7 +35,19 @@ public function getColumns() public function columnExists($column) { - return in_array($column, $this->columns); + $notFakeField = true; + if (is_array($this->originalColumns)) { + foreach ($this->originalColumns as $columnDefinition) { + if (!is_array($columnDefinition) || array_keys($columnDefinition)[0] != $column) { + continue; + } + if (!empty($columnDefinition[$column]['fake'])) { + $notFakeField = false; + } + } + } + + return in_array($column, $this->columns) && $notFakeField; } public function getParser() From ef8affb892b5213566b9c424873957dc86899fb8 Mon Sep 17 00:00:00 2001 From: mzf Date: Thu, 18 Feb 2016 18:32:44 +0700 Subject: [PATCH 05/31] Travis-CI fix --- src/Adapters/AdapterInterface.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Adapters/AdapterInterface.php b/src/Adapters/AdapterInterface.php index 784841d..586eb1b 100644 --- a/src/Adapters/AdapterInterface.php +++ b/src/Adapters/AdapterInterface.php @@ -36,7 +36,7 @@ public function getColumns() public function columnExists($column) { $notFakeField = true; - if (is_array($this->originalColumns)) { + if (isset($this->originalColumns) && is_array($this->originalColumns)) { foreach ($this->originalColumns as $columnDefinition) { if (!is_array($columnDefinition) || array_keys($columnDefinition)[0] != $column) { continue; From 441cd9fce5e9450a941352a772331420ca7db6ee Mon Sep 17 00:00:00 2001 From: Stefan Chiriac Date: Sun, 13 Nov 2016 21:09:01 +0200 Subject: [PATCH 06/31] add multi models search --- spec/suite/Adapters/AdapterInterfaceSpec.php | 8 +- src/Adapters/AdapterInterface.php | 78 +++++++++++++++++--- src/Adapters/ArrayAdapter.php | 34 ++++++--- src/Adapters/QueryBuilder.php | 12 +-- src/Adapters/ResultSet.php | 20 +++-- 5 files changed, 117 insertions(+), 35 deletions(-) diff --git a/spec/suite/Adapters/AdapterInterfaceSpec.php b/spec/suite/Adapters/AdapterInterfaceSpec.php index 357fa56..c5ac4a5 100644 --- a/spec/suite/Adapters/AdapterInterfaceSpec.php +++ b/spec/suite/Adapters/AdapterInterfaceSpec.php @@ -63,10 +63,12 @@ public function getResponse() { it("->columnExists()", function() { $adapter = new MyAdapter(10); - $columns = ['user', 'name', 'email']; + $columns = ['user', 'name', 'email', ['c.id', 'alias' => 'id']]; $adapter->setColumns($columns); - expect($adapter->columnExists('user'))->toBe(true); - expect($adapter->columnExists('user1'))->toBe(false); + expect($adapter->columnExists('user'))->toBe('user'); + expect($adapter->columnExists('id'))->toBe('c.id'); + expect($adapter->columnExists('id', true))->toBe('id'); + expect($adapter->columnExists('user1'))->toBe(null); }); diff --git a/src/Adapters/AdapterInterface.php b/src/Adapters/AdapterInterface.php index 290f309..9e4e9ea 100644 --- a/src/Adapters/AdapterInterface.php +++ b/src/Adapters/AdapterInterface.php @@ -21,6 +21,31 @@ public function setParser(ParamsParser $parser) { } public function setColumns(array $columns) { + foreach ($columns as $i => $column) { + if (is_array($column)) { + $columnName = $column[0]; + $columns[$i] = [$columnName]; + if (!isset($column['alias'])) { + $pos = strpos($column, '.'); + if ($pos !== false) { + $columns[$i]['alias'] = substr($column, $pos + 1); + } + } + } else { + $colArray = explode(" as ", $column); + $column = $colArray[0]; + $columns[$i] = [$column]; + if (isset($colArray[1])) { + $columns[$i]['alias'] = $colArray[1]; + } else { + $pos = strpos($column, '.'); + if ($pos !== false) { + $columns[$i]['alias'] = substr($column, $pos + 1); + } + } + } + } + $this->columns = $columns; } @@ -28,8 +53,36 @@ public function getColumns() { return $this->columns; } - public function columnExists($column) { - return in_array($column, $this->columns); + public function columnExists($column, $getAlias = false) { + $col = null; + if (isset($this->columns) && is_array($this->columns)) { + foreach ($this->columns as $columnDefinition) { + if (is_array($columnDefinition)) { + if ($columnDefinition[0] != $column) { + if (isset($columnDefinition['alias']) && $columnDefinition['alias'] == $column) { + if ($getAlias) { + $col = $columnDefinition['alias']; + } else { + $col = $columnDefinition[0]; + } + break; + } + } else { + if ($getAlias && isset($columnDefinition['alias'])) { + $col = $columnDefinition['alias']; + } else { + $col = $columnDefinition[0]; + } + break; + } + } elseif ($column == $columnDefinition) { + $col = $column; + break; + } + } + } + + return $col; } public function getParser() { @@ -68,24 +121,26 @@ public function sanitaze($string) { return mb_substr($string, 0, $this->length); } - public function bind($case, $closure) { + public function bind($case, $getAlias, $closure) { switch($case) { case "global_search": $search = $this->parser->getSearchValue(); if (!mb_strlen($search)) return; - foreach($this->parser->getSearchableColumns() as $column) { - if (!$this->columnExists($column)) continue; - $closure($column, $this->sanitaze($search)); + foreach ($this->parser->getSearchableColumns() as $column) { + $col = $this->columnExists($column, $getAlias); + if (is_null($col)) continue; + $closure($col, $this->sanitaze($search)); } break; case "column_search": $columnSearch = $this->parser->getColumnsSearch(); if (!$columnSearch) return; - foreach($columnSearch as $key => $column) { - if (!$this->columnExists($column['data'])) continue; - $closure($column['data'], $this->sanitaze($column['search']['value'])); + foreach ($columnSearch as $key => $column) { + $col = $this->columnExists($column['data'], $getAlias); + if (is_null($col)) continue; + $closure($col, $this->sanitaze($column['search']['value'])); } break; case "order": @@ -99,9 +154,10 @@ public function bind($case, $closure) { $orderDir = $orderBy['dir']; $column = $this->parser->getColumnById($orderBy['column']); - if (is_null($column) || !$this->columnExists($column)) continue; + $col = $this->columnExists($column, $getAlias); + if (is_null($col)) continue; - $orderArray[] = "{$column} {$orderDir}"; + $orderArray[] = "{$col} {$orderDir}"; } $closure($orderArray); diff --git a/src/Adapters/ArrayAdapter.php b/src/Adapters/ArrayAdapter.php index 3f3086c..a766be2 100644 --- a/src/Adapters/ArrayAdapter.php +++ b/src/Adapters/ArrayAdapter.php @@ -18,15 +18,15 @@ public function getResponse() { $offset = $this->parser->getOffset(); $total = count($this->array); - $this->bind('global_search', function($column, $search) { + $this->bind('global_search', false, function($column, $search) { $this->global[$column][] = $search; }); - $this->bind('column_search', function($column, $search) { + $this->bind('column_search', false, function($column, $search) { $this->column[$column][] = $search; }); - $this->bind('order', function($order) { + $this->bind('order', true, function($order) { $this->order = $order; }); @@ -35,10 +35,16 @@ public function getResponse() { $check = false; if (count($this->global)) { - foreach($this->global as $column=>$filters) { - foreach($filters as $search) { - $check = (strpos($item[$column], $search) !== false); - if ($check) break 2; + foreach ($this->global as $column => $filters) { + foreach ($filters as $search) { + $col = $this->columnExists($column, true); + if (!is_null($col)) { + $check = (strpos($item[$col], $search) !== false); + } else { + $check = false; + } + if ($check) + break 2; } } } else { @@ -46,10 +52,16 @@ public function getResponse() { } if (count($this->column) && $check) { - foreach($this->column as $column=>$filters) { - foreach($filters as $search) { - $check = (strpos($item[$column], $search) !== false); - if (!$check) break 2; + foreach ($this->column as $column => $filters) { + foreach ($filters as $search) { + $col = $this->columnExists($column, true); + if (!is_null($col)) { + $check = (strpos($item[$col], $search) !== false); + } else { + $check = false; + } + if (!$check) + break 2; } } } diff --git a/src/Adapters/QueryBuilder.php b/src/Adapters/QueryBuilder.php index fd394db..f31ede0 100644 --- a/src/Adapters/QueryBuilder.php +++ b/src/Adapters/QueryBuilder.php @@ -18,15 +18,17 @@ public function getResponse() { $total = $builder->getPaginate(); - $this->bind('global_search', function($column, $search) { - $this->builder->orWhere("{$column} LIKE :key_{$column}:", ["key_{$column}" => "%{$search}%"]); + $this->bind('global_search', false, function($column, $search) { + $key = "key_" . str_replace(".", "", $column); + $this->builder->orWhere("{$column} LIKE :{$key}:", ["{$key}" => "%{$search}%"]); }); - $this->bind('column_search', function($column, $search) { - $this->builder->andWhere("{$column} LIKE :key_{$column}:", ["key_{$column}" => "%{$search}%"]); + $this->bind('column_search', false, function($column, $search) { + $key = "key_" . str_replace(" ", "", str_replace(".", "", $column)); + $this->builder->andWhere("{$column} LIKE :{$key}:", ["{$key}" => "%{$search}%"]); }); - $this->bind('order', function($order) { + $this->bind('order', false, function($order) { if (!empty($order)) { $this->builder->orderBy(implode(', ', $order)); } diff --git a/src/Adapters/ResultSet.php b/src/Adapters/ResultSet.php index 5a50e7e..b6ad296 100644 --- a/src/Adapters/ResultSet.php +++ b/src/Adapters/ResultSet.php @@ -15,15 +15,15 @@ public function getResponse() { $offset = $this->parser->getOffset(); $total = $this->resultSet->count(); - $this->bind('global_search', function($column, $search) { + $this->bind('global_search', false, function($column, $search) { $this->global[$column][] = $search; }); - $this->bind('column_search', function($column, $search) { + $this->bind('column_search', false, function($column, $search) { $this->column[$column][] = $search; }); - $this->bind('order', function($order) { + $this->bind('order', true, function($order) { $this->order = $order; }); @@ -34,7 +34,12 @@ public function getResponse() { if (count($this->global)) { foreach($this->global as $column=>$filters) { foreach($filters as $search) { - $check = (strpos($item->$column, $search) !== false); + $col = $this->columnExists($column, true); + if (!is_null($col)) { + $check = (strpos($item->$col, $search) !== false); + } else { + $check = false; + } if ($check) break 2; } } @@ -45,7 +50,12 @@ public function getResponse() { if (count($this->column) && $check) { foreach($this->column as $column=>$filters) { foreach($filters as $search) { - $check = (strpos($item->$column, $search) !== false); + $col = $this->columnExists($column, true); + if (!is_null($col)) { + $check = (strpos($item->$col, $search) !== false); + } else { + $check = false; + } if (!$check) break 2; } } From 33a240eadd8c3647b7ada53e8fc23e8b4a360fe8 Mon Sep 17 00:00:00 2001 From: Stefan Chiriac Date: Mon, 14 Nov 2016 22:44:35 +0200 Subject: [PATCH 07/31] fix and add tests, fix column parsing --- spec/suite/Adapters/AdapterInterfaceSpec.php | 11 ++++++++--- src/Adapters/AdapterInterface.php | 4 +++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/spec/suite/Adapters/AdapterInterfaceSpec.php b/spec/suite/Adapters/AdapterInterfaceSpec.php index c5ac4a5..4b2caff 100644 --- a/spec/suite/Adapters/AdapterInterfaceSpec.php +++ b/spec/suite/Adapters/AdapterInterfaceSpec.php @@ -52,9 +52,9 @@ public function getResponse() { it("should successfully set columns", function() { $adapter = new MyAdapter(10); - $columns = ['user', 'name', 'email']; + $columns = ['user', 'name', 'c.email']; $adapter->setColumns($columns); - expect($adapter->getColumns())->toBe($columns); + expect($adapter->getColumns())->toBe([['user'], ['name'], ['c.email', 'alias' => 'email']]); }); @@ -63,11 +63,16 @@ public function getResponse() { it("->columnExists()", function() { $adapter = new MyAdapter(10); - $columns = ['user', 'name', 'email', ['c.id', 'alias' => 'id']]; + $columns = ['user', 'name', 'email', 'r.role as role', ['c.id', 'alias' => 'id']]; $adapter->setColumns($columns); expect($adapter->columnExists('user'))->toBe('user'); expect($adapter->columnExists('id'))->toBe('c.id'); expect($adapter->columnExists('id', true))->toBe('id'); + expect($adapter->columnExists('c.id'))->toBe('c.id'); + expect($adapter->columnExists('c.id', true))->toBe('id'); + expect($adapter->columnExists('role', true))->toBe('role'); + expect($adapter->columnExists('r.role'))->toBe('r.role'); + expect($adapter->columnExists('c.role'))->toBe(null); expect($adapter->columnExists('user1'))->toBe(null); }); diff --git a/src/Adapters/AdapterInterface.php b/src/Adapters/AdapterInterface.php index 9e4e9ea..e3fe24b 100644 --- a/src/Adapters/AdapterInterface.php +++ b/src/Adapters/AdapterInterface.php @@ -8,7 +8,7 @@ abstract class AdapterInterface { protected $parser = null; protected $columns = []; - protected $lentgh = 30; + protected $length = 30; public function __construct($length) { $this->length = $length; @@ -30,6 +30,8 @@ public function setColumns(array $columns) { if ($pos !== false) { $columns[$i]['alias'] = substr($column, $pos + 1); } + } else { + $columns[$i]['alias'] = $column['alias']; } } else { $colArray = explode(" as ", $column); From ca207e41b2ace368b7fe40a84a7ceb8646df03e6 Mon Sep 17 00:00:00 2001 From: Stefan Chiriac Date: Wed, 16 Nov 2016 23:36:20 +0200 Subject: [PATCH 08/31] adding implementation example --- README.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/README.md b/README.md index f7525ef..5c7e221 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ This is a [Phalcon Framework](http://phalconphp.com/) adapter for [DataTables](h * Ordering * Multiple column ordering * Column-based search +* Multi model search # Installation ### Installation via Composer @@ -91,6 +92,32 @@ class TestController extends \Phalcon\Mvc\Controller { } ``` +### Controller (using multi models): +```php +request->isAjax()) { + $builder = $this->modelsManager->createBuilder() + ->columns('u.id, u.name, u.email, u.name as role_name') + ->addFrom('Example\Models\User', 'u') + ->addFrom('Example\Models\Role', 'r') + ->where('u.role_id = r.id') + + $dataTables = new DataTable(); + $dataTables->fromBuilder($builder)->sendResponse(); + + // or pass an array of columns to the builder + $columns = ['u.id', 'u.name', 'u.email', ['u.name', 'alias' => 'role_name']]; + $dataTables = new DataTable(); + $dataTables->fromBuilder($builder, $columns)->sendResponse(); + } + } +} +``` + ### Model: ```php Date: Thu, 17 Nov 2016 22:08:29 +0200 Subject: [PATCH 09/31] merge fixes for search bypasses conditions and case insensitivity search --- src/Adapters/ArrayAdapter.php | 4 ++-- src/Adapters/QueryBuilder.php | 24 +++++++++++++++++++----- src/Adapters/ResultSet.php | 4 ++-- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/Adapters/ArrayAdapter.php b/src/Adapters/ArrayAdapter.php index a766be2..64160b5 100644 --- a/src/Adapters/ArrayAdapter.php +++ b/src/Adapters/ArrayAdapter.php @@ -39,7 +39,7 @@ public function getResponse() { foreach ($filters as $search) { $col = $this->columnExists($column, true); if (!is_null($col)) { - $check = (strpos($item[$col], $search) !== false); + $check = (stripos($item[$col], $search) !== false); } else { $check = false; } @@ -56,7 +56,7 @@ public function getResponse() { foreach ($filters as $search) { $col = $this->columnExists($column, true); if (!is_null($col)) { - $check = (strpos($item[$col], $search) !== false); + $check = (stripos($item[$col], $search) !== false); } else { $check = false; } diff --git a/src/Adapters/QueryBuilder.php b/src/Adapters/QueryBuilder.php index f31ede0..7a64fa3 100644 --- a/src/Adapters/QueryBuilder.php +++ b/src/Adapters/QueryBuilder.php @@ -4,6 +4,9 @@ class QueryBuilder extends AdapterInterface{ protected $builder; + private $global_search; + private $column_search; + private $_bind; public function setBuilder($builder) { $this->builder = $builder; @@ -17,15 +20,19 @@ public function getResponse() { ]); $total = $builder->getPaginate(); + $this->global_search = []; + $this->column_search = []; $this->bind('global_search', false, function($column, $search) { - $key = "key_" . str_replace(".", "", $column); - $this->builder->orWhere("{$column} LIKE :{$key}:", ["{$key}" => "%{$search}%"]); + $key = "keyg_" . str_replace(".", "", $column); + $this->global_search[] = "{$column} LIKE :{$key}:"; + $this->_bind[$key] = "%{$search}%"; }); $this->bind('column_search', false, function($column, $search) { - $key = "key_" . str_replace(" ", "", str_replace(".", "", $column)); - $this->builder->andWhere("{$column} LIKE :{$key}:", ["{$key}" => "%{$search}%"]); + $key = "keyc_" . str_replace(" ", "", str_replace(".", "", $column)); + $this->column_search[] = "{$column} LIKE :{$key}:"; + $this->_bind[$key] = "%{$search}%"; }); $this->bind('order', false, function($order) { @@ -34,9 +41,16 @@ public function getResponse() { } }); + if (!empty($this->global_search) || !empty($this->column_search)) { + $where = implode(' OR ', $this->global_search); + if (!empty($this->column_search)) + $where = (empty($where) ? '' : ('(' . $where . ') AND ')) . implode(' AND ', $this->column_search); + $this->builder->andWhere($where, $this->_bind); + } + $builder = new PQueryBuilder([ 'builder' => $this->builder, - 'limit' => $this->parser->getLimit(), + 'limit' => $this->parser->getLimit($total->total_items), 'page' => $this->parser->getPage(), ]); diff --git a/src/Adapters/ResultSet.php b/src/Adapters/ResultSet.php index b6ad296..ff90595 100644 --- a/src/Adapters/ResultSet.php +++ b/src/Adapters/ResultSet.php @@ -36,7 +36,7 @@ public function getResponse() { foreach($filters as $search) { $col = $this->columnExists($column, true); if (!is_null($col)) { - $check = (strpos($item->$col, $search) !== false); + $check = (stripos($item->$col, $search) !== false); } else { $check = false; } @@ -52,7 +52,7 @@ public function getResponse() { foreach($filters as $search) { $col = $this->columnExists($column, true); if (!is_null($col)) { - $check = (strpos($item->$col, $search) !== false); + $check = (stripos($item->$col, $search) !== false); } else { $check = false; } From 6129d93b9cee25d7108cff64920e3cf04d6b8777 Mon Sep 17 00:00:00 2001 From: Stefan Chiriac Date: Sat, 19 Nov 2016 23:03:58 +0200 Subject: [PATCH 10/31] update readme and composer.json --- .travis.yml | 3 +- README.md | 3 +- composer.json | 10 +- composer.lock | 244 +++++++--------------------------------------- kahlan-config.php | 8 +- 5 files changed, 51 insertions(+), 217 deletions(-) diff --git a/.travis.yml b/.travis.yml index e97912f..5cd561d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ php: - 5.5 - 5.4 - 5.6 + - 7.0 matrix: fast_finish: true @@ -15,7 +16,7 @@ before_script: - composer install --dev --prefer-source - phpenv rehash -script: vendor/bin/kahlan +script: vendor/kahlan/kahlan/bin/kahlan after_success: - "if [ $(phpenv version-name) = '5.6' ]; then curl -F 'json_file=@coveralls.json' https://coveralls.io/api/v1/jobs --verbose; fi" diff --git a/README.md b/README.md index 5c7e221..30f62ce 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ # About This is a [Phalcon Framework](http://phalconphp.com/) adapter for [DataTables](http://www.datatables.net/). +This version contains community fixes and multi model search support # Support ### Currently supported * QueryBuilder interface @@ -26,7 +27,7 @@ This is a [Phalcon Framework](http://phalconphp.com/) adapter for [DataTables](h ```json { "require": { - "m1ome/phalcon-datatables": "1.*" + "magnxpyr/phalcon-datatables": "1.*" } } ``` diff --git a/composer.json b/composer.json index a49262e..f07b8cf 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "m1ome/phalcon-datatables", + "name": "magnxpyr/phalcon-datatables", "description": "DataTables adapter for Phalcon Framework", "keywords": ["Phalcon", "DataTables", "Plugin"], "license": "MIT", @@ -12,6 +12,10 @@ { "name": "Pavel Makarenko", "email": "cryfall@gmail.com" + }, + { + "name": "Stefan Chiriac", + "email": "stefan@magnxpyr.com" } ], "minimum-stability": "dev", @@ -24,7 +28,7 @@ } }, "require-dev": { - "crysalead/kahlan": "~1.1", - "fzaninotto/faker": "1.5.*@dev" + "fzaninotto/faker": "1.5.*@dev", + "kahlan/kahlan": "^3.0" } } diff --git a/composer.lock b/composer.lock index 497292a..21ed834 100644 --- a/composer.lock +++ b/composer.lock @@ -1,121 +1,46 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "9f3494fc9480de0e0db04a5169ef1dcb", + "hash": "5ee59dd837f047bf54e4080f550737cf", + "content-hash": "640f30eb73f7ba840124e4416b8c5d45", "packages": [], "packages-dev": [ { - "name": "crysalead/box", - "version": "1.0.1", + "name": "fzaninotto/faker", + "version": "dev-master", "source": { "type": "git", - "url": "https://github.com/crysalead/box.git", - "reference": "1c2fbbf73cf29aedf7f1c0dc0d367c7c0f2e5771" + "url": "https://github.com/fzaninotto/Faker.git", + "reference": "6b1d646e38fad6dcd3191d69c40918fd7aeaa2f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/crysalead/box/zipball/1c2fbbf73cf29aedf7f1c0dc0d367c7c0f2e5771", - "reference": "1c2fbbf73cf29aedf7f1c0dc0d367c7c0f2e5771", + "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/6b1d646e38fad6dcd3191d69c40918fd7aeaa2f8", + "reference": "6b1d646e38fad6dcd3191d69c40918fd7aeaa2f8", "shasum": "" }, "require": { - "php": ">=5.4" + "php": "^5.3.3|^7.0" }, "require-dev": { - "crysalead/kahlan": "dev-master" - }, - "type": "library", - "autoload": { - "psr-4": { - "box\\": "src/", - "box\\spec\\": "spec/" - }, - "files": [ - "src/init.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Minimalist Dependency Injection Container.", - "keywords": [ - "dependency injection", - "dependency injection container", - "inversion of control", - "ioc" - ], - "time": "2015-03-21 00:44:48" - }, - { - "name": "crysalead/dir", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/crysalead/dir.git", - "reference": "4d86ccfdb5e2466bca006e76515f51fba4d0b652" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/crysalead/dir/zipball/4d86ccfdb5e2466bca006e76515f51fba4d0b652", - "reference": "4d86ccfdb5e2466bca006e76515f51fba4d0b652", - "shasum": "" - }, - "require": { - "php": ">=5.4" + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~1.5" }, - "require-dev": { - "crysalead/kahlan": "dev-master" + "suggest": { + "ext-intl": "*" }, "type": "library", - "autoload": { - "psr-4": { - "dir\\": "src/", - "dir\\spec\\": "spec/" + "extra": { + "branch-alias": { + "dev-master": "1.5.x-dev" } }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Recursive directory scanner to locate directories and/or files in a file system", - "keywords": [ - "Directory Scanner", - "directory", - "file", - "file system" - ], - "time": "2015-03-19 13:42:41" - }, - { - "name": "crysalead/filter", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/crysalead/filter.git", - "reference": "066c2b5eda011245e5adfc1c9ed6450d972b7cec" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/crysalead/filter/zipball/066c2b5eda011245e5adfc1c9ed6450d972b7cec", - "reference": "066c2b5eda011245e5adfc1c9ed6450d972b7cec", - "shasum": "" - }, - "require": { - "crysalead/jit": "~1.0", - "php": ">=5.4" - }, - "require-dev": { - "crysalead/kahlan": "dev-master" - }, - "type": "library", "autoload": { "psr-4": { - "filter\\": "src/", - "filter\\spec\\": "spec/" + "Faker\\": "src/Faker/" } }, "notification-url": "https://packagist.org/downloads/", @@ -124,81 +49,36 @@ ], "authors": [ { - "name": "CrysaLEAD" + "name": "François Zaninotto" } ], - "description": "Method filtering system", + "description": "Faker is a PHP library that generates fake data for you.", "keywords": [ - "aop", - "aspect", - "aspect programming", - "method filtering" + "data", + "faker", + "fixtures" ], - "time": "2015-03-21 01:36:02" + "time": "2015-03-30 08:56:14" }, { - "name": "crysalead/jit", - "version": "dev-master", + "name": "kahlan/kahlan", + "version": "3.0.2", "source": { "type": "git", - "url": "https://github.com/crysalead/jit.git", - "reference": "deaf0e1234020afc1e238a393dd3787af59dc1ca" + "url": "https://github.com/kahlan/kahlan.git", + "reference": "3a48a2e638e31dca1427dd5ae38640b621e8eb3a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/crysalead/jit/zipball/deaf0e1234020afc1e238a393dd3787af59dc1ca", - "reference": "deaf0e1234020afc1e238a393dd3787af59dc1ca", + "url": "https://api.github.com/repos/kahlan/kahlan/zipball/3a48a2e638e31dca1427dd5ae38640b621e8eb3a", + "reference": "3a48a2e638e31dca1427dd5ae38640b621e8eb3a", "shasum": "" }, "require": { "php": ">=5.4" }, "require-dev": { - "crysalead/kahlan": "dev-master" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "jit\\": "src/", - "jit\\spec\\": "spec/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Just In Time Code Patcher.", - "keywords": [ - "Code Patcher", - "jit" - ], - "time": "2015-03-21 01:31:12" - }, - { - "name": "crysalead/kahlan", - "version": "1.1.3", - "source": { - "type": "git", - "url": "https://github.com/crysalead/kahlan.git", - "reference": "2f44500dcdfe7f73d603caaaf08ee7de93dfeccf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/crysalead/kahlan/zipball/2f44500dcdfe7f73d603caaaf08ee7de93dfeccf", - "reference": "2f44500dcdfe7f73d603caaaf08ee7de93dfeccf", - "shasum": "" - }, - "require": { - "crysalead/box": "~1.0", - "crysalead/dir": "~1.0", - "crysalead/filter": "~1.0", - "crysalead/jit": "~1.0", - "php": ">=5.4" + "squizlabs/php_codesniffer": "^2.7" }, "bin": [ "bin/kahlan" @@ -206,11 +86,11 @@ "type": "library", "autoload": { "psr-4": { - "kahlan\\": "src/", - "kahlan\\spec\\": "spec/" + "Kahlan\\": "src/" }, "files": [ - "src/init.php" + "src/init.php", + "src/functions.php" ] }, "notification-url": "https://packagist.org/downloads/", @@ -222,7 +102,7 @@ "name": "CrysaLEAD" } ], - "description": "Behavior-Driven Development (BDD) library.", + "description": "The PHP Test Framework for Freedom, Truth and Justice.", "keywords": [ "BDD", "Behavior-Driven Development", @@ -233,59 +113,7 @@ "testing", "unit test" ], - "time": "2015-03-21 01:57:35" - }, - { - "name": "fzaninotto/faker", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/fzaninotto/Faker.git", - "reference": "6b1d646e38fad6dcd3191d69c40918fd7aeaa2f8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/6b1d646e38fad6dcd3191d69c40918fd7aeaa2f8", - "reference": "6b1d646e38fad6dcd3191d69c40918fd7aeaa2f8", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~1.5" - }, - "suggest": { - "ext-intl": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.5.x-dev" - } - }, - "autoload": { - "psr-4": { - "Faker\\": "src/Faker/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "François Zaninotto" - } - ], - "description": "Faker is a PHP library that generates fake data for you.", - "keywords": [ - "data", - "faker", - "fixtures" - ], - "time": "2015-03-30 08:56:14" + "time": "2016-10-16 11:23:34" } ], "aliases": [], diff --git a/kahlan-config.php b/kahlan-config.php index fb7eeef..a2aa7dd 100644 --- a/kahlan-config.php +++ b/kahlan-config.php @@ -1,9 +1,9 @@ args(); -$args->argument('coverage', 'default', 3); +$args = $this->commandLine(); +$args->option('coverage', 'default', 3); Filter::register('phalcon.namespace', function($chain) { $this->_autoloader->addPsr4('Spec\\Models\\', __DIR__ . '/spec/models/'); From 769a687570947721e257bb7d864beba2888168bf Mon Sep 17 00:00:00 2001 From: Stefan Chiriac Date: Sat, 19 Nov 2016 23:47:02 +0200 Subject: [PATCH 11/31] fix tests --- spec/suite/DataTableSpec.php | 2 +- spec/suite/ParamsParserSpec.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/suite/DataTableSpec.php b/spec/suite/DataTableSpec.php index 3fac694..ce70311 100644 --- a/spec/suite/DataTableSpec.php +++ b/spec/suite/DataTableSpec.php @@ -6,7 +6,7 @@ describe("DataTable", function() { - before(function() { + \Kahlan\beforeEach(function() { $this->di = \Phalcon\DI::getDefault(); diff --git a/spec/suite/ParamsParserSpec.php b/spec/suite/ParamsParserSpec.php index a25a3f9..8a719f8 100644 --- a/spec/suite/ParamsParserSpec.php +++ b/spec/suite/ParamsParserSpec.php @@ -5,7 +5,7 @@ describe("Malformed", function() { - before(function() { + \Kahlan\beforeEach(function() { $this->parser = new \DataTables\ParamsParser(10); }); From a581156246aabfdf13090ca7817cab4f52a47440 Mon Sep 17 00:00:00 2001 From: Stefan Chiriac Date: Sat, 19 Nov 2016 23:48:24 +0200 Subject: [PATCH 12/31] travis git clone phalcon 3.0.1 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5cd561d..f22bc9d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ matrix: fast_finish: true before_script: - - git clone -b 1.3.4 git://github.com/phalcon/cphalcon.git --depth=1 && cd cphalcon/build/safe && phpize && ./configure --enable-phalcon && sudo make install + - git clone -b 3.0.1 git://github.com/phalcon/cphalcon.git --depth=1 && cd cphalcon/build/safe && phpize && ./configure --enable-phalcon && sudo make install - cd ../../../ && phpenv config-add ./spec/phalcon.ini - composer self-update - composer install --dev --prefer-source From b1c188739f9ebe66ad82191c95828a3857823d19 Mon Sep 17 00:00:00 2001 From: Stefan Chiriac Date: Sun, 20 Nov 2016 00:01:20 +0200 Subject: [PATCH 13/31] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f22bc9d..1a554e8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ matrix: fast_finish: true before_script: - - git clone -b 3.0.1 git://github.com/phalcon/cphalcon.git --depth=1 && cd cphalcon/build/safe && phpize && ./configure --enable-phalcon && sudo make install + - git clone -b 3.0.x git://github.com/phalcon/cphalcon.git --depth=1 && cd cphalcon/build/safe && phpize && ./configure --enable-phalcon && sudo make install - cd ../../../ && phpenv config-add ./spec/phalcon.ini - composer self-update - composer install --dev --prefer-source From 898c1cd03a98508cd030125e0ea6196dcbc3ec1f Mon Sep 17 00:00:00 2001 From: Stefan Chiriac Date: Sun, 20 Nov 2016 00:18:39 +0200 Subject: [PATCH 14/31] revert travis gitclone to 1.3.4 --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1a554e8..c999a2c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,13 +4,12 @@ php: - 5.5 - 5.4 - 5.6 - - 7.0 matrix: fast_finish: true before_script: - - git clone -b 3.0.x git://github.com/phalcon/cphalcon.git --depth=1 && cd cphalcon/build/safe && phpize && ./configure --enable-phalcon && sudo make install + - git clone -b 1.3.4 git://github.com/phalcon/cphalcon.git --depth=1 && cd cphalcon/build/safe && phpize && ./configure --enable-phalcon && sudo make install - cd ../../../ && phpenv config-add ./spec/phalcon.ini - composer self-update - composer install --dev --prefer-source From 4fbba6a04b376c409ee75f55061b5e041523e874 Mon Sep 17 00:00:00 2001 From: Stefan Chiriac Date: Sun, 20 Nov 2016 00:42:59 +0200 Subject: [PATCH 15/31] fix stub --- spec/suite/DataTableSpec.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/suite/DataTableSpec.php b/spec/suite/DataTableSpec.php index ce70311..26930f0 100644 --- a/spec/suite/DataTableSpec.php +++ b/spec/suite/DataTableSpec.php @@ -142,7 +142,7 @@ it("should disable view & send response", function() { $this->di->set('view', function() { - $view = Stub::create(); + $view = new \Phalcon\Mvc\View(); return $view; }); From 629ce1f0fc2b0ca0f5b018fdf3c1e30ec317fc14 Mon Sep 17 00:00:00 2001 From: Assad Nazar Date: Fri, 29 Nov 2019 19:55:45 +0500 Subject: [PATCH 16/31] updated code for use in phalcon version 4 --- composer.json | 13 +++++-------- src/Adapters/QueryBuilder.php | 4 ++-- src/DataTable.php | 2 +- src/ParamsParser.php | 4 +--- 4 files changed, 9 insertions(+), 14 deletions(-) diff --git a/composer.json b/composer.json index f07b8cf..4a3dd2e 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "magnxpyr/phalcon-datatables", - "description": "DataTables adapter for Phalcon Framework", + "description": "DataTables adapter for Phalcon Framework Version 4", "keywords": ["Phalcon", "DataTables", "Plugin"], "license": "MIT", "autoload": { @@ -10,17 +10,14 @@ }, "authors": [ { - "name": "Pavel Makarenko", - "email": "cryfall@gmail.com" - }, - { - "name": "Stefan Chiriac", - "email": "stefan@magnxpyr.com" + "name": "Assad Nazar", + "email": "assad.nazar@gmail.com" } ], "minimum-stability": "dev", "require": { - + "php": ">=7.2", + "ext-phalcon": "~4.0.0-rc.1" }, "extra": { "branch-alias": { diff --git a/src/Adapters/QueryBuilder.php b/src/Adapters/QueryBuilder.php index 7a64fa3..096b426 100644 --- a/src/Adapters/QueryBuilder.php +++ b/src/Adapters/QueryBuilder.php @@ -19,7 +19,7 @@ public function getResponse() { 'page' => 1, ]); - $total = $builder->getPaginate(); + $total = $builder->paginate(); $this->global_search = []; $this->column_search = []; @@ -54,7 +54,7 @@ public function getResponse() { 'page' => $this->parser->getPage(), ]); - $filtered = $builder->getPaginate(); + $filtered = $builder->paginate(); return $this->formResponse([ 'total' => $total->total_items, diff --git a/src/DataTable.php b/src/DataTable.php index 42ae149..0f272fa 100644 --- a/src/DataTable.php +++ b/src/DataTable.php @@ -7,7 +7,7 @@ use DataTables\Adapters\ArrayAdapter; use Phalcon\Http\Response; -class DataTable extends \Phalcon\Mvc\User\Plugin +class DataTable extends \Phalcon\Di\Injectable { protected $options; diff --git a/src/ParamsParser.php b/src/ParamsParser.php index c3d4bee..49aa665 100644 --- a/src/ParamsParser.php +++ b/src/ParamsParser.php @@ -2,9 +2,7 @@ namespace DataTables; -use Phalcon\Mvc\User\Component; - -class ParamsParser extends Component +class ParamsParser extends \Phalcon\Di\Injectable { protected $params = []; From 7cfd894dc7dd97c81ef2906367dc7cf08261a8e9 Mon Sep 17 00:00:00 2001 From: Assad Nazar Date: Fri, 29 Nov 2019 20:04:20 +0500 Subject: [PATCH 17/31] edited composer namespace --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 4a3dd2e..295340f 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "magnxpyr/phalcon-datatables", + "name": "assadnazar/phalcon4-datatables", "description": "DataTables adapter for Phalcon Framework Version 4", "keywords": ["Phalcon", "DataTables", "Plugin"], "license": "MIT", From 9b874b0989f1bddb0f2ee17dd992fe085e80e3c8 Mon Sep 17 00:00:00 2001 From: Assad Nazar Date: Fri, 29 Nov 2019 20:04:20 +0500 Subject: [PATCH 18/31] edited composer namespace --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 4a3dd2e..295340f 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "magnxpyr/phalcon-datatables", + "name": "assadnazar/phalcon4-datatables", "description": "DataTables adapter for Phalcon Framework Version 4", "keywords": ["Phalcon", "DataTables", "Plugin"], "license": "MIT", From df01718fcd57e2a7d08bf3d0c216e5fcf594e943 Mon Sep 17 00:00:00 2001 From: Assad Nazar Date: Fri, 29 Nov 2019 20:40:17 +0500 Subject: [PATCH 19/31] dev alias changed to 1.1-dev --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 295340f..061c626 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ }, "extra": { "branch-alias": { - "dev_master": "1.0-dev" + "dev_master": "1.1-dev" } }, "require-dev": { From e48623e3838ade705fb9e6757d2270c846bf0c32 Mon Sep 17 00:00:00 2001 From: Assad Nazar Date: Fri, 29 Nov 2019 20:42:14 +0500 Subject: [PATCH 20/31] reverted version alias --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 061c626..295340f 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ }, "extra": { "branch-alias": { - "dev_master": "1.1-dev" + "dev_master": "1.0-dev" } }, "require-dev": { From dc7ec04a6801c9befd4a5c40ffb91fbb85737e8d Mon Sep 17 00:00:00 2001 From: Assad Nazar Date: Fri, 29 Nov 2019 20:56:28 +0500 Subject: [PATCH 21/31] branch alias --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 295340f..79fa5e9 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ }, "extra": { "branch-alias": { - "dev_master": "1.0-dev" + "dev_master": "1.2.1-dev" } }, "require-dev": { From d74da62df0049f200edc40d6a70111c224a6c990 Mon Sep 17 00:00:00 2001 From: assadnazar Date: Fri, 29 Nov 2019 21:04:04 +0500 Subject: [PATCH 22/31] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 30f62ce..cf2b38c 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ This version contains community fixes and multi model search support ```json { "require": { - "magnxpyr/phalcon-datatables": "1.*" + "assadnazar/phalcon4-datatables": "dev-master" } } ``` From 136bda498689a77a7bfa1fedbc862579e412ff1d Mon Sep 17 00:00:00 2001 From: assadnazar Date: Sun, 8 Mar 2020 03:26:47 +0500 Subject: [PATCH 23/31] Added Excel and PDF Export Options --- src/DataTable.php | 69 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/src/DataTable.php b/src/DataTable.php index 0f272fa..b692ab2 100644 --- a/src/DataTable.php +++ b/src/DataTable.php @@ -40,6 +40,75 @@ public function getResponse() { return !empty($this->response) ? $this->response : []; } + + public function exportResponse($type = 'Excel') + { + $data = $this->getResponse()['data']; + + $headings = array(); + + foreach($data[0] as $key => $value) + { + $headings[$key] = $key; + } + + array_unshift($data, $headings); + + unset($headings); + + $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet(); + $spreadsheet->getActiveSheet()->fromArray($data, Null, 'A1'); + + $totalCols = $spreadsheet->getActiveSheet()->getHighestColumn(); + $spreadsheet->getActiveSheet()->removeColumn($totalCols); + + // Removed last column so re-calculate columns + $totalCols = $spreadsheet->getActiveSheet()->getHighestColumn(); + $totalRows = $spreadsheet->getActiveSheet()->getHighestRow(); + + // Default Styles + $styleHeader = [ + 'font' => [ + 'bold' => true, + ], + 'alignment' => [ + 'horizontal' => \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER, + 'vertical' => \PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_CENTER, + ], + ]; + $styleCells = [ + + 'borders' => [ + 'allBorders' => [ + 'borderStyle' => \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN, + ], + ] + ]; + $spreadsheet->getActiveSheet()->getStyle("A1:{$totalCols}1")->applyFromArray($styleHeader); + $spreadsheet->getActiveSheet()->getStyle("A1:{$totalCols}{$totalRows}")->applyFromArray($styleCells); + + $fileName = "Export_" . date('m-d-Y'); + + switch ($type) { + case 'PDF': + header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + header('Content-Disposition: attachment;filename="'. $fileName .'.pdf"'); + header('Cache-Control: max-age=0'); + + $writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Mpdf'); + $writer->save('php://output'); + break; + case 'Excel': + default: + header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + header('Content-Disposition: attachment;filename="'. $fileName .'.xlsx"'); + header('Cache-Control: max-age=0'); + + $writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Xlsx'); + $writer->save('php://output'); + break; + } + } public function sendResponse() { From e5fded9913ce522c0165e6b0eeb83b5f11b82e75 Mon Sep 17 00:00:00 2001 From: assadnazar Date: Sun, 8 Mar 2020 03:28:36 +0500 Subject: [PATCH 24/31] require phpspreadsheet and mpdf --- composer.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 79fa5e9..58979e9 100644 --- a/composer.json +++ b/composer.json @@ -16,8 +16,10 @@ ], "minimum-stability": "dev", "require": { - "php": ">=7.2", - "ext-phalcon": "~4.0.0-rc.1" + "php": ">=7.2", + "ext-phalcon": "~4.0.0-rc.1", + "phpoffice/phpspreadsheet": "1.11.0", + "mpdf/mpdf": "8.0.5" }, "extra": { "branch-alias": { From 33f5d12b8c0d913d6181a1d76264f0209bc5856d Mon Sep 17 00:00:00 2001 From: assadnazar Date: Sun, 8 Mar 2020 03:38:30 +0500 Subject: [PATCH 25/31] Export Feature Description Added --- README.md | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cf2b38c..2b07fab 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ This version contains community fixes and multi model search support * Multiple column ordering * Column-based search * Multi model search +* Export to Excel and PDF # Installation ### Installation via Composer @@ -27,7 +28,9 @@ This version contains community fixes and multi model search support ```json { "require": { - "assadnazar/phalcon4-datatables": "dev-master" + "assadnazar/phalcon4-datatables": "dev-master", + "phpoffice/phpspreadsheet": "1.11.0", + "mpdf/mpdf": "8.0.5" } } ``` @@ -119,6 +122,35 @@ class TestController extends \Phalcon\Mvc\Controller { } ``` +### Export Feature (using QueryBuilder): +```jquery +$(".exportBtn").on("click", function(e){ + e.preventDefault(); + // Get ajax params + let params = $.param($('#tableID').DataTable().ajax.params()); + let _href = $(this).attr('href'); + // append params to url + export type (Excel/PDF) + $(this).attr('href', _href + params + '/' + $(this).text()); + window.location.href = $(this).attr('href'); +}) +``` + +```php +modelsManager->createBuilder() + ->columns('id, name, email, balance') + ->from('Example\Models\User'); + + $dataTables = new DataTable(); + $dataTables->fromBuilder($builder)->exportResponse($type); + } +} +``` + ### Model: ```php Date: Sun, 8 Mar 2020 03:39:52 +0500 Subject: [PATCH 26/31] Updated Export Features --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2b07fab..a3a8ccb 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,7 @@ $(".exportBtn").on("click", function(e){ // append params to url + export type (Excel/PDF) $(this).attr('href', _href + params + '/' + $(this).text()); window.location.href = $(this).attr('href'); -}) +}); ``` ```php @@ -140,7 +140,7 @@ $(".exportBtn").on("click", function(e){ use \DataTables\DataTable; class TestController extends \Phalcon\Mvc\Controller { - public function exportAction($type) { + public function exportAction($filters, $type) { $builder = $this->modelsManager->createBuilder() ->columns('id, name, email, balance') ->from('Example\Models\User'); From 56eb70e14af680c40fc7ac0601d19626af5e0d41 Mon Sep 17 00:00:00 2001 From: assadnazar Date: Fri, 27 Mar 2020 02:45:52 +0500 Subject: [PATCH 27/31] Update DataTable.php Conditional Removal of last column if it exists --- src/DataTable.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/DataTable.php b/src/DataTable.php index b692ab2..25e9fe2 100644 --- a/src/DataTable.php +++ b/src/DataTable.php @@ -59,12 +59,15 @@ public function exportResponse($type = 'Excel') $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet(); $spreadsheet->getActiveSheet()->fromArray($data, Null, 'A1'); - $totalCols = $spreadsheet->getActiveSheet()->getHighestColumn(); - $spreadsheet->getActiveSheet()->removeColumn($totalCols); - - // Removed last column so re-calculate columns $totalCols = $spreadsheet->getActiveSheet()->getHighestColumn(); $totalRows = $spreadsheet->getActiveSheet()->getHighestRow(); + + // In Case we have 'DT_RowId' in our data, then remove last column and re-calculate columns + if(array_key_exists('DT_RowId', $data) + { + $spreadsheet->getActiveSheet()->removeColumn($totalCols); + $totalCols = $spreadsheet->getActiveSheet()->getHighestColumn(); + } // Default Styles $styleHeader = [ From 37cea853ee2e7c045708d493f1a72d71109626b6 Mon Sep 17 00:00:00 2001 From: assadnazar Date: Fri, 27 Mar 2020 03:10:09 +0500 Subject: [PATCH 28/31] Update DataTable.php --- src/DataTable.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DataTable.php b/src/DataTable.php index 25e9fe2..f9a51f2 100644 --- a/src/DataTable.php +++ b/src/DataTable.php @@ -63,7 +63,7 @@ public function exportResponse($type = 'Excel') $totalRows = $spreadsheet->getActiveSheet()->getHighestRow(); // In Case we have 'DT_RowId' in our data, then remove last column and re-calculate columns - if(array_key_exists('DT_RowId', $data) + if(array_key_exists('DT_RowId', $data)) { $spreadsheet->getActiveSheet()->removeColumn($totalCols); $totalCols = $spreadsheet->getActiveSheet()->getHighestColumn(); From abecda1849c61388385938f114e7743990d657d6 Mon Sep 17 00:00:00 2001 From: assadnazar Date: Fri, 27 Mar 2020 03:14:54 +0500 Subject: [PATCH 29/31] Bracket missed bracket --- src/DataTable.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DataTable.php b/src/DataTable.php index f9a51f2..25e9fe2 100644 --- a/src/DataTable.php +++ b/src/DataTable.php @@ -63,7 +63,7 @@ public function exportResponse($type = 'Excel') $totalRows = $spreadsheet->getActiveSheet()->getHighestRow(); // In Case we have 'DT_RowId' in our data, then remove last column and re-calculate columns - if(array_key_exists('DT_RowId', $data)) + if(array_key_exists('DT_RowId', $data) { $spreadsheet->getActiveSheet()->removeColumn($totalCols); $totalCols = $spreadsheet->getActiveSheet()->getHighestColumn(); From 2de5d5299ec4e4d2017210f6ddd9dd307aeacc9e Mon Sep 17 00:00:00 2001 From: assadnazar Date: Fri, 27 Mar 2020 03:15:36 +0500 Subject: [PATCH 30/31] Update DataTable.php Missed bracket --- src/DataTable.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DataTable.php b/src/DataTable.php index 25e9fe2..f9a51f2 100644 --- a/src/DataTable.php +++ b/src/DataTable.php @@ -63,7 +63,7 @@ public function exportResponse($type = 'Excel') $totalRows = $spreadsheet->getActiveSheet()->getHighestRow(); // In Case we have 'DT_RowId' in our data, then remove last column and re-calculate columns - if(array_key_exists('DT_RowId', $data) + if(array_key_exists('DT_RowId', $data)) { $spreadsheet->getActiveSheet()->removeColumn($totalCols); $totalCols = $spreadsheet->getActiveSheet()->getHighestColumn(); From 1a06653ea522614ad518eb9532d9262bcb740590 Mon Sep 17 00:00:00 2001 From: assadnazar Date: Fri, 27 Mar 2020 03:32:16 +0500 Subject: [PATCH 31/31] Update DataTable.php --- src/DataTable.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DataTable.php b/src/DataTable.php index f9a51f2..7761f8c 100644 --- a/src/DataTable.php +++ b/src/DataTable.php @@ -63,7 +63,7 @@ public function exportResponse($type = 'Excel') $totalRows = $spreadsheet->getActiveSheet()->getHighestRow(); // In Case we have 'DT_RowId' in our data, then remove last column and re-calculate columns - if(array_key_exists('DT_RowId', $data)) + if(array_key_exists('DT_RowId', $data[0])) { $spreadsheet->getActiveSheet()->removeColumn($totalCols); $totalCols = $spreadsheet->getActiveSheet()->getHighestColumn();