diff --git a/.travis.yml b/.travis.yml index e97912f..c999a2c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,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 f7525ef..a3a8ccb 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 @@ -16,6 +17,8 @@ This is a [Phalcon Framework](http://phalconphp.com/) adapter for [DataTables](h * Ordering * Multiple column ordering * Column-based search +* Multi model search +* Export to Excel and PDF # Installation ### Installation via Composer @@ -25,7 +28,9 @@ This is a [Phalcon Framework](http://phalconphp.com/) adapter for [DataTables](h ```json { "require": { - "m1ome/phalcon-datatables": "1.*" + "assadnazar/phalcon4-datatables": "dev-master", + "phpoffice/phpspreadsheet": "1.11.0", + "mpdf/mpdf": "8.0.5" } } ``` @@ -91,6 +96,61 @@ 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(); + } + } +} +``` + +### 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 =7.2", + "ext-phalcon": "~4.0.0-rc.1", + "phpoffice/phpspreadsheet": "1.11.0", + "mpdf/mpdf": "8.0.5" }, "extra": { "branch-alias": { - "dev_master": "1.0-dev" + "dev_master": "1.2.1-dev" } }, "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/'); diff --git a/spec/suite/Adapters/AdapterInterfaceSpec.php b/spec/suite/Adapters/AdapterInterfaceSpec.php index 357fa56..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,10 +63,17 @@ public function getResponse() { it("->columnExists()", function() { $adapter = new MyAdapter(10); - $columns = ['user', 'name', 'email']; + $columns = ['user', 'name', 'email', 'r.role as role', ['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('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/spec/suite/DataTableSpec.php b/spec/suite/DataTableSpec.php index 3fac694..26930f0 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(); @@ -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; }); 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); }); diff --git a/src/Adapters/AdapterInterface.php b/src/Adapters/AdapterInterface.php index 290f309..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; @@ -21,6 +21,33 @@ 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 { + $columns[$i]['alias'] = $column['alias']; + } + } 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 +55,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 +123,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 +156,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..64160b5 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 = (stripos($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 = (stripos($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..096b426 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; @@ -16,29 +19,42 @@ public function getResponse() { 'page' => 1, ]); - $total = $builder->getPaginate(); + $total = $builder->paginate(); + $this->global_search = []; + $this->column_search = []; - $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 = "keyg_" . str_replace(".", "", $column); + $this->global_search[] = "{$column} LIKE :{$key}:"; + $this->_bind[$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 = "keyc_" . str_replace(" ", "", str_replace(".", "", $column)); + $this->column_search[] = "{$column} LIKE :{$key}:"; + $this->_bind[$key] = "%{$search}%"; }); - $this->bind('order', function($order) { + $this->bind('order', false, function($order) { if (!empty($order)) { $this->builder->orderBy(implode(', ', $order)); } }); + 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(), ]); - $filtered = $builder->getPaginate(); + $filtered = $builder->paginate(); return $this->formResponse([ 'total' => $total->total_items, diff --git a/src/Adapters/ResultSet.php b/src/Adapters/ResultSet.php index 5a50e7e..ff90595 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 = (stripos($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 = (stripos($item->$col, $search) !== false); + } else { + $check = false; + } if (!$check) break 2; } } diff --git a/src/DataTable.php b/src/DataTable.php index ad4bdd2..7761f8c 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 : []; + } + + 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(); + $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[0])) + { + $spreadsheet->getActiveSheet()->removeColumn($totalCols); + $totalCols = $spreadsheet->getActiveSheet()->getHighestColumn(); + } + + // 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; + } } - $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..49aa665 100644 --- a/src/ParamsParser.php +++ b/src/ParamsParser.php @@ -1,82 +1,97 @@ 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 \Phalcon\Di\Injectable +{ + + 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'] : ''; + } + }