├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── composer.json ├── config ├── app.php └── paths.php ├── docs ├── CONTRIBUTING.md └── README.md ├── phpunit.xml.dist ├── src ├── Controller │ └── AppController.php ├── Plugin.php ├── StorageEngine │ ├── CacheStorageEngine.php │ └── StorageEngineInterface.php ├── Table │ ├── Column.php │ ├── Columns.php │ ├── ConfigBundle.php │ ├── Option │ │ ├── CallBack │ │ │ └── MainCallBack.php │ │ ├── ChildOptionAbstract.php │ │ ├── MainOption.php │ │ ├── OptionAbstract.php │ │ └── Section │ │ │ ├── ColumnsOption.php │ │ │ ├── FeaturesOption.php │ │ │ └── OptionsOption.php │ ├── QueryBaseState.php │ └── Tables.php ├── Tools │ ├── Builder.php │ ├── Functions.php │ ├── Js.php │ └── Validator.php └── View │ ├── Cell │ └── DataTablesCell.php │ └── Helper │ └── DataTablesHelper.php ├── templates ├── cell │ └── DataTables │ │ └── table.php └── twig │ └── js │ ├── bake │ └── callback_created_cell.twig │ └── functions │ └── callback_created_cell.twig ├── tests ├── Fixture │ ├── ArticlesFixture.php │ └── UsersFixture.php ├── TestCase │ ├── PluginTest.php │ ├── StorageEngine │ │ └── CacheStorageEngineTest.php │ ├── Table │ │ ├── ColumnTest.php │ │ ├── ColumnsTest.php │ │ ├── Option │ │ │ ├── Callback │ │ │ │ └── MainCallBackTest.php │ │ │ ├── MainOptionTest.php │ │ │ └── Section │ │ │ │ ├── FeaturesOptionTest.php │ │ │ │ └── OptionsOptionTest.php │ │ └── TablesTest.php │ ├── Tools │ │ ├── FunctionsTest.php │ │ ├── JsTest.php │ │ └── ValidatorTest.php │ └── View │ │ ├── Cell │ │ └── DataTablesCellTest.php │ │ └── Helper │ │ └── DataTablesHelperTest.php ├── bootstrap.php ├── phpstan.neon └── test_app │ ├── src │ ├── Application.php │ ├── Controller │ │ └── AppController.php │ ├── DataTables │ │ └── Tables │ │ │ └── CategoriesTables.php │ └── View │ │ └── AppView.php │ └── templates │ └── data_tables │ └── Categories │ └── main │ └── callback_created_cell.twig └── webroot └── .gitkeep /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | /phpunit.xml 3 | /phpunit.phar 4 | /vendor 5 | /tmp/ 6 | composer.phar 7 | .idea/ 8 | config/Migrations/schema-dump-default.lock 9 | /.phpunit.result.cache 10 | schema-dump-default.lock 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 7.2 5 | - 7.4 6 | 7 | env: 8 | matrix: 9 | - DB=mysql db_dsn='mysql://root@127.0.0.1/cakephp_test' 10 | global: 11 | - DEFAULT=1 12 | 13 | services: 14 | - postgresql 15 | - mysql 16 | 17 | matrix: 18 | fast_finish: true 19 | 20 | include: 21 | - php: 7.2 22 | env: DB=pgsql db_dsn='postgres://postgres@127.0.0.1/cakephp_test' 23 | 24 | - php: 7.3 25 | env: DB=sqlite db_dsn='sqlite:///:memory:' 26 | 27 | - php: 7.2 28 | env: PREFER_LOWEST=1 29 | 30 | - php: 7.3 31 | env: CODECOVERAGE=1 DEFAULT=0 DB=mysql db_dsn='mysql://root@127.0.0.1/cakephp_test' 32 | 33 | - php: 7.3 34 | env: CHECKS=1 DEFAULT=1 35 | 36 | before_install: 37 | - phpenv config-rm xdebug.ini 38 | 39 | before_script: 40 | - if [[ $CHECKS != 1 ]]; then composer require --dev phpunit/phpunit:"^8.3"; fi 41 | 42 | - if [[ $PREFER_LOWEST != 1 ]]; then composer install --prefer-source --no-interaction; fi 43 | - if [[ $PREFER_LOWEST == 1 ]]; then composer update --prefer-lowest --prefer-stable --prefer-dist --no-interaction; fi 44 | - if [[ $PREFER_LOWEST == 1 ]]; then composer require --dev dereuromark/composer-prefer-lowest:dev-master; fi 45 | 46 | - if [[ $DB == 'mysql' ]]; then mysql -u root -e 'CREATE DATABASE cakephp_test;'; fi 47 | - if [[ $DB == 'pgsql' ]]; then psql -c 'CREATE DATABASE cakephp_test;' -U postgres; fi 48 | 49 | script: 50 | - if [[ $DEFAULT == 1 ]]; then vendor/bin/phpunit; fi 51 | - if [[ $PREFER_LOWEST == 1 ]]; then vendor/bin/validate-prefer-lowest; fi 52 | 53 | - if [[ $CHECKS == 1 ]]; then composer phpstan-setup && composer phpstan; fi 54 | - if [[ $CHECKS == 1 ]]; then composer cs-check; fi 55 | 56 | - if [[ $CODECOVERAGE == 1 ]]; then phpdbg -qrr vendor/bin/phpunit --coverage-clover=clover.xml; fi 57 | 58 | after_success: 59 | - if [[ $CODECOVERAGE == 1 ]]; then bash <(curl -s https://codecov.io/bash); fi 60 | 61 | cache: 62 | directories: 63 | - $HOME/.composer/cache 64 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at allan.m.carvalho@outlook.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Allan Mariucci Carvalho 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CakePHP DataTables Plugin 2 | [![Build Status](https://api.travis-ci.com/allanmcarvalho/cakephp-datatables.svg?branch=master)](https://travis-ci.com/github/allanmcarvalho/cakephp-datatables) 3 | [![Coverage Status](https://img.shields.io/codecov/c/github/allanmcarvalho/cakephp-datatables/master)](https://codecov.io/github/allanmcarvalho/cakephp-datatables?branch=master) 4 | [![Maintainability](https://img.shields.io/codeclimate/maintainability/allanmcarvalho/cakephp-datatables)](https://codeclimate.com/github/allanmcarvalho/cakephp-datatables/maintainability) 5 | [![Latest Stable Version](https://img.shields.io/packagist/v/allanmcarvalho/cakephp-datatables?color=%23f9a400)](https://packagist.org/packages/allanmcarvalho/cakephp-datatables) 6 | [![CodeSize](https://img.shields.io/github/languages/code-size/allanmcarvalho/cakephp-datatables)](https://img.shields.io/github/languages/code-size/allanmcarvalho/cakephp-datatables) 7 | [![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.2-8892BF.svg)](https://php.net/) 8 | [![License](https://img.shields.io/packagist/l/allanmcarvalho/cakephp-datatables?color=blue)](https://packagist.org/packages/allanmcarvalho/cakephp-datatables) 9 | [![Total Downloads](https://poser.pugx.org/allanmcarvalho/cakephp-datatables/d/total)](https://packagist.org/packages/allanmcarvalho/cakephp-datatables) 10 | [![Coding Standards](https://img.shields.io/badge/cs-PSR--2--R-green.svg)](https://github.com/php-fig-rectified/fig-rectified-standards) 11 | [![PHPStan](https://img.shields.io/badge/PHPStan-level%208-%23149c83)](https://github.com/phpstan/phpstan) 12 | 13 | This branch is for use with **CakePHP 4.0+**. For details see [version map](https://github.com/allanmcarvalho/cakephp-datatables/wiki#cakephp-version-map). 14 | 15 | 16 | This is a very simple and minimalistic (Automagic) implementation of 17 | [DataTables jQuery Plugin](https://datatables.net/) for CakePHP. 18 | If you need to create simple and basics DataTables tables without extremely hard configurations, this will be a very wise choice. 19 | 20 | Overall functionality is inspired and improved by one plugin from CakepPHP 2.x that isn't longer available. 21 | 22 | The plugin is an attempt to provide a basic, simple and friendly way to implement the 23 | DataTables library in order to be connected with CakePHP features and requirements. 24 | 25 | ## Installation and Usage 26 | See [Documentation](docs). 27 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "allanmcarvalho/cakephp-datatables", 3 | "type": "cakephp-plugin", 4 | "description": "DataTables plugin for CakePHP 4.x", 5 | "homepage": "https://github.com/allanmcarvalho/cakephp-datatables", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Allan Carvalho", 10 | "homepage": "https://wsssoftware.com.br", 11 | "role": "Maintainer" 12 | }, 13 | { 14 | "name": "Contributors", 15 | "homepage": "https://github.com/allanmcarvalho/cakephp-datatables/graphs/contributors", 16 | "role": "Contributor" 17 | } 18 | ], 19 | "require": { 20 | "php": ">=7.2", 21 | "cakephp/cakephp": "^4.0", 22 | "twig/twig": "^3.0", 23 | "matthiasmullie/minify": "^1.3.50", 24 | "ext-json": "*" 25 | }, 26 | "require-dev": { 27 | "phpunit/phpunit": "^8.0", 28 | "fig-r/psr2r-sniffer": "dev-master" 29 | }, 30 | "support": { 31 | "source": "https://github.com/allanmcarvalho/cakephp-datatables" 32 | }, 33 | "autoload": { 34 | "psr-4": { 35 | "DataTables\\": "src/" 36 | } 37 | }, 38 | "autoload-dev": { 39 | "psr-4": { 40 | "DataTables\\Test\\": "tests/", 41 | "Cake\\Test\\": "vendor/cakephp/cakephp/tests/", 42 | "TestApp\\": "tests/test_app/src/" 43 | } 44 | }, 45 | "prefer-stable": true, 46 | "minimum-stability": "dev", 47 | "scripts": { 48 | "phpstan": "phpstan analyse -c tests/phpstan.neon -l 5 src/", 49 | "phpstan-setup": "cp composer.json composer.backup && composer require --dev phpstan/phpstan:^0.12 && mv composer.backup composer.json", 50 | "test": "php phpunit.phar", 51 | "test-setup": "[ ! -f phpunit.phar ] && wget https://phar.phpunit.de/phpunit-8.5.2.phar && mv phpunit-8.5.2.phar phpunit.phar || true", 52 | "cs-check": "phpcs -p -s --standard=vendor/fig-r/psr2r-sniffer/PSR2R/ruleset.xml --ignore=/config/Migrations/ --extensions=php src/ tests/ config/", 53 | "cs-fix": "phpcbf -p --standard=vendor/fig-r/psr2r-sniffer/PSR2R/ruleset.xml --ignore=/config/Migrations/ --extensions=php src/ tests/ config/" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /config/app.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | 11 | declare(strict_types = 1); 12 | 13 | return [ 14 | 'DataTables' => [ 15 | 'StorageEngine' => [ 16 | 'class' => \DataTables\StorageEngine\CacheStorageEngine::class, 17 | 'disableWhenDebugOn' => true, 18 | ], 19 | 'resources' => [ 20 | 'templates' => ROOT . DS . 'templates' . DS . 'data_tables' . DS, 21 | 'twigCacheFolder' => CACHE . DS . 'data_tables' . DS . 'twig' . DS, 22 | ], 23 | 'Cache' => [ 24 | '_data_tables_config_bundles_' => [ 25 | 'className' => \Cake\Cache\Engine\FileEngine::class, 26 | 'prefix' => 'built_config_', 27 | 'path' => CACHE . DS . 'data_tables' . DS . 'config_bundles' . DS, 28 | 'serialize' => true, 29 | 'duration' => '+30 days', 30 | 'url' => env('CACHE_CAKECORE_URL', null), 31 | ], 32 | ], 33 | ], 34 | ]; 35 | -------------------------------------------------------------------------------- /config/paths.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | declare(strict_types = 1); 11 | 12 | /* 13 | * Use the DS to separate the directories in other defines 14 | */ 15 | if (!defined('DS')) { 16 | define('DS', DIRECTORY_SEPARATOR); 17 | } 18 | 19 | /* 20 | * These defines should only be edited if you have cake installed in 21 | * a directory layout other than the way it is distributed. 22 | * When using custom settings be sure to use the DS and do not add a trailing DS. 23 | */ 24 | 25 | /* 26 | * The full path to the directory which holds "src", WITHOUT a trailing DS. 27 | */ 28 | define('DATA_TABLES_ROOT', dirname(__DIR__)); 29 | 30 | /* 31 | * The actual directory name for the application directory. Normally 32 | * named 'src'. 33 | */ 34 | define('DATA_TABLES_APP_DIR', 'src'); 35 | 36 | /* 37 | * Path to the application's directory. 38 | */ 39 | define('DATA_TABLES_APP', DATA_TABLES_ROOT . DS . DATA_TABLES_APP_DIR . DS); 40 | 41 | /* 42 | * Path to the config directory. 43 | */ 44 | define('DATA_TABLES_CONFIG', DATA_TABLES_ROOT . DS . 'config' . DS); 45 | 46 | /* 47 | * File path to the templates directory. 48 | * 49 | * To derive your templates from your webserver change this to: 50 | * 51 | * `define('WWW_ROOT', rtrim($_SERVER['DOCUMENT_ROOT'], DS) . DS);` 52 | */ 53 | define('DATA_TABLES_TEMPLATES', DATA_TABLES_ROOT . DS . 'templates' . DS); 54 | 55 | /* 56 | * File path to the webroot directory. 57 | * 58 | * To derive your webroot from your webserver change this to: 59 | * 60 | * `define('WWW_ROOT', rtrim($_SERVER['DOCUMENT_ROOT'], DS) . DS);` 61 | */ 62 | define('DATA_TABLES_WWW_ROOT', DATA_TABLES_ROOT . DS . 'webroot' . DS); 63 | 64 | /* 65 | * Path to the tests directory. 66 | */ 67 | define('DATA_TABLES_TESTS', DATA_TABLES_ROOT . DS . 'tests' . DS); 68 | -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | I am looking forward to your contributions. 4 | 5 | There are a few guidelines that I need contributors to follow: 6 | * Coding standards (`composer cs-check` to check and `composer cs-fix` to fix) 7 | * Passing tests (`php phpunit.phar`) 8 | 9 | 10 | 11 | ## Updating Locale POT file 12 | 13 | Run this from your app dir to update the plugin's `datatables.pot` file: 14 | ``` 15 | bin/cake i18n extract --plugin DataTables --extract-core=no --merge=no --overwrite 16 | ``` 17 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # CakePHP DataTables Plugin Documentation 2 | 3 | 4 | ## Installation 5 | ``` 6 | composer require allanmcarvalho/cakephp-datatables 7 | ``` 8 | Load the plugin in your `src/Application.php`'s bootstrap() using: 9 | ```php 10 | $this->addPlugin('DataTables'); 11 | //OR 12 | $this->addPlugin(\DataTables\Plugin::class); 13 | ``` 14 | 15 | It is also advised to have the `posix` PHP extension enabled. 16 | 17 | 18 | ## Configuration 19 | 20 | ### Under Construction! 21 | 22 | ## Contributing 23 | 24 | See [CONTRIBUTING.md](CONTRIBUTING.md). 25 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | tests/TestCase/ 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | src/ 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/Controller/AppController.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | declare(strict_types = 1); 11 | 12 | namespace DataTables\Controller; 13 | 14 | use App\Controller\AppController as BaseController; 15 | 16 | /** 17 | * Class AppController 18 | * 19 | * @author Allan Carvalho 20 | * @license MIT License https://github.com/allanmcarvalho/cakephp-datatables/blob/master/LICENSE 21 | * @link https://github.com/allanmcarvalho/cakephp-datatables 22 | */ 23 | class AppController extends BaseController { 24 | 25 | /** 26 | * @return void 27 | */ 28 | public function initialize(): void { 29 | parent::initialize(); // TODO: Change the autogenerated stub 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/Plugin.php: -------------------------------------------------------------------------------- 1 | 10 | * @license MIT License https://github.com/allanmcarvalho/cakephp-data-renderer/blob/master/LICENSE 11 | * @link https://github.com/allanmcarvalho/cakephp-data-renderer 12 | */ 13 | declare(strict_types = 1); 14 | 15 | namespace DataTables; 16 | 17 | require_once __DIR__ . DS . '..' . DS . 'config' . DS . 'paths.php'; 18 | 19 | use Cake\Cache\Cache; 20 | use Cake\Core\BasePlugin; 21 | use Cake\Core\Configure; 22 | use Cake\Core\PluginApplicationInterface; 23 | use Cake\Error\FatalErrorException; 24 | use Cake\Utility\Hash; 25 | 26 | /** 27 | * Plugin for DataTables 28 | */ 29 | class Plugin extends BasePlugin { 30 | 31 | /** 32 | * @return void 33 | */ 34 | public function initialize(): void { 35 | parent::initialize(); 36 | } 37 | 38 | /** 39 | * Load all the plugin configuration and bootstrap logic. 40 | * 41 | * The host application is provided as an argument. This allows you to load 42 | * additional plugin dependencies, or attach events. 43 | * 44 | * @param \Cake\Core\PluginApplicationInterface $app The host application 45 | * @return void 46 | */ 47 | public function bootstrap(PluginApplicationInterface $app): void { 48 | $applicationDataTablesConfigs = Configure::read('DataTables', []); 49 | if (!is_array($applicationDataTablesConfigs)) { 50 | throw new FatalErrorException('DataTables config key must contain an array'); 51 | } 52 | $applicationDataTablesConfigs = Configure::read('DataTables', []); 53 | Configure::load('DataTables.app', 'default', true); 54 | $pluginDataTablesConfigs = Configure::read('DataTables', []); 55 | Configure::write('DataTables', Hash::merge($pluginDataTablesConfigs, $applicationDataTablesConfigs)); 56 | unset($applicationDataTablesConfigs); 57 | unset($pluginDataTablesConfigs); 58 | foreach (Configure::read('DataTables.Cache') as $cacheConfigName => $cacheConfig) { 59 | if (empty(Cache::getConfig($cacheConfigName))) { 60 | Cache::setConfig($cacheConfigName, $cacheConfig); 61 | } 62 | } 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/StorageEngine/CacheStorageEngine.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | declare(strict_types = 1); 11 | 12 | namespace DataTables\StorageEngine; 13 | 14 | use Cake\Cache\Cache; 15 | use DataTables\Table\ConfigBundle; 16 | 17 | /** 18 | * Class CacheStorageEngine 19 | * 20 | * @author Allan Carvalho 21 | * @license MIT License https://github.com/allanmcarvalho/cakephp-datatables/blob/master/LICENSE 22 | * @link https://github.com/allanmcarvalho/cakephp-datatables 23 | */ 24 | class CacheStorageEngine implements StorageEngineInterface { 25 | 26 | /** 27 | * @var string 28 | */ 29 | private $_cacheConfigName = '_data_tables_config_bundles_'; 30 | 31 | /** 32 | * CacheStorageEngine constructor. 33 | * 34 | * @param string|null $cacheConfigName 35 | */ 36 | public function __construct(?string $cacheConfigName = null) { 37 | if (!empty($cacheConfigName)) { 38 | $this->_cacheConfigName = $cacheConfigName; 39 | } 40 | Cache::getConfigOrFail($this->_cacheConfigName); 41 | } 42 | 43 | /** 44 | * @inheritDoc 45 | */ 46 | public function save(string $key, ConfigBundle $configBundle): bool { 47 | return Cache::write($key, $configBundle, '_data_tables_config_bundles_'); 48 | } 49 | 50 | /** 51 | * @inheritDoc 52 | */ 53 | public function exists(string $key): bool { 54 | return Cache::read($key, '_data_tables_config_bundles_') instanceof ConfigBundle; 55 | } 56 | 57 | /** 58 | * @inheritDoc 59 | */ 60 | public function read(string $key): ?ConfigBundle { 61 | $configBundle = Cache::read($key, '_data_tables_config_bundles_'); 62 | return ($configBundle instanceof ConfigBundle) ? $configBundle : null; 63 | } 64 | 65 | /** 66 | * @inheritDoc 67 | */ 68 | public function delete(string $key): bool { 69 | return Cache::delete($key, '_data_tables_config_bundles_'); 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/StorageEngine/StorageEngineInterface.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | declare(strict_types = 1); 11 | 12 | namespace DataTables\StorageEngine; 13 | 14 | use DataTables\Table\ConfigBundle; 15 | 16 | /** 17 | * Interface StorageEngineInterface 18 | * 19 | * @package DataTables\StorageEngine 20 | */ 21 | interface StorageEngineInterface { 22 | 23 | /** 24 | * Create or replace if exists a ConfigBundle for a key. 25 | * 26 | * @param string $key A unique key that represent this bundle. 27 | * @param \DataTables\Table\ConfigBundle $configBundle A ConfigBundle instance. 28 | * @return bool True if the data was successfully saved, false on failure. 29 | */ 30 | public function save(string $key, ConfigBundle $configBundle): bool; 31 | 32 | /** 33 | * Check a ConfigBundle exist for a key. 34 | * 35 | * @param string $key A unique key that represent this bundle. 36 | * @return bool True if the data exists, false if not. 37 | */ 38 | public function exists(string $key): bool; 39 | 40 | /** 41 | * Read if a ConfigBundle for a key. 42 | * 43 | * @param string $key A unique key that represent this bundle. 44 | * @return \DataTables\Table\ConfigBundle|null ConfigBundle class if the data was successfully read, null on not found. 45 | */ 46 | public function read(string $key): ?ConfigBundle; 47 | 48 | /** 49 | * Delete a ConfigBundle exist for a key. 50 | * 51 | * @param string $key A unique key that represent this bundle. 52 | * @return bool True if the data was successfully deleted, false on failure. 53 | */ 54 | public function delete(string $key): bool; 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/Table/Column.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | declare(strict_types = 1); 11 | 12 | namespace DataTables\Table; 13 | 14 | use Cake\Error\FatalErrorException; 15 | use Cake\Utility\Inflector; 16 | use Cake\Utility\Text; 17 | use DataTables\Tools\Validator; 18 | use InvalidArgumentException; 19 | 20 | /** 21 | * Class Column 22 | * 23 | * @author Allan Carvalho 24 | * @license MIT License https://github.com/allanmcarvalho/cakephp-datatables/blob/master/LICENSE 25 | * @link https://github.com/allanmcarvalho/cakephp-datatables 26 | */ 27 | final class Column { 28 | 29 | const TYPE_DATE = 'date'; 30 | const TYPE_NUM = 'num'; 31 | const TYPE_NUM_FMT = 'num-fmt'; 32 | const TYPE_HTML_NUM = 'html-num'; 33 | const TYPE_HTML_NUM_FMT = 'html-num-fmt'; 34 | const TYPE_HTML = 'html'; 35 | const TYPE_STRING = 'string'; 36 | const VALID_TYPES = [ 37 | self::TYPE_DATE, 38 | self::TYPE_NUM, 39 | self::TYPE_NUM_FMT, 40 | self::TYPE_HTML_NUM, 41 | self::TYPE_HTML_NUM_FMT, 42 | self::TYPE_HTML, 43 | self::TYPE_STRING, 44 | ]; 45 | 46 | const DOM_TEXT = 'dom-text'; 47 | const DOM_SELECT = 'dom-select'; 48 | const DOM_CHECKBOX = 'dom-checkbox'; 49 | const VALID_ORDER_DATA_TYPES = [ 50 | self::DOM_TEXT, 51 | self::DOM_SELECT, 52 | self::DOM_CHECKBOX, 53 | ]; 54 | 55 | /** 56 | * The column name. 57 | * 58 | * @var string 59 | */ 60 | private $_name; 61 | 62 | /** 63 | * If the column is or not a database column. 64 | * 65 | * @var bool 66 | */ 67 | private $_database; 68 | 69 | /** 70 | * If the column is or not a database column. 71 | * 72 | * @var array 73 | */ 74 | private $_columnSchema; 75 | 76 | /** 77 | * @var string|null 78 | */ 79 | private $_cellType = null; 80 | 81 | /** 82 | * @var string|null 83 | */ 84 | private $_className = null; 85 | 86 | /** 87 | * @var string|null 88 | */ 89 | private $_contentPadding = null; 90 | 91 | /** 92 | * @var string|array|null 93 | */ 94 | private $_createdCell = null; 95 | 96 | /** 97 | * @var integer|array|null 98 | */ 99 | private $_orderData = null; 100 | 101 | /** 102 | * @var string|null 103 | */ 104 | private $_orderDataType = null; 105 | 106 | /** 107 | * @var array 108 | */ 109 | private $_orderSequence = []; 110 | 111 | /** 112 | * @var boolean|null 113 | */ 114 | private $_orderable = null; 115 | 116 | /** 117 | * @var boolean|null 118 | */ 119 | private $_searchable = null; 120 | 121 | /** 122 | * @var string|null 123 | */ 124 | private $_title = null; 125 | 126 | /** 127 | * @var string|null 128 | */ 129 | private $_type = null; 130 | 131 | /** 132 | * @var boolean|null 133 | */ 134 | private $_visible = null; 135 | 136 | /** 137 | * @var string|null 138 | */ 139 | private $_width = null; 140 | 141 | /** 142 | * Column constructor. 143 | * 144 | * @param string $name 145 | * @param string|null $title 146 | * @param bool $database 147 | * @param array $columnSchema 148 | */ 149 | public function __construct(string $name, string $title = null, bool $database = true, array $columnSchema = []) { 150 | $this->_name = $name; 151 | if (!empty($title)) { 152 | $this->_title = $title; 153 | } elseif ($database === true) { 154 | $this->_title = Inflector::humanize(explode('.', $name)[1]); 155 | } else { 156 | $this->_title = Inflector::humanize($name); 157 | } 158 | 159 | $this->_database = $database; 160 | $this->_columnSchema = $columnSchema; 161 | } 162 | 163 | /** 164 | * Set the attributes using a Column class. 165 | * 166 | * @param \DataTables\Table\Column $column 167 | * @return void 168 | */ 169 | public function setDefault(Column $column): void { 170 | $ignoredMethods = ['setDefault', 'setTitle', 'setDatabase', 'setName']; 171 | $methods = get_class_methods($this); 172 | foreach ($methods as $method) { 173 | if (substr($method, 0, 3) === 'set' && !in_array($method, $ignoredMethods)) { 174 | $setMethod = $method; 175 | $getMethod = substr_replace($method, 'get', 0, 3); 176 | $checkMethod = substr_replace($method, 'is', 0, 3); 177 | if (in_array($getMethod, $methods)) { 178 | $this->{$setMethod}($column->{$getMethod}()); 179 | } elseif (in_array($checkMethod, $methods)) { 180 | $this->{$setMethod}($column->{$checkMethod}()); 181 | } else { 182 | throw new FatalErrorException("Method getter '$getMethod' or checker '$checkMethod' not found."); 183 | } 184 | } 185 | } 186 | } 187 | 188 | /** 189 | * Get column name 190 | * 191 | * @return string 192 | */ 193 | public function getName(): string { 194 | return $this->_name; 195 | } 196 | 197 | /** 198 | * Check if is a database column or not. 199 | * 200 | * @return bool 201 | */ 202 | public function isDatabase(): bool { 203 | return $this->_database; 204 | } 205 | 206 | /** 207 | * Getter method. 208 | * Change the cell type created for the column - either TD cells or TH cells. 209 | * This can be useful as TH cells have semantic meaning in the table body, allowing them to act as a header for a 210 | * row (you may wish to add scope='row' to the TH elements using columns.createdCell option). 211 | * 212 | * @return string|null 213 | * @link https://datatables.net/reference/option/columns.cellType 214 | */ 215 | public function getCellType(): ?string { 216 | return $this->_cellType; 217 | } 218 | 219 | /** 220 | * Setter method. 221 | * Change the cell type created for the column - either TD cells or TH cells. 222 | * This can be useful as TH cells have semantic meaning in the table body, allowing them to act as a header for a 223 | * row (you may wish to add scope='row' to the TH elements using columns.createdCell option). 224 | * 225 | * @param string|null $cellType 226 | * @return \DataTables\Table\Column 227 | * @link https://datatables.net/reference/option/columns.cellType 228 | */ 229 | public function setCellType(?string $cellType): self { 230 | if (!in_array($cellType, ['td', 'th']) && !empty($cellType)) { 231 | throw new InvalidArgumentException("\$cellType must be 'td' or 'th'. Found: $cellType."); 232 | } 233 | $this->_cellType = $cellType; 234 | return $this; 235 | } 236 | 237 | /** 238 | * Getter method. 239 | * Quite simply this option adds a class to each cell in a column, regardless of if the table source is from DOM, 240 | * Javascript or Ajax. This can be useful for styling columns. 241 | * 242 | * @return string|null 243 | * @link https://datatables.net/reference/option/columns.className 244 | */ 245 | public function getClassName(): ?string { 246 | return $this->_className; 247 | } 248 | 249 | /** 250 | * Setter method. 251 | * Quite simply this option adds a class to each cell in a column, regardless of if the table source is from DOM, 252 | * Javascript or Ajax. This can be useful for styling columns. 253 | * 254 | * @param string|null $className 255 | * @return \DataTables\Table\Column 256 | * @link https://datatables.net/reference/option/columns.className 257 | */ 258 | public function setClassName(?string $className): self { 259 | $this->_className = $className; 260 | return $this; 261 | } 262 | 263 | /** 264 | * Getter method. 265 | * Quite simply this option adds a class to each cell in a column, regardless of if the table source is from DOM, 266 | * Javascript or Ajax. This can be useful for styling columns. 267 | * 268 | * @return string|null 269 | * @link https://datatables.net/reference/option/columns.contentPadding 270 | */ 271 | public function getContentPadding(): ?string { 272 | return $this->_contentPadding; 273 | } 274 | 275 | /** 276 | * Setter method. 277 | * The first thing to say about this property is that generally you shouldn't need this! 278 | * 279 | * Having said that, it can be useful on rare occasions. When DataTables calculates the column widths to assign to 280 | * each column, it finds the longest string in each column and then constructs a temporary table and reads the 281 | * widths from that. The problem with this is that "mmm" is much wider then "iiii", but the latter is a longer 282 | * string - thus the calculation can go wrong (doing it properly and putting it into an DOM object and measuring 283 | * that is horribly slow!). Thus as a "work around" we provide this option. It will append its value to the text 284 | * that is found to be the longest string for the column - i.e. padding. 285 | * 286 | * @param string $contentPadding 287 | * @return \DataTables\Table\Column 288 | * @link https://datatables.net/reference/option/columns.contentPadding 289 | */ 290 | public function setContentPadding(?string $contentPadding): self { 291 | $this->_contentPadding = $contentPadding; 292 | return $this; 293 | } 294 | 295 | /** 296 | * Getter method. 297 | * This is a callback function that is executed whenever a cell is created (Ajax source, etc) or read from a DOM 298 | * source. It can be used as a complement to columns.render allowing modification of the cell's DOM element (add 299 | * background colour for example) when the element is created (cells may not be immediately created on table 300 | * initialisation if deferRender is enabled, or if rows are dynamically added using the API (rows.add()). 301 | * 302 | * This is the counterpart callback for rows, which use the createdRow option. 303 | * 304 | * Accessible parameters inside js function: 305 | * - cell (node) - The TD node that has been created. 306 | * - cellData (any) - Cell data. If you use columns.render to modify the data, use $(cell).html() to get and modify 307 | * the rendered data. The information given here is the original and unmodified data from the data source. 308 | * - rowData (any) - Data source object / array for the whole row. 309 | * - rowIndex (integer) - DataTables' internal index for the row. 310 | * - colIndex (integer) - DataTables' internal index for the column. 311 | * 312 | * @return string|array|null 313 | * @link https://datatables.net/reference/option/columns.createdCell 314 | * @link https://datatables.net/reference/type/node 315 | * @link https://datatables.net/reference/type/integer 316 | */ 317 | public function getCreatedCell() { 318 | return $this->_createdCell; 319 | } 320 | 321 | /** 322 | * Setter method. 323 | * This is a callback function that is executed whenever a cell is created (Ajax source, etc) or read from a DOM 324 | * source. It can be used as a complement to columns.render allowing modification of the cell's DOM element (add 325 | * background colour for example) when the element is created (cells may not be immediately created on table 326 | * initialisation if deferRender is enabled, or if rows are dynamically added using the API (rows.add()). 327 | * 328 | * This is the counterpart callback for rows, which use the createdRow option. 329 | * 330 | * Accessible parameters inside js function: 331 | * - cell (node) - The TD node that has been created. 332 | * - cellData (any) - Cell data. If you use columns.render to modify the data, use $(cell).html() to get and modify 333 | * the rendered data. The information given here is the original and unmodified data from the data source. 334 | * - rowData (any) - Data source object / array for the whole row. 335 | * - rowIndex (integer) - DataTables' internal index for the row. 336 | * - colIndex (integer) - DataTables' internal index for the column. 337 | * 338 | * @param string|array|null $bodyOrParams To use application template file, leave blank or pass an array with params 339 | * that will be used in file. To use the body mode, pass an string that will 340 | * putted inside the js function. 341 | * @return \DataTables\Table\Column 342 | * @link https://datatables.net/reference/option/columns.createdCell 343 | * @link https://datatables.net/reference/type/node 344 | * @link https://datatables.net/reference/type/integer 345 | */ 346 | public function setCreatedCell($bodyOrParams = []): self { 347 | $bodyOrParamsType = getType($bodyOrParams); 348 | $validTypes = ['string', 'array', 'NULL']; 349 | $validTypesString = str_replace(' and ', ' or ', Text::toList($validTypes)); 350 | if (!in_array($bodyOrParamsType, $validTypes)) { 351 | throw new InvalidArgumentException("In \$bodyOrParams you can use only $validTypesString. Found: '$bodyOrParamsType'."); 352 | } 353 | $this->_createdCell = $bodyOrParams; 354 | return $this; 355 | } 356 | 357 | /** 358 | * Getter method. 359 | * Allows a column's sorting to take either the data from a different (often hidden) column as the data to sort, or 360 | * data from multiple columns. 361 | * 362 | * A common example of this is a table which contains first and last name columns next to each other, it is 363 | * intuitive that they would be linked together to multi-column sort. Another example, with a single column, is the 364 | * case where the data shown to the end user is not directly sortable itself (a column with images in it), but 365 | * there is some meta data than can be sorted (e.g. file name) - note that orthogonal data is an alternative method 366 | * that can be used for this. 367 | * 368 | * @return int|array|null 369 | * @link https://datatables.net/reference/option/columns.orderData 370 | */ 371 | public function getOrderData() { 372 | return $this->_orderData; 373 | } 374 | 375 | /** 376 | * Setter method. 377 | * Allows a column's sorting to take either the data from a different (often hidden) column as the data to sort, or 378 | * data from multiple columns. 379 | * 380 | * A common example of this is a table which contains first and last name columns next to each other, it is 381 | * intuitive that they would be linked together to multi-column sort. Another example, with a single column, is the 382 | * case where the data shown to the end user is not directly sortable itself (a column with images in it), but 383 | * there is some meta data than can be sorted (e.g. file name) - note that orthogonal data is an alternative method 384 | * that can be used for this. 385 | * 386 | * @param int|array|null $orderData 387 | * @return \DataTables\Table\Column 388 | * @link https://datatables.net/reference/option/columns.orderData 389 | */ 390 | public function setOrderData($orderData): self { 391 | $orderDataType = getType($orderData); 392 | $validTypes = ['integer', 'array', 'NULL']; 393 | $validTypesString = str_replace(' and ', ' or ', Text::toList($validTypes)); 394 | if (is_array($orderData)) { 395 | Validator::getInstance()->checkKeysValueTypesOrFail($orderData, 'integer', 'integer', '$orderData'); 396 | } elseif ($orderDataType === 'integer' && $orderData < 0) { 397 | throw new InvalidArgumentException("In \$orderData must be greater or equal 0. Found: '$orderData'."); 398 | } elseif (!in_array($orderDataType, $validTypes)) { 399 | throw new InvalidArgumentException("In \$orderData you can use only $validTypesString. Found: '$orderDataType'."); 400 | } 401 | $this->_orderData = $orderData; 402 | return $this; 403 | } 404 | 405 | /** 406 | * Getter method. 407 | * DataTables' primary order method (the ordering feature) makes use of data that has been cached in memory rather 408 | * than reading the data directly from the DOM every time an order is performed for performance reasons (reading 409 | * from the DOM is inherently slow). However, there are times when you do actually want to read directly from the 410 | * DOM, acknowledging that there will be a performance hit, for example when you have form elements in the table 411 | * and the end user can alter the values. This configuration option is provided to allow plug-ins to provide this 412 | * capability in DataTables. 413 | * 414 | * Please note that there are no columns.orderDataType plug-ins built into DataTables, they must be added 415 | * separately. See the DataTables sorting plug-ins page for further information. 416 | * 417 | * @return string|null 418 | * @link https://datatables.net/reference/option/columns.orderDataType 419 | * @link https://datatables.net/plug-ins/sorting/ 420 | */ 421 | public function getOrderDataType(): ?string { 422 | return $this->_orderDataType; 423 | } 424 | 425 | /** 426 | * Setter method. 427 | * DataTables' primary order method (the ordering feature) makes use of data that has been cached in memory rather 428 | * than reading the data directly from the DOM every time an order is performed for performance reasons (reading 429 | * from the DOM is inherently slow). However, there are times when you do actually want to read directly from the 430 | * DOM, acknowledging that there will be a performance hit, for example when you have form elements in the table 431 | * and the end user can alter the values. This configuration option is provided to allow plug-ins to provide this 432 | * capability in DataTables. 433 | * 434 | * Please note that there are no columns.orderDataType plug-ins built into DataTables, they must be added 435 | * separately. See the DataTables sorting plug-ins page for further information. 436 | * 437 | * @param string|null $orderDataType 438 | * @return \DataTables\Table\Column 439 | * @link https://datatables.net/reference/option/columns.orderDataType 440 | * @link https://datatables.net/plug-ins/sorting/ 441 | */ 442 | public function setOrderDataType(?string $orderDataType): self { 443 | $validOrderDataTypeString = str_replace(' and ', ' or ', Text::toList(static::VALID_ORDER_DATA_TYPES)); 444 | if (!in_array($orderDataType, static::VALID_ORDER_DATA_TYPES) && !empty($orderDataType)) { 445 | throw new InvalidArgumentException("In \$orderDataType you can use only $validOrderDataTypeString. Found: '$orderDataType'."); 446 | } 447 | $this->_orderDataType = $orderDataType; 448 | return $this; 449 | } 450 | 451 | /** 452 | * Getter method. 453 | * You can control the default ordering direction, and even alter the behaviour of the order handler (i.e. only 454 | * allow ascending sorting etc) using this parameter. 455 | * 456 | * @return array 457 | * @link https://datatables.net/reference/option/columns.orderSequence 458 | */ 459 | public function getOrderSequence(): array { 460 | return $this->_orderSequence; 461 | } 462 | 463 | /** 464 | * Setter method. 465 | * You can control the default ordering direction, and even alter the behaviour of the order handler (i.e. only 466 | * allow ascending sorting etc) using this parameter. 467 | * 468 | * @param array $orderSequence 469 | * @return \DataTables\Table\Column 470 | * @link https://datatables.net/reference/option/columns.orderSequence 471 | */ 472 | public function setOrderSequence(array $orderSequence = []): self { 473 | Validator::getInstance()->checkKeysValueTypesOrFail($orderSequence, 'integer', 'string', '$orderSequence'); 474 | foreach ($orderSequence as $item) { 475 | if (!in_array($item, ['asc', 'desc'])) { 476 | throw new InvalidArgumentException("In \$orderDataType you can use only 'asc' or 'desc'. Found: '$item'."); 477 | } 478 | } 479 | $this->_orderSequence = $orderSequence; 480 | return $this; 481 | } 482 | 483 | /** 484 | * Checker method. 485 | * Using this parameter, you can remove the end user's ability to order upon a column. This might be useful for 486 | * generated content columns, for example if you have 'Edit' or 'Delete' buttons in the table. 487 | * 488 | * Note that this option only affects the end user's ability to order a column. Developers are still able to order 489 | * a column using the order option or the order() method if required. 490 | * 491 | * @return bool|null 492 | * @link https://datatables.net/reference/option/columns.orderable 493 | */ 494 | public function isOrderable(): ?bool { 495 | return $this->_orderable; 496 | } 497 | 498 | /** 499 | * Setter method. 500 | * Using this parameter, you can remove the end user's ability to order upon a column. This might be useful for 501 | * generated content columns, for example if you have 'Edit' or 'Delete' buttons in the table. 502 | * 503 | * Note that this option only affects the end user's ability to order a column. Developers are still able to order 504 | * a column using the order option or the order() method if required. 505 | * 506 | * @param bool|null $orderable 507 | * @return \DataTables\Table\Column 508 | * @link https://datatables.net/reference/option/columns.orderable 509 | */ 510 | public function setOrderable(?bool $orderable): self { 511 | $this->_orderable = $orderable; 512 | return $this; 513 | } 514 | 515 | /** 516 | * Checker method. 517 | * Using this parameter, you can define if DataTables should include this column in the filterable data in the 518 | * table. You may want to use this option to disable search on generated columns such as 'Edit' and 'Delete' 519 | * buttons for example. 520 | * 521 | * @return bool|null 522 | * @link https://datatables.net/reference/option/columns.searchable 523 | */ 524 | public function isSearchable(): ?bool { 525 | return $this->_searchable; 526 | } 527 | 528 | /** 529 | * Setter method. 530 | * Using this parameter, you can define if DataTables should include this column in the filterable data in the 531 | * table. You may want to use this option to disable search on generated columns such as 'Edit' and 'Delete' 532 | * buttons for example. 533 | * 534 | * @param bool|null $searchable 535 | * @return \DataTables\Table\Column 536 | * @link https://datatables.net/reference/option/columns.searchable 537 | */ 538 | public function setSearchable(?bool $searchable): self { 539 | $this->_searchable = $searchable; 540 | return $this; 541 | } 542 | 543 | /** 544 | * Getter method. 545 | * The titles of columns are typically read directly from the DOM (from the cells in the THEAD element), but it can 546 | * often be useful to either override existing values, or have DataTables actually construct a header with column 547 | * titles for you (for example if there is not a THEAD element in the table before DataTables is constructed). This 548 | * option is available to provide that ability. 549 | * 550 | * Please note that when constructing a header, DataTables can only construct a simple header with a single cell 551 | * for each column. Complex headers with colspan and rowspan attributes must either already be defined in the 552 | * document, or be constructed using standard DOM / jQuery methods. 553 | * 554 | * @return string 555 | * @link https://datatables.net/reference/option/columns.title 556 | */ 557 | public function getTitle(): string { 558 | return $this->_title; 559 | } 560 | 561 | /** 562 | * Setter method. 563 | * The titles of columns are typically read directly from the DOM (from the cells in the THEAD element), but it can 564 | * often be useful to either override existing values, or have DataTables actually construct a header with column 565 | * titles for you (for example if there is not a THEAD element in the table before DataTables is constructed). This 566 | * option is available to provide that ability. 567 | * 568 | * Please note that when constructing a header, DataTables can only construct a simple header with a single cell 569 | * for each column. Complex headers with colspan and rowspan attributes must either already be defined in the 570 | * document, or be constructed using standard DOM / jQuery methods. 571 | * 572 | * @param string $title 573 | * @return \DataTables\Table\Column 574 | * @link https://datatables.net/reference/option/columns.title 575 | */ 576 | public function setTitle(string $title): self { 577 | $this->_title = $title; 578 | return $this; 579 | } 580 | 581 | /** 582 | * Getter method. 583 | * When operating in client-side processing mode, DataTables can process the data used for the display in each cell 584 | * in a manner suitable for the action being performed. For example, HTML tags will be removed from the strings 585 | * used for filter matching, while sort formatting may remove currency symbols to allow currency values to be 586 | * sorted numerically. The formatting action performed to normalise the data so it can be ordered and searched 587 | * depends upon the column's type. 588 | * 589 | * DataTables has a number of built in types which are automatically detected: 590 | * - date - Date / time values. Note that DataTables' built in date parsing uses Javascript's Date.parse() method 591 | * which supports only a very limited subset of dates. Additional date format support can be added through the 592 | * use of plug-ins. 593 | * - Sorting - sorted chronologically 594 | * - Filtering - no effect 595 | * - num - Simple number sorting. 596 | * - Sorting - sorted numerically 597 | * - Filtering - no effect 598 | * - num-fmt - Numeric sorting of formatted numbers. Numbers which are formatted with thousands separators, 599 | * currency symbols or a percentage indicator will be sorted numerically automatically by DataTables. 600 | * - Supported built-in currency symbols are $, £, € and ¥. 601 | * - Supported built-in thousands separators are ' and ,. 602 | * Examples: 603 | * - $100,000 - sorted as 100000 604 | * - £10'000 - sorted as 10000 605 | * - 5'000 - sorted as 5000 606 | * - 40% - sorted as 40 607 | * - Sorting - sorted numerically 608 | * - Filtering - no effect 609 | * - html-num - As per the num option, but with HTML tags also in the data. 610 | * - Sorting - sorted numerically 611 | * - Filtering - HTML tags removed from filtering string 612 | * - html-num-fmt - As per the num-fmt option, but with HTML tags also in the data. 613 | * - Sorting - sorted numerically 614 | * - Filtering - HTML tags removed from filtering string 615 | * - html - Basic string processing for HTML tags 616 | * - Sorting - sorted with HTML tags removed 617 | * - Filtering - HTML tags removed from filtering string 618 | * - string - Fall back type if the data in the column does not match the requirements for the other data types 619 | * (above). 620 | * - Sorting - no effect 621 | * - Filtering - no effect 622 | * 623 | * It is expected that the above options will cover the majority of data types used with DataTables, however, data 624 | * is flexible and comes in many forms, so additional types with different effects can be added through the use of 625 | * plug-ins. This provides the ability to sort almost any data format imaginable! 626 | * 627 | * As an optimisation, if you know the column type in advance, you can set the value using this option, saving 628 | * DataTables from running its auto detection routine. 629 | * 630 | * Please note that if you are using server-side processing (serverSide) this option has no effect since the 631 | * ordering and search actions are performed by a server-side script. 632 | * 633 | * @return string|null 634 | * @link https://datatables.net/reference/option/columns.type 635 | */ 636 | public function getType(): ?string { 637 | return $this->_type; 638 | } 639 | 640 | /** 641 | * Setter method. 642 | * When operating in client-side processing mode, DataTables can process the data used for the display in each cell 643 | * in a manner suitable for the action being performed. For example, HTML tags will be removed from the strings 644 | * used for filter matching, while sort formatting may remove currency symbols to allow currency values to be 645 | * sorted numerically. The formatting action performed to normalise the data so it can be ordered and searched 646 | * depends upon the column's type. 647 | * 648 | * DataTables has a number of built in types which are automatically detected: 649 | * - date - Date / time values. Note that DataTables' built in date parsing uses Javascript's Date.parse() method 650 | * which supports only a very limited subset of dates. Additional date format support can be added through the 651 | * use of plug-ins. 652 | * - Sorting - sorted chronologically 653 | * - Filtering - no effect 654 | * - num - Simple number sorting. 655 | * - Sorting - sorted numerically 656 | * - Filtering - no effect 657 | * - num-fmt - Numeric sorting of formatted numbers. Numbers which are formatted with thousands separators, 658 | * currency symbols or a percentage indicator will be sorted numerically automatically by DataTables. 659 | * - Supported built-in currency symbols are $, £, € and ¥. 660 | * - Supported built-in thousands separators are ' and ,. 661 | * Examples: 662 | * - $100,000 - sorted as 100000 663 | * - £10'000 - sorted as 10000 664 | * - 5'000 - sorted as 5000 665 | * - 40% - sorted as 40 666 | * - Sorting - sorted numerically 667 | * - Filtering - no effect 668 | * - html-num - As per the num option, but with HTML tags also in the data. 669 | * - Sorting - sorted numerically 670 | * - Filtering - HTML tags removed from filtering string 671 | * - html-num-fmt - As per the num-fmt option, but with HTML tags also in the data. 672 | * - Sorting - sorted numerically 673 | * - Filtering - HTML tags removed from filtering string 674 | * - html - Basic string processing for HTML tags 675 | * - Sorting - sorted with HTML tags removed 676 | * - Filtering - HTML tags removed from filtering string 677 | * - string - Fall back type if the data in the column does not match the requirements for the other data types 678 | * (above). 679 | * - Sorting - no effect 680 | * - Filtering - no effect 681 | * 682 | * It is expected that the above options will cover the majority of data types used with DataTables, however, data 683 | * is flexible and comes in many forms, so additional types with different effects can be added through the use of 684 | * plug-ins. This provides the ability to sort almost any data format imaginable! 685 | * 686 | * As an optimisation, if you know the column type in advance, you can set the value using this option, saving 687 | * DataTables from running its auto detection routine. 688 | * 689 | * Please note that if you are using server-side processing (serverSide) this option has no effect since the 690 | * ordering and search actions are performed by a server-side script. 691 | * 692 | * @param string|null $type 693 | * @return \DataTables\Table\Column 694 | * @link https://datatables.net/reference/option/columns.type 695 | */ 696 | public function setType(?string $type): self { 697 | $validTypesString = str_replace(' and ', ' or ', Text::toList(static::VALID_TYPES)); 698 | if (!in_array($type, static::VALID_TYPES) && !empty($type)) { 699 | throw new InvalidArgumentException("Type must be $validTypesString. Found: '$type'."); 700 | } 701 | $this->_type = $type; 702 | return $this; 703 | } 704 | 705 | /** 706 | * Checker method. 707 | * DataTables and show and hide columns dynamically through use of this option and the column().visible() / 708 | * columns().visible() methods. This option can be used to get the initial visibility state of the column, with the 709 | * API methods used to alter that state at a later time. 710 | * 711 | * This can be particularly useful if your table holds a large number of columns and you wish the user to have the 712 | * ability to control which columns they can see, or you have data in the table that the end user shouldn't see 713 | * (for example a database ID column). 714 | * 715 | * @return bool|null 716 | * @link https://datatables.net/reference/option/columns.visible 717 | */ 718 | public function isVisible(): ?bool { 719 | return $this->_visible; 720 | } 721 | 722 | /** 723 | * Setter method. 724 | * DataTables and show and hide columns dynamically through use of this option and the column().visible() / 725 | * columns().visible() methods. This option can be used to get the initial visibility state of the column, with the 726 | * API methods used to alter that state at a later time. 727 | * 728 | * This can be particularly useful if your table holds a large number of columns and you wish the user to have the 729 | * ability to control which columns they can see, or you have data in the table that the end user shouldn't see 730 | * (for example a database ID column). 731 | * 732 | * @param bool|null $visible 733 | * @return \DataTables\Table\Column 734 | * @link https://datatables.net/reference/option/columns.visible 735 | */ 736 | public function setVisible(?bool $visible): self { 737 | $this->_visible = $visible; 738 | return $this; 739 | } 740 | 741 | /** 742 | * Getter method. 743 | * This parameter can be used to define the width of a column, and may take any CSS value (3em, 20px etc). 744 | * 745 | * Please note that pixel perfect column width is virtually impossible to achieve in tables with dynamic content, 746 | * so do not be surprised if the width of the column if off by a few pixels from what you assign using this 747 | * property. Column width in tables depends upon many properties such as cell borders, table borders, the 748 | * border-collapse property, the content of the table and many other properties. Both DataTables and the browsers 749 | * attempt to lay the table out in an optimal manner taking this options all into account. 750 | * 751 | * @return string|null 752 | * @link https://datatables.net/reference/option/columns.width 753 | */ 754 | public function getWidth(): ?string { 755 | return $this->_width; 756 | } 757 | 758 | /** 759 | * Setter method. 760 | * This parameter can be used to define the width of a column, and may take any CSS value (3em, 20px etc). 761 | * 762 | * Please note that pixel perfect column width is virtually impossible to achieve in tables with dynamic content, 763 | * so do not be surprised if the width of the column if off by a few pixels from what you assign using this 764 | * property. Column width in tables depends upon many properties such as cell borders, table borders, the 765 | * border-collapse property, the content of the table and many other properties. Both DataTables and the browsers 766 | * attempt to lay the table out in an optimal manner taking this options all into account. 767 | * 768 | * @param string|null $width 769 | * @return \DataTables\Table\Column 770 | * @link https://datatables.net/reference/option/columns.width 771 | */ 772 | public function setWidth(?string $width): self { 773 | $this->_width = $width; 774 | return $this; 775 | } 776 | 777 | } 778 | -------------------------------------------------------------------------------- /src/Table/Columns.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | declare(strict_types = 1); 11 | 12 | namespace DataTables\Table; 13 | 14 | use Cake\Error\FatalErrorException; 15 | use Cake\Utility\Inflector; 16 | use InvalidArgumentException; 17 | 18 | /** 19 | * Class Columns 20 | * 21 | * @author Allan Carvalho 22 | * @license MIT License https://github.com/allanmcarvalho/cakephp-datatables/blob/master/LICENSE 23 | * @link https://github.com/allanmcarvalho/cakephp-datatables 24 | */ 25 | final class Columns { 26 | 27 | /** 28 | * Created columns. 29 | * 30 | * @var \DataTables\Table\Column[] 31 | */ 32 | private $_columns = []; 33 | 34 | /** 35 | * A selected Tables class. 36 | * 37 | * @var \DataTables\Table\Tables 38 | */ 39 | private $_tables; 40 | 41 | /** 42 | * Default application column configuration. 43 | * 44 | * @var \DataTables\Table\Column 45 | */ 46 | public $Default; 47 | 48 | /** 49 | * Columns constructor. 50 | * 51 | * @param \DataTables\Table\Tables $tables 52 | */ 53 | public function __construct(Tables $tables) { 54 | $this->_tables = $tables; 55 | $this->Default = new Column('default', 'empty', false); 56 | } 57 | 58 | /** 59 | * Return all configured columns. 60 | * 61 | * @return array 62 | */ 63 | public function getColumns(): array { 64 | return $this->_columns; 65 | } 66 | 67 | /** 68 | * Add a database column to DataTables table. 69 | * 70 | * @param string $dataBaseField 71 | * @param string|null $title 72 | * @return \DataTables\Table\Column 73 | */ 74 | public function addDatabaseColumn(string $dataBaseField, ?string $title = null): Column { 75 | $column = $this->normalizeDataTableField($dataBaseField, $title); 76 | return $this->saveColumn($column); 77 | } 78 | 79 | /** 80 | * Add a non database column to DataTables table. 81 | * 82 | * @param string $label 83 | * @param string|null $title 84 | * @return \DataTables\Table\Column 85 | */ 86 | public function addNonDatabaseColumn(string $label, ?string $title = null): Column { 87 | $column = new Column($label, $title, false); 88 | return $this->saveColumn($column); 89 | } 90 | 91 | /** 92 | * Save the column on array. 93 | * 94 | * @param \DataTables\Table\Column $column 95 | * @return \DataTables\Table\Column 96 | */ 97 | private function saveColumn(Column $column): Column { 98 | foreach ($this->_columns as $key => $savedColumn) { 99 | if ($savedColumn->getName() === $column->getName()) { 100 | throw new FatalErrorException("Column '{$column->getName()}' already exist in index $key."); 101 | } 102 | } 103 | $column->setDefault($this->Default); 104 | $this->_columns[] = $column; 105 | return $column; 106 | } 107 | 108 | /** 109 | * Check if class, tables, fields and associations exists, and after normalize the name. 110 | * 111 | * @param string $dataBaseField 112 | * @param string|null $title 113 | * @return \DataTables\Table\Column 114 | */ 115 | private function normalizeDataTableField(string $dataBaseField, ?string $title): Column { 116 | $ormTable = $this->_tables->getOrmTable(); 117 | $explodedDataBaseField = explode('.', $dataBaseField); 118 | if (count($explodedDataBaseField) === 2) { 119 | $table = Inflector::camelize($explodedDataBaseField[0]); 120 | $column = Inflector::dasherize($explodedDataBaseField[1]); 121 | } elseif (count($explodedDataBaseField) == 1) { 122 | $table = Inflector::camelize($ormTable->getAlias()); 123 | $column = Inflector::dasherize($explodedDataBaseField[0]); 124 | } else { 125 | throw new InvalidArgumentException("$dataBaseField is a invalid \$dataBaseField."); 126 | } 127 | 128 | if ($table === Inflector::camelize($ormTable->getAlias())) { 129 | if (!$ormTable->getSchema()->hasColumn($column)) { 130 | throw new InvalidArgumentException("The field '$column' not exists in '$table'"); 131 | } 132 | $columnSchema = $this->_tables->getOrmTable()->getSchema()->getColumn($column); 133 | } else { 134 | if (!$ormTable->hasAssociation($table)) { 135 | throw new InvalidArgumentException("The table '$table' isn't associated with '" . $ormTable->getAlias() . "'."); 136 | } 137 | /** @var \Cake\ORM\Association|\Cake\ORM\Table $association */ 138 | $association = $ormTable->getAssociation($table); 139 | if (!$association->getSchema()->hasColumn($column)) { 140 | throw new InvalidArgumentException("The field '$column' not exists in '{$association->getAlias()}'"); 141 | } 142 | $columnSchema = $association->getSchema()->getColumn($column); 143 | } 144 | $column = new Column("$table.$column", $title, true, $columnSchema); 145 | 146 | return $column; 147 | } 148 | 149 | } 150 | -------------------------------------------------------------------------------- /src/Table/ConfigBundle.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | declare(strict_types = 1); 11 | 12 | namespace DataTables\Table; 13 | 14 | use Cake\View\View; 15 | use DataTables\Table\Option\MainOption; 16 | 17 | /** 18 | * Class ConfigBundle 19 | * 20 | * @author Allan Carvalho 21 | * @license MIT License https://github.com/allanmcarvalho/cakephp-datatables/blob/master/LICENSE 22 | * @link https://github.com/allanmcarvalho/cakephp-datatables 23 | */ 24 | final class ConfigBundle { 25 | 26 | /** 27 | * @var string The md5 used to check changes. 28 | */ 29 | private $_checkMd5; 30 | 31 | /** 32 | * @var \DataTables\Table\QueryBaseState The DataTables query state. 33 | */ 34 | public $Query; 35 | 36 | /** 37 | * @var \DataTables\Table\Columns The DataTables table columns. 38 | */ 39 | public $Columns; 40 | 41 | /** 42 | * @var \DataTables\Table\Option\MainOption The DataTables JS Options. 43 | */ 44 | public $Options; 45 | 46 | /** 47 | * ConfigBundle constructor. 48 | * 49 | * @param string $checkMd5 The md5 used to check changes. 50 | * @param \DataTables\Table\QueryBaseState $queryBaseState The DataTables base query. 51 | * @param \DataTables\Table\Columns $_columns The DataTables table columns. 52 | * @param \DataTables\Table\Option\MainOption $options The DataTables JS Options. 53 | */ 54 | public function __construct(string $checkMd5, QueryBaseState $queryBaseState, Columns $_columns, MainOption $options) { 55 | $this->_checkMd5 = $checkMd5; 56 | $this->Query = $queryBaseState; 57 | $this->Columns = $_columns; 58 | $this->Options = $options; 59 | } 60 | 61 | /** 62 | * @return string 63 | */ 64 | public function getCheckMd5(): string { 65 | return $this->_checkMd5; 66 | } 67 | 68 | /** 69 | * @return string 70 | */ 71 | public function getUniqueId(): string { 72 | return $this->_checkMd5; 73 | } 74 | 75 | /** 76 | * @param \Cake\View\View $view 77 | * @return string 78 | */ 79 | public function generateTableHtml(View $view): string { 80 | return $view->cell('DataTables.DataTables::table', [$this->Columns])->render(); 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/Table/Option/CallBack/MainCallBack.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | declare(strict_types = 1); 11 | 12 | namespace DataTables\Table\Option\CallBack; 13 | 14 | use Cake\Core\Configure; 15 | use Cake\Error\FatalErrorException; 16 | use Cake\Utility\Inflector; 17 | use DataTables\Tools\Validator; 18 | use InvalidArgumentException; 19 | use Twig\Environment; 20 | use Twig\Loader\FilesystemLoader; 21 | 22 | /** 23 | * Class MainCallBack 24 | * 25 | * @author Allan Carvalho 26 | * @license MIT License https://github.com/allanmcarvalho/cakephp-datatables/blob/master/LICENSE 27 | * @link https://github.com/allanmcarvalho/cakephp-datatables 28 | */ 29 | final class MainCallBack { 30 | 31 | /** 32 | * @var string 33 | */ 34 | protected $_callbackNamePrefix = 'callback_'; 35 | 36 | /** 37 | * @var string 38 | */ 39 | protected $_callbackName; 40 | 41 | /** 42 | * @var string 43 | */ 44 | protected $_appTemplateFolder; 45 | 46 | /** 47 | * @var string 48 | */ 49 | protected $_pluginTemplateFolder; 50 | 51 | /** 52 | * @var string 53 | */ 54 | protected $_ext = '.twig'; 55 | 56 | /** 57 | * @var \Twig\Environment 58 | */ 59 | protected $_twig; 60 | 61 | /** 62 | * @var \Twig\Loader\FilesystemLoader 63 | */ 64 | protected $_twigLoader; 65 | 66 | /** 67 | * Storage a instance of object. 68 | * 69 | * @var self[] 70 | */ 71 | public static $instance; 72 | 73 | /** 74 | * MainCallBack constructor. 75 | * 76 | * @param string $callbackName 77 | * @param string $tablesName 78 | * @param string $config 79 | */ 80 | public function __construct(string $callbackName, string $tablesName, string $config) { 81 | $basePath = Configure::read('DataTables.resources.templates'); 82 | if (substr($basePath, -1, 1) !== DS) { 83 | $basePath .= DS; 84 | } 85 | $this->_callbackName = $this->_callbackNamePrefix . $callbackName . $this->_ext; 86 | $this->_appTemplateFolder = $basePath . $tablesName . DS . $config . DS; 87 | $this->_pluginTemplateFolder = DATA_TABLES_TEMPLATES . 'twig' . DS . 'js' . DS . 'functions' . DS; 88 | $this->_twigLoader = new FilesystemLoader(); 89 | $this->_twig = new Environment($this->_twigLoader); 90 | if (Configure::read('debug') === true) { 91 | $this->_twig->setCache(false); 92 | } else { 93 | $this->_twig->setCache(Configure::read('DataTables.resources.twigCacheFolder')); 94 | } 95 | } 96 | 97 | /** 98 | * Return a instance of builder object. 99 | * 100 | * @param string $callBack 101 | * @param string $tablesName 102 | * @param string $config 103 | * @return \DataTables\Table\Option\CallBack\MainCallBack 104 | */ 105 | public static function getInstance(string $callBack, string $tablesName, string $config): MainCallBack { 106 | $callBack = Inflector::underscore($callBack); 107 | $tablesName = Inflector::camelize($tablesName); 108 | $config = Inflector::underscore($config); 109 | $md5 = md5($callBack . $tablesName . $config); 110 | if (empty(static::$instance[$md5])) { 111 | static::$instance[$md5] = new self($callBack, $tablesName, $config); 112 | } 113 | return static::$instance[$md5]; 114 | } 115 | 116 | /** 117 | * Destroy all instances if exist. 118 | * 119 | * @return void 120 | */ 121 | public static function destroyAllInstances(): void { 122 | static::$instance = []; 123 | } 124 | 125 | /** 126 | * Render callback js functions with application template file or body. 127 | * 128 | * @param string|array $bodyOrParams To use application template file, leave blank or pass an array with params 129 | * that will be used in file. To use the body mode, pass an string that will 130 | * putted inside the js function. 131 | * @return string 132 | * @throws \Twig\Error\LoaderError 133 | * @throws \Twig\Error\RuntimeError 134 | * @throws \Twig\Error\SyntaxError 135 | * @link https://twig.symfony.com/doc/3.x/api.html 136 | */ 137 | public function render($bodyOrParams = []) { 138 | $bodyParamsType = getType($bodyOrParams); 139 | if ($bodyParamsType === 'array') { 140 | $this->checkIfFileExistsOfFail($this->_appTemplateFolder . $this->_callbackName); 141 | Validator::getInstance()->checkKeysValueTypesOrFail($bodyOrParams, 'string', '*'); 142 | $this->_twigLoader->setPaths($this->_appTemplateFolder); 143 | $body = $this->_twig->render($this->_callbackName, $bodyOrParams); 144 | } elseif ($bodyParamsType === 'string') { 145 | $body = $bodyOrParams; 146 | } else { 147 | throw new InvalidArgumentException("$bodyOrParams must be 'string' or 'array'. Found: $bodyParamsType."); 148 | } 149 | $this->checkIfFileExistsOfFail($this->_pluginTemplateFolder . $this->_callbackName); 150 | $this->_twigLoader->setPaths($this->_pluginTemplateFolder); 151 | return $this->_twig->render($this->_callbackName, compact('body')); 152 | } 153 | 154 | /** 155 | * Check if a file exists or fail. 156 | * 157 | * @param string $file 158 | * @return void 159 | */ 160 | private function checkIfFileExistsOfFail(string $file): void { 161 | if (!is_file($file)) { 162 | throw new FatalErrorException("File '$file' not found."); 163 | } 164 | } 165 | 166 | } 167 | -------------------------------------------------------------------------------- /src/Table/Option/ChildOptionAbstract.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | 11 | namespace DataTables\Table\Option; 12 | 13 | abstract class ChildOptionAbstract extends OptionAbstract { 14 | 15 | /** 16 | * @var \DataTables\Table\Option\MainOption|null 17 | */ 18 | protected $_mainOption = null; 19 | 20 | /** 21 | * ChildOptionAbstract constructor. 22 | * 23 | * @param \DataTables\Table\Option\MainOption $mainOption 24 | */ 25 | public function __construct(MainOption $mainOption) { 26 | parent::__construct(); 27 | $this->_mainOption = $mainOption; 28 | foreach ($this->_config as $key => $config) { 29 | $this->_setConfig($key, $config, false); 30 | } 31 | foreach ($this->_mustPrint as $key => $mustPrint) { 32 | $this->getMainOption()->setMustPrint($key, $mustPrint); 33 | } 34 | } 35 | 36 | /** 37 | * Return the MainOption class. 38 | * 39 | * @return \DataTables\Table\Option\MainOption; 40 | */ 41 | protected function getMainOption(): MainOption { 42 | return $this->_mainOption; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/Table/Option/MainOption.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | declare(strict_types = 1); 11 | 12 | namespace DataTables\Table\Option; 13 | 14 | use Cake\Core\Configure; 15 | use Cake\Utility\Hash; 16 | use DataTables\Table\Option\Section\ColumnsOption; 17 | use DataTables\Table\Option\Section\FeaturesOption; 18 | use DataTables\Table\Option\Section\OptionsOption; 19 | 20 | /** 21 | * Class Options 22 | * 23 | * @property \DataTables\Table\Option\Section\FeaturesOption $Features 24 | * @property \DataTables\Table\Option\Section\OptionsOption $Options 25 | * @property \DataTables\Table\Option\Section\ColumnsOption $Columns 26 | * @author Allan Carvalho 27 | * @license MIT License https://github.com/allanmcarvalho/cakephp-datatables/blob/master/LICENSE 28 | * @link https://github.com/allanmcarvalho/cakephp-datatables 29 | */ 30 | final class MainOption extends OptionAbstract { 31 | 32 | /** 33 | * @var array 34 | * @inheritDoc 35 | */ 36 | protected $_mustPrint = []; 37 | 38 | /** 39 | * @var array 40 | * @inheritDoc 41 | */ 42 | protected $_config = []; 43 | 44 | /** 45 | * Define if all options will be printed or not. 46 | * 47 | * @var bool 48 | */ 49 | protected $_printAllOptions = false; 50 | 51 | /** 52 | * @inheritDoc 53 | */ 54 | public function __construct() { 55 | parent::__construct(); 56 | $this->Features = new FeaturesOption($this); 57 | $this->Options = new OptionsOption($this); 58 | $this->Columns = new ColumnsOption($this); 59 | } 60 | 61 | /** 62 | * Get if all options will be printed or not. 63 | * 64 | * @return bool 65 | */ 66 | public function isPrintAllOptions(): bool { 67 | return $this->_printAllOptions; 68 | } 69 | 70 | /** 71 | * Tell if a field or a many fields will be printed or not. 72 | * 73 | * @param string|null $field The field that you intent to see or null for all. 74 | * @return string|array|null A value if exists or null. 75 | */ 76 | public function getMustPrint(?string $field = null) { 77 | if (!empty($field)) { 78 | return Hash::get($this->_mustPrint, $field, null); 79 | } 80 | 81 | return $this->_mustPrint; 82 | } 83 | 84 | /** 85 | * Set if a field must be printed or not. 86 | * 87 | * @param string $field The field that will be changed. 88 | * @param bool $must True or false to set if it will printed or not. 89 | * @return \DataTables\Table\Option\MainOption 90 | */ 91 | public function setMustPrint(string $field, bool $must = true): MainOption { 92 | $this->_mustPrint = Hash::insert($this->_mustPrint, $field, $must); 93 | return $this; 94 | } 95 | 96 | /** 97 | * Define if all options will be printed or not. 98 | * 99 | * @param bool $printAllOptions 100 | * @return $this 101 | */ 102 | public function setPrintAllOptions(bool $printAllOptions): self { 103 | $this->_printAllOptions = $printAllOptions; 104 | 105 | return $this; 106 | } 107 | 108 | /** 109 | * Get a config. 110 | * 111 | * @param string|null $field The field that you intent to see or null for all. 112 | * @param string|array|null $default A default value for called config. 113 | * @return mixed A value if exists or null. 114 | */ 115 | public function getConfig(?string $field = null, $default = null) { 116 | return $this->_getConfig($field, $default); 117 | } 118 | 119 | /** 120 | * Set manually a config. 121 | * 122 | * @param string $field The field that will be changed. 123 | * @param mixed $value A value intended to save at config. 124 | * @param bool $mustPrint Set or not the field as 'mustPrint'. 125 | * @return $this 126 | */ 127 | public function setConfig(string $field, $value, bool $mustPrint = true): self { 128 | $this->_setConfig($field, $value, $mustPrint); 129 | 130 | return $this; 131 | } 132 | 133 | /** 134 | * Get the config as json. 135 | * 136 | * @param bool|null $printAllOptions 137 | * @return string 138 | */ 139 | public function getConfigAsJson(?bool $printAllOptions = null): string { 140 | $options = 0; 141 | if (Configure::read('debug') === true) { 142 | $options = JSON_PRETTY_PRINT; 143 | } 144 | return json_encode($this->getConfigAsArray($printAllOptions), $options); 145 | } 146 | 147 | /** 148 | * Get the config as array. 149 | * 150 | * @param bool|null $printAllOptions 151 | * @return array 152 | */ 153 | public function getConfigAsArray(?bool $printAllOptions = null): array { 154 | if ($printAllOptions === true || (empty($printAllOptions) && $this->_printAllOptions === true)) { 155 | return $this->_config; 156 | } 157 | $result = []; 158 | foreach (Hash::flatten($this->_mustPrint) as $key => $config) { 159 | $result = Hash::insert($result, $key, Hash::get($this->_config, $key, null)); 160 | } 161 | return $result; 162 | } 163 | 164 | } 165 | -------------------------------------------------------------------------------- /src/Table/Option/OptionAbstract.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | declare(strict_types = 1); 11 | 12 | namespace DataTables\Table\Option; 13 | 14 | use Cake\Utility\Hash; 15 | 16 | /** 17 | * Class OptionAbstract 18 | * 19 | * @author Allan Carvalho 20 | * @license MIT License https://github.com/allanmcarvalho/cakephp-datatables/blob/master/LICENSE 21 | * @link https://github.com/allanmcarvalho/cakephp-datatables 22 | */ 23 | abstract class OptionAbstract { 24 | 25 | /** 26 | * The options that was set and/or must be printed. 27 | * 28 | * @var array 29 | */ 30 | protected $_mustPrint = []; 31 | 32 | /** 33 | * DataTables Js configs 34 | * 35 | * @var array 36 | */ 37 | protected $_config = []; 38 | 39 | /** 40 | * OptionAbstract constructor. 41 | */ 42 | public function __construct() { 43 | } 44 | 45 | /** 46 | * Get a config. 47 | * 48 | * @param string|null $field The field that you intent to see or null for all. 49 | * @param string|array|null $default A default value for called config. 50 | * @return mixed|void A value if exists or null. 51 | */ 52 | protected function _getConfig(?string $field = null, $default = null) { 53 | if ($this instanceof ChildOptionAbstract) { 54 | return $this->getMainOption()->getConfig($field, $default); 55 | } 56 | if (!empty($field)) { 57 | return Hash::get($this->_config, $field, $default); 58 | } 59 | return $this->_config; 60 | } 61 | 62 | /** 63 | * Set manually a config. 64 | * 65 | * @param string $field The field that will be changed. 66 | * @param mixed $value A value intended to save at config. 67 | * @param bool $mustPrint Set or not the field as 'mustPrint'. 68 | * @return void 69 | */ 70 | protected function _setConfig(string $field, $value, bool $mustPrint = true): void { 71 | if ($this instanceof MainOption) { 72 | $this->_config = Hash::insert($this->_config, $field, $value); 73 | if ($mustPrint === true) { 74 | $this->setMustPrint($field, true); 75 | } 76 | } elseif ($this instanceof ChildOptionAbstract) { 77 | $this->getMainOption()->setConfig($field, $value, $mustPrint); 78 | } 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/Table/Option/Section/ColumnsOption.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | declare(strict_types = 1); 11 | 12 | namespace DataTables\Table\Option\Section; 13 | 14 | use DataTables\Table\Columns; 15 | use DataTables\Table\Option\ChildOptionAbstract; 16 | use DataTables\Table\Option\MainOption; 17 | 18 | /** 19 | * Class ColumnsOption 20 | * 21 | * @author Allan Carvalho 22 | * @license MIT License https://github.com/allanmcarvalho/cakephp-datatables/blob/master/LICENSE 23 | * @link https://github.com/allanmcarvalho/cakephp-datatables 24 | */ 25 | final class ColumnsOption extends ChildOptionAbstract { 26 | 27 | /** 28 | * @var array 29 | * @inheritDoc 30 | */ 31 | protected $_mustPrint = []; 32 | 33 | /** 34 | * @var array 35 | * @inheritDoc 36 | */ 37 | protected $_config = [ 38 | 'columnDefs' => [], 39 | 'columns' => [], 40 | ]; 41 | 42 | /** 43 | * Setter method. 44 | * Set all columns and defColumns options using a Columns class. 45 | * 46 | * @param \DataTables\Table\Columns $columns 47 | * @return \DataTables\Table\Option\MainOption 48 | * @link https://datatables.net/reference/option/ 49 | */ 50 | public function setColumns(Columns $columns): MainOption { 51 | return $this->getMainOption(); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/Table/Option/Section/FeaturesOption.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | declare(strict_types = 1); 11 | 12 | namespace DataTables\Table\Option\Section; 13 | 14 | use Cake\Error\FatalErrorException; 15 | use DataTables\Table\Option\ChildOptionAbstract; 16 | use DataTables\Table\Option\MainOption; 17 | 18 | /** 19 | * Class FeaturesOption 20 | * 21 | * @author Allan Carvalho 22 | * @license MIT License https://github.com/allanmcarvalho/cakephp-datatables/blob/master/LICENSE 23 | * @link https://github.com/allanmcarvalho/cakephp-datatables 24 | */ 25 | final class FeaturesOption extends ChildOptionAbstract { 26 | 27 | /** 28 | * @var array 29 | * @inheritDoc 30 | */ 31 | protected $_mustPrint = [ 32 | 'serverSide' => true, 33 | ]; 34 | 35 | /** 36 | * @var array 37 | * @inheritDoc 38 | */ 39 | protected $_config = [ 40 | 'autoWidth' => true, 41 | 'deferRender' => false, 42 | 'info' => true, 43 | 'lengthChange' => true, 44 | 'ordering' => true, 45 | 'paging' => true, 46 | 'processing' => false, 47 | 'scrollX' => false, 48 | 'scrollY' => null, 49 | 'searching' => true, 50 | 'serverSide' => true, 51 | 'stateSave' => false, 52 | ]; 53 | 54 | /** 55 | * Checker method. 56 | * Enable or disable automatic column width calculation. This can be disabled as an optimisation(it takes a finite 57 | * amount of time to calculate the widths) if the tables widths are passed in using. 58 | * 59 | * @link https://datatables.net/reference/option/autoWidth 60 | * @return bool 61 | */ 62 | public function isAutoWidth(): bool { 63 | return (bool)$this->_getConfig('autoWidth'); 64 | } 65 | 66 | /** 67 | * Setter method. 68 | * Enable or disable automatic column width calculation. This can be disabled as an optimisation(it takes a finite 69 | * amount of time to calculate the widths) if the tables widths are passed in using. 70 | * 71 | * @param bool $autoWidth 72 | * @return \DataTables\Table\Option\MainOption 73 | * @link https://datatables.net/reference/option/autoWidth 74 | */ 75 | public function setAutoWidth(bool $autoWidth): MainOption { 76 | $this->_setConfig('autoWidth', $autoWidth); 77 | 78 | return $this->getMainOption(); 79 | } 80 | 81 | /** 82 | * Checker method. 83 | * By default, when DataTables loads data from an Ajax or Javascript data source (ajax and data respectively) it 84 | * will create all HTML elements needed up-front. When working with large data sets, this operation can take a 85 | * not-insignificant amount of time, particularly in older browsers such as IE6-8. This option allows DataTables to 86 | * create the nodes (rows and cells in the table body) only when they are needed for a draw. 87 | * 88 | * As an example to help illustrate this, if you load a data set with 10,000 rows, but a paging display length of 89 | * only 10 records, rather than create all 10,000 rows, when deferred rendering is enabled, DataTables will create 90 | * only 10. When the end user then sorts, pages or filters the data the rows needed for the next display will be 91 | * created automatically. This effectively spreads the load of creating the rows across the life time of the page. 92 | * 93 | * Note that when enabled, it goes without saying that not all nodes will always be available in the table, so when 94 | * working with API methods such as columns().nodes() you must take this into account. Below shows an example of 95 | * how to use jQuery delegated events to handle such a situation. 96 | * 97 | * @link https://datatables.net/reference/option/deferRender 98 | * @return bool 99 | */ 100 | public function isDeferRender(): bool { 101 | return (bool)$this->_getConfig('deferRender'); 102 | } 103 | 104 | /** 105 | * Setter method. 106 | * By default, when DataTables loads data from an Ajax or Javascript data source (ajax and data respectively) it 107 | * will create all HTML elements needed up-front. When working with large data sets, this operation can take a 108 | * not-insignificant amount of time, particularly in older browsers such as IE6-8. This option allows DataTables to 109 | * create the nodes (rows and cells in the table body) only when they are needed for a draw. 110 | * 111 | * As an example to help illustrate this, if you load a data set with 10,000 rows, but a paging display length of 112 | * only 10 records, rather than create all 10,000 rows, when deferred rendering is enabled, DataTables will create 113 | * only 10. When the end user then sorts, pages or filters the data the rows needed for the next display will be 114 | * created automatically. This effectively spreads the load of creating the rows across the life time of the page. 115 | * 116 | * Note that when enabled, it goes without saying that not all nodes will always be available in the table, so when 117 | * working with API methods such as columns().nodes() you must take this into account. Below shows an example of 118 | * how to use jQuery delegated events to handle such a situation. 119 | * 120 | * @param bool $deferRender 121 | * @return \DataTables\Table\Option\MainOption 122 | * @link https://datatables.net/reference/option/deferRender 123 | */ 124 | public function setDeferRender(bool $deferRender): MainOption { 125 | $this->_setConfig('deferRender', $deferRender); 126 | 127 | return $this->getMainOption(); 128 | } 129 | 130 | /** 131 | * Checker method. 132 | * When this option is enabled, Datatables will show information about the table including information about 133 | * filtered data if that action is being performed. This option allows that feature to be enabled or disabled. 134 | * 135 | * Note that by default the information display is shown below the table on the left, but this can be controlled 136 | * using dom and CSS). 137 | * 138 | * @link https://datatables.net/reference/option/info 139 | * @return bool 140 | */ 141 | public function isInfo(): bool { 142 | return (bool)$this->_getConfig('info'); 143 | } 144 | 145 | /** 146 | * Setter method. 147 | * When this option is enabled, Datatables will show information about the table including information about 148 | * filtered data if that action is being performed. This option allows that feature to be enabled or disabled. 149 | * 150 | * Note that by default the information display is shown below the table on the left, but this can be controlled 151 | * using dom and CSS). 152 | * 153 | * @param bool $info 154 | * @return \DataTables\Table\Option\MainOption 155 | * @link https://datatables.net/reference/option/info 156 | */ 157 | public function setInfo(bool $info): MainOption { 158 | $this->_setConfig('info', $info); 159 | 160 | return $this->getMainOption(); 161 | } 162 | 163 | /** 164 | * Checker method. 165 | * When pagination is enabled, this option will control the display of an option for the end user to change the 166 | * number of records to be shown per page. The options shown in the list are controlled by the lengthMenu 167 | * configuration option. 168 | * 169 | * Note that by default the control is shown at the top left of the table. That can be controlled using dom and 170 | * CSS. 171 | * 172 | * If this option is disabled (false) the length change input control is removed - although the page.len() method 173 | * can still be used if you wish to programmatically change the page size and pageLength can be used to specify the 174 | * initial page length. Paging itself is not affected. 175 | * 176 | * Additionally, if pagination is disabled using the paging option, this option is automatically disabled since it 177 | * has no relevance when there is no pagination. 178 | * 179 | * @link https://datatables.net/reference/option/lengthChange 180 | * @return bool 181 | */ 182 | public function isLengthChange(): bool { 183 | return (bool)$this->_getConfig('lengthChange'); 184 | } 185 | 186 | /** 187 | * Setter method. 188 | * When pagination is enabled, this option will control the display of an option for the end user to change the 189 | * number of records to be shown per page. The options shown in the list are controlled by the lengthMenu 190 | * configuration option. 191 | * 192 | * Note that by default the control is shown at the top left of the table. That can be controlled using dom and 193 | * CSS. 194 | * 195 | * If this option is disabled (false) the length change input control is removed - although the page.len() method 196 | * can still be used if you wish to programmatically change the page size and pageLength can be used to specify the 197 | * initial page length. Paging itself is not affected. 198 | * 199 | * Additionally, if pagination is disabled using the paging option, this option is automatically disabled since it 200 | * has no relevance when there is no pagination. 201 | * 202 | * @param bool $lengthChange 203 | * @return \DataTables\Table\Option\MainOption 204 | * @link https://datatables.net/reference/option/lengthChange 205 | */ 206 | public function setLengthChange(bool $lengthChange): MainOption { 207 | $this->_setConfig('lengthChange', $lengthChange); 208 | 209 | return $this->getMainOption(); 210 | } 211 | 212 | /** 213 | * Checker method. 214 | * Enable or disable ordering of columns - it is as simple as that! DataTables, by default, allows end users to 215 | * click on the header cell for each column, ordering the table by the data in that column. The ability to order 216 | * data can be disabled using this option. 217 | * 218 | * Note that the ability to add or remove sorting of individual columns can be disabled by the columns.orderable 219 | * option for each column. This parameter is a global option - when disabled, there are no sorting actions applied 220 | * by DataTables at all. 221 | * 222 | * @link https://datatables.net/reference/option/ordering 223 | * @return bool 224 | */ 225 | public function isOrdering(): bool { 226 | return (bool)$this->_getConfig('ordering'); 227 | } 228 | 229 | /** 230 | * Setter method. 231 | * Enable or disable ordering of columns - it is as simple as that! DataTables, by default, allows end users to 232 | * click on the header cell for each column, ordering the table by the data in that column. The ability to order 233 | * data can be disabled using this option. 234 | * 235 | * Note that the ability to add or remove sorting of individual columns can be disabled by the columns.orderable 236 | * option for each column. This parameter is a global option - when disabled, there are no sorting actions applied 237 | * by DataTables at all. 238 | * 239 | * @param bool $ordering 240 | * @return \DataTables\Table\Option\MainOption 241 | * @link https://datatables.net/reference/option/ordering 242 | */ 243 | public function setOrdering(bool $ordering): MainOption { 244 | $this->_setConfig('ordering', $ordering); 245 | 246 | return $this->getMainOption(); 247 | } 248 | 249 | /** 250 | * Checker method. 251 | * DataTables can split the rows in tables into individual pages, which is an efficient method of showing a large 252 | * number of records in a small space. The end user is provided with controls to request the display of different 253 | * data as the navigate through the data. This feature is enabled by default, but if you wish to disable it, you 254 | * may do so with this parameter. 255 | * 256 | * @link https://datatables.net/reference/option/paging 257 | * @return bool 258 | */ 259 | public function isPaging(): bool { 260 | return (bool)$this->_getConfig('paging'); 261 | } 262 | 263 | /** 264 | * Setter method. 265 | * DataTables can split the rows in tables into individual pages, which is an efficient method of showing a large 266 | * number of records in a small space. The end user is provided with controls to request the display of different 267 | * data as the navigate through the data. This feature is enabled by default, but if you wish to disable it, you 268 | * may do so with this parameter. 269 | * 270 | * @param bool $paging 271 | * @return \DataTables\Table\Option\MainOption 272 | * @link https://datatables.net/reference/option/paging 273 | */ 274 | public function setPaging(bool $paging): MainOption { 275 | $this->_setConfig('paging', $paging); 276 | 277 | return $this->getMainOption(); 278 | } 279 | 280 | /** 281 | * Checker method. 282 | * Enable or disable the display of a 'processing' indicator when the table is being processed (e.g. a sort). This 283 | * is particularly useful for tables with large amounts of data where it can take a noticeable amount of time to 284 | * sort the entries. 285 | * 286 | * @link https://datatables.net/reference/option/processing 287 | * @return bool 288 | */ 289 | public function isProcessing(): bool { 290 | return (bool)$this->_getConfig('processing'); 291 | } 292 | 293 | /** 294 | * Setter method. 295 | * Enable or disable the display of a 'processing' indicator when the table is being processed (e.g. a sort). This 296 | * is particularly useful for tables with large amounts of data where it can take a noticeable amount of time to 297 | * sort the entries. 298 | * 299 | * @param bool $processing 300 | * @return \DataTables\Table\Option\MainOption 301 | * @link https://datatables.net/reference/option/processing 302 | */ 303 | public function setProcessing(bool $processing): MainOption { 304 | $this->_setConfig('processing', $processing); 305 | 306 | return $this->getMainOption(); 307 | } 308 | 309 | /** 310 | * Checker method. 311 | * Enable horizontal scrolling. When a table is too wide to fit into a certain layout, or you have a large number 312 | * of columns in the table, you can enable horizontal (x) scrolling to show the table in a viewport, which can be 313 | * scrolled. 314 | * 315 | * This property can be true which will allow the table to scroll horizontally when needed (recommended), or any 316 | * CSS unit, or a number (in which case it will be treated as a pixel measurement). 317 | * 318 | * @link https://datatables.net/reference/option/scrollX 319 | * @return bool 320 | */ 321 | public function isScrollX(): bool { 322 | return (bool)$this->_getConfig('scrollX'); 323 | } 324 | 325 | /** 326 | * Setter method. 327 | * Enable horizontal scrolling. When a table is too wide to fit into a certain layout, or you have a large number 328 | * of columns in the table, you can enable horizontal (x) scrolling to show the table in a viewport, which can be 329 | * scrolled. 330 | * 331 | * This property can be true which will allow the table to scroll horizontally when needed (recommended), or any 332 | * CSS unit, or a number (in which case it will be treated as a pixel measurement). 333 | * 334 | * @param bool $scrollX 335 | * @return \DataTables\Table\Option\MainOption 336 | * @link https://datatables.net/reference/option/scrollX 337 | */ 338 | public function setScrollX(bool $scrollX): MainOption { 339 | $this->_setConfig('scrollX', $scrollX); 340 | 341 | return $this->getMainOption(); 342 | } 343 | 344 | /** 345 | * Getter method. 346 | * Enable vertical scrolling. Vertical scrolling will constrain the DataTable to the given height, and enable 347 | * scrolling for any data which overflows the current viewport. This can be used as an alternative to paging to 348 | * display a lot of data in a small area (although paging and scrolling can both be enabled at the same time if 349 | * desired). 350 | * 351 | * The value given here can be any CSS unit, or a number (in which case it will be treated as a pixel measurement) 352 | * and is applied to the table body (i.e. it does not take into account the header or footer height directly). 353 | * 354 | * @link https://datatables.net/reference/option/scrollY 355 | * @return string 356 | */ 357 | public function getScrollY(): ?string { 358 | return (string)$this->_getConfig('scrollY'); 359 | } 360 | 361 | /** 362 | * Setter method. 363 | * Enable vertical scrolling. Vertical scrolling will constrain the DataTable to the given height, and enable 364 | * scrolling for any data which overflows the current viewport. This can be used as an alternative to paging to 365 | * display a lot of data in a small area (although paging and scrolling can both be enabled at the same time if 366 | * desired). 367 | * 368 | * The value given here can be any CSS unit, or a number (in which case it will be treated as a pixel measurement) 369 | * and is applied to the table body (i.e. it does not take into account the header or footer height directly). 370 | * 371 | * @param string $scrollY 372 | * @return \DataTables\Table\Option\MainOption 373 | * @link https://datatables.net/reference/option/scrollY 374 | */ 375 | public function setScrollY(?string $scrollY): MainOption { 376 | $this->_setConfig('scrollY', $scrollY); 377 | 378 | return $this->getMainOption(); 379 | } 380 | 381 | /** 382 | * Checker method. 383 | * This option allows the search abilities of DataTables to be enabled or disabled. Searching in DataTables is 384 | * "smart" in that it allows the end user to input multiple words (space separated) and will match a row containing 385 | * those words, even if not in the order that was specified (this allow matching across multiple columns). 386 | * 387 | * Please be aware that technically the search in DataTables is actually a filter, since it is subtractive, 388 | * removing data from the data set as the input becomes more complex. It is named "search" here, and else where in 389 | * the DataTables API for consistency and to ensure there are no conflicts with other methods of a similar name 390 | * (specific the filter() API method). 391 | * 392 | * Note that if you wish to use the search abilities of DataTables this must remain true - to remove the default 393 | * search input box whilst retaining searching abilities (for example you might use the search() method), use the 394 | * dom option. 395 | * 396 | * @link https://datatables.net/reference/option/searching 397 | * @return bool 398 | */ 399 | public function isSearching(): bool { 400 | return (bool)$this->_getConfig('searching'); 401 | } 402 | 403 | /** 404 | * Setter method. 405 | * This option allows the search abilities of DataTables to be enabled or disabled. Searching in DataTables is 406 | * "smart" in that it allows the end user to input multiple words (space separated) and will match a row containing 407 | * those words, even if not in the order that was specified (this allow matching across multiple columns). 408 | * 409 | * Please be aware that technically the search in DataTables is actually a filter, since it is subtractive, 410 | * removing data from the data set as the input becomes more complex. It is named "search" here, and else where in 411 | * the DataTables API for consistency and to ensure there are no conflicts with other methods of a similar name 412 | * (specific the filter() API method). 413 | * 414 | * Note that if you wish to use the search abilities of DataTables this must remain true - to remove the default 415 | * search input box whilst retaining searching abilities (for example you might use the search() method), use the 416 | * dom option. 417 | * 418 | * @param bool $searching 419 | * @return \DataTables\Table\Option\MainOption 420 | * @link https://datatables.net/reference/option/searching 421 | */ 422 | public function setSearching(bool $searching): MainOption { 423 | $this->_setConfig('searching', $searching); 424 | 425 | return $this->getMainOption(); 426 | } 427 | 428 | /** 429 | * Checker method. 430 | * DataTables has two fundamental modes of operation: 431 | * - Client-side processing - where filtering, paging and sorting calculations are all performed in the 432 | * web-browser. 433 | * - Server-side processing - where filtering, paging and sorting calculations are all performed by a server. 434 | * 435 | * By default DataTables operates in client-side processing mode, but can be switched to server-side processing 436 | * mode using this option. Server-side processing is useful when working with large data sets (typically >50'000 437 | * records) as it means a database engine can be used to perform the sorting etc calculations - operations that 438 | * modern database engines are highly optimised for, allowing use of DataTables with massive data sets (millions 439 | * of rows). 440 | * 441 | * When operating in server-side processing mode, DataTables will send parameters to the server indicating what 442 | * data it needs (what page, what filters are applied etc), and also expects certain parameters back in order that 443 | * it has all the information required to display the table. The client-server communication protocol DataTables 444 | * uses is detailed in the DataTables documentation. 445 | * 446 | * @link https://datatables.net/reference/option/serverSide 447 | * @return bool 448 | */ 449 | public function isServerSide(): bool { 450 | return (bool)$this->_getConfig('serverSide'); 451 | } 452 | 453 | /** 454 | * Setter method. 455 | * DataTables has two fundamental modes of operation: 456 | * - Client-side processing - where filtering, paging and sorting calculations are all performed in the 457 | * web-browser. 458 | * - Server-side processing - where filtering, paging and sorting calculations are all performed by a server. 459 | * 460 | * By default DataTables operates in client-side processing mode, but can be switched to server-side processing 461 | * mode using this option. Server-side processing is useful when working with large data sets (typically >50'000 462 | * records) as it means a database engine can be used to perform the sorting etc calculations - operations that 463 | * modern database engines are highly optimised for, allowing use of DataTables with massive data sets (millions 464 | * of rows). 465 | * 466 | * When operating in server-side processing mode, DataTables will send parameters to the server indicating what 467 | * data it needs (what page, what filters are applied etc), and also expects certain parameters back in order that 468 | * it has all the information required to display the table. The client-server communication protocol DataTables 469 | * uses is detailed in the DataTables documentation. 470 | * 471 | * @param bool $serverSide 472 | * @return \DataTables\Table\Option\MainOption 473 | * @link https://datatables.net/reference/option/serverSide 474 | */ 475 | public function setServerSide(bool $serverSide): MainOption { 476 | if ($serverSide === false) { 477 | throw new FatalErrorException("By the plugin business rule, you can't change this option."); 478 | } 479 | $this->_setConfig('serverSide', $serverSide); 480 | 481 | return $this->getMainOption(); 482 | } 483 | 484 | /** 485 | * Checker method. 486 | * Enable or disable state saving. When enabled aDataTables will store state information such as pagination 487 | * position, display length, filtering and sorting. When the end user reloads the page the table's state will be 488 | * altered to match what they had previously set up. 489 | * 490 | * Data storage for the state information in the browser is performed by use of the localStorage or sessionStorage 491 | * HTML5 APIs. The stateDuration indicated to DataTables which API should be used (localStorage: 0 or greater, or 492 | * sessionStorage: -1). 493 | * 494 | * To be able to uniquely identify each table's state data, information is stored using a combination of the 495 | * table's DOM id and the current page's pathname. If the table's id changes, or the page URL changes, the state 496 | * information will be lost. 497 | * 498 | * Please note that the use of the HTML5 APIs for data storage means that the built in state saving option will not 499 | * work with IE6/7 as these browsers do not support these APIs. Alternative options of using cookies or saving the 500 | * state on the server through Ajax can be used through the stateSaveCallback and stateLoadCallback options. 501 | * 502 | * @link https://datatables.net/reference/option/stateSave 503 | * @return bool 504 | */ 505 | public function isStateSave(): bool { 506 | return (bool)$this->_getConfig('stateSave'); 507 | } 508 | 509 | /** 510 | * Setter method. 511 | * Enable or disable state saving. When enabled aDataTables will store state information such as pagination 512 | * position, display length, filtering and sorting. When the end user reloads the page the table's state will be 513 | * altered to match what they had previously set up. 514 | * 515 | * Data storage for the state information in the browser is performed by use of the localStorage or sessionStorage 516 | * HTML5 APIs. The stateDuration indicated to DataTables which API should be used (localStorage: 0 or greater, or 517 | * sessionStorage: -1). 518 | * 519 | * To be able to uniquely identify each table's state data, information is stored using a combination of the 520 | * table's DOM id and the current page's pathname. If the table's id changes, or the page URL changes, the state 521 | * information will be lost. 522 | * 523 | * Please note that the use of the HTML5 APIs for data storage means that the built in state saving option will not 524 | * work with IE6/7 as these browsers do not support these APIs. Alternative options of using cookies or saving the 525 | * state on the server through Ajax can be used through the stateSaveCallback and stateLoadCallback options. 526 | * 527 | * @param bool $stateSave 528 | * @return \DataTables\Table\Option\MainOption 529 | * @link https://datatables.net/reference/option/stateSave 530 | */ 531 | public function setStateSave(bool $stateSave): MainOption { 532 | $this->_setConfig('stateSave', $stateSave); 533 | 534 | return $this->getMainOption(); 535 | } 536 | 537 | } 538 | -------------------------------------------------------------------------------- /src/Table/QueryBaseState.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | declare(strict_types = 1); 11 | 12 | namespace DataTables\Table; 13 | 14 | /** 15 | * Class QueryBaseState 16 | * 17 | * @author Allan Carvalho 18 | * @license MIT License https://github.com/allanmcarvalho/cakephp-datatables/blob/master/LICENSE 19 | * @link https://github.com/allanmcarvalho/cakephp-datatables 20 | */ 21 | final class QueryBaseState { 22 | 23 | /** 24 | * @var array 25 | */ 26 | private $_containItems = []; 27 | 28 | /** 29 | * @var array 30 | */ 31 | private $_selectItems = []; 32 | 33 | /** 34 | * @var array 35 | */ 36 | private $_selectAllExceptItems = []; 37 | 38 | /** 39 | * @var array 40 | */ 41 | private $_leftJoinWithItems = []; 42 | 43 | /** 44 | * @var array 45 | */ 46 | private $_innerJoinWithItems = []; 47 | 48 | /** 49 | * @var array 50 | */ 51 | private $_notMatchingItems = []; 52 | 53 | /** 54 | * @var array 55 | */ 56 | private $_orderAscItems = []; 57 | 58 | /** 59 | * @var array 60 | */ 61 | private $_orderDescItems = []; 62 | 63 | /** 64 | * @var array 65 | */ 66 | private $_whereItems = []; 67 | 68 | /** 69 | * @var array 70 | */ 71 | private $_whereInListItems = []; 72 | 73 | /** 74 | * @var array 75 | */ 76 | private $_whereNotNullItems = []; 77 | 78 | /** 79 | * @var array 80 | */ 81 | private $_whereNotInListItems = []; 82 | 83 | /** 84 | * @var array 85 | */ 86 | private $_whereNullItems = []; 87 | 88 | /** 89 | * @var array 90 | */ 91 | private $_andWhereItems = []; 92 | 93 | /** 94 | * @var array 95 | */ 96 | private $_urlWhereItems = []; 97 | 98 | /** 99 | * Return 100 | * 101 | * @return array 102 | */ 103 | public function getArray(): array { 104 | return [ 105 | 'contain' => $this->_containItems, 106 | 'select' => $this->_selectItems, 107 | 'selectAllExcept' => $this->_selectAllExceptItems, 108 | 'leftJoinWith' => $this->_leftJoinWithItems, 109 | 'innerJoinWith' => $this->_innerJoinWithItems, 110 | 'notMatching' => $this->_notMatchingItems, 111 | 'orderAsc' => $this->_orderAscItems, 112 | 'orderDesc' => $this->_orderDescItems, 113 | 'where' => $this->_whereItems, 114 | 'whereInList' => $this->_whereInListItems, 115 | 'whereNotNull' => $this->_whereNotNullItems, 116 | 'whereNotInList' => $this->_whereNotInListItems, 117 | 'whereNull' => $this->_whereNullItems, 118 | 'andWhere' => $this->_andWhereItems, 119 | 'urlWhere' => $this->_urlWhereItems, 120 | ]; 121 | } 122 | 123 | /** 124 | * @param array|string $associations List of table aliases to be queried. 125 | * @param callable|bool $override The query builder for the association, or 126 | * if associations is an array, a bool on whether to override previous list 127 | * with the one passed 128 | * defaults to merging previous list with the new one. 129 | * @return $this 130 | * @see \Cake\ORM\Query::contain() 131 | */ 132 | public function contain($associations, $override = false): self { 133 | $queryBuilder = null; 134 | if ($override === true) { 135 | $this->_containItems = []; 136 | } 137 | $queryBuilder = null; 138 | if (is_callable($override)) { 139 | $queryBuilder = $override; 140 | } 141 | if ($associations) { 142 | $this->_containItems[] = [ 143 | 'associations' => $associations, 144 | 'queryBuilder' => $queryBuilder, 145 | ]; 146 | } 147 | return $this; 148 | } 149 | 150 | /** 151 | * @param array|\Cake\Database\ExpressionInterface|callable|string|\Cake\ORM\Table|\Cake\ORM\Association $fields Fields 152 | * to be added to the list. 153 | * @param bool $overwrite whether to reset fields with passed list or not 154 | * @return $this 155 | * @see \Cake\ORM\Query::select() 156 | */ 157 | public function select($fields, bool $overwrite = false): self { 158 | if ($overwrite === true) { 159 | $this->_selectItems = []; 160 | } 161 | $this->_selectItems[] = [ 162 | 'fields' => $fields, 163 | ]; 164 | return $this; 165 | } 166 | 167 | /** 168 | * @param \Cake\ORM\Table|\Cake\ORM\Association $table The table to use to get an array of columns 169 | * @param string[] $excludedFields The un-aliased column names you do not want selected from $table 170 | * @param bool $overwrite Whether to reset/remove previous selected fields 171 | * @return $this 172 | * @throws \InvalidArgumentException If Association|Table is not passed in first argument 173 | * @see \Cake\ORM\Query::selectAllExcept() 174 | */ 175 | public function selectAllExcept($table, array $excludedFields, bool $overwrite = false): self { 176 | if ($overwrite === true) { 177 | $this->_selectAllExceptItems = []; 178 | } 179 | $this->_selectAllExceptItems[] = [ 180 | 'table' => $table, 181 | 'excludedFields' => $excludedFields, 182 | ]; 183 | return $this; 184 | } 185 | 186 | /** 187 | * @param string $assoc The association to join with 188 | * @param callable|null $builder a function that will receive a pre-made query object 189 | * that can be used to add custom conditions or selecting some fields 190 | * @return $this 191 | * @see \Cake\ORM\Query::leftJoinWith() 192 | */ 193 | public function leftJoinWith(string $assoc, ?callable $builder = null): self { 194 | $this->_leftJoinWithItems[] = [ 195 | 'assoc' => $assoc, 196 | 'builder' => $builder, 197 | ]; 198 | return $this; 199 | } 200 | 201 | /** 202 | * @param string $assoc The association to join with 203 | * @param callable|null $builder a function that will receive a pre-made query object 204 | * that can be used to add custom conditions or selecting some fields 205 | * @return $this 206 | * @see \Cake\ORM\Query::innerJoinWith() 207 | */ 208 | public function innerJoinWith(string $assoc, ?callable $builder = null): self { 209 | $this->_innerJoinWithItems[] = [ 210 | 'assoc' => $assoc, 211 | 'builder' => $builder, 212 | ]; 213 | return $this; 214 | } 215 | 216 | /** 217 | * @param string $assoc The association to filter by 218 | * @param callable|null $builder a function that will receive a pre-made query object 219 | * that can be used to add custom conditions or selecting some fields 220 | * @return $this 221 | * @see \Cake\ORM\Query::notMatching() 222 | */ 223 | public function notMatching(string $assoc, ?callable $builder = null): self { 224 | $this->_notMatchingItems[] = [ 225 | 'assoc' => $assoc, 226 | 'builder' => $builder, 227 | ]; 228 | return $this; 229 | } 230 | 231 | /** 232 | * @param string|\Cake\Database\Expression\QueryExpression $field The field to order on. 233 | * @param bool $overwrite Whether or not to reset the order clauses. 234 | * @return $this 235 | * @see \Cake\ORM\Query::orderAsc() 236 | */ 237 | public function orderAsc($field, bool $overwrite = false): self { 238 | if ($overwrite === true) { 239 | $this->_orderAscItems = []; 240 | } 241 | $this->_orderAscItems[] = [ 242 | 'field' => $field, 243 | ]; 244 | return $this; 245 | } 246 | 247 | /** 248 | * @param string|\Cake\Database\Expression\QueryExpression $field The field to order on. 249 | * @param bool $overwrite Whether or not to reset the order clauses. 250 | * @return $this 251 | * @see \Cake\ORM\Query::orderDesc() 252 | */ 253 | public function orderDesc($field, bool $overwrite = false): self { 254 | if ($overwrite === true) { 255 | $this->_orderDescItems = []; 256 | } 257 | $this->_orderDescItems[] = [ 258 | 'field' => $field, 259 | ]; 260 | return $this; 261 | } 262 | 263 | /** 264 | * @param string|array|\Cake\Database\ExpressionInterface|\Closure|null $conditions The conditions to filter on. 265 | * @param array $types associative array of type names used to bind values to query 266 | * @param bool $overwrite whether to reset conditions with passed list or not 267 | * @return $this 268 | * @see \Cake\ORM\Query::where() 269 | */ 270 | public function where($conditions = null, array $types = [], bool $overwrite = false): self { 271 | if ($overwrite === true) { 272 | $this->_whereItems = []; 273 | } 274 | $this->_whereItems[] = [ 275 | 'conditions' => $conditions, 276 | 'types' => $types, 277 | ]; 278 | return $this; 279 | } 280 | 281 | /** 282 | * @param string $field Field 283 | * @param array $values Array of values 284 | * @param array $options Options 285 | * @return $this 286 | * @see \Cake\ORM\Query::whereInList() 287 | */ 288 | public function whereInList(string $field, array $values, array $options = []): self { 289 | $this->_whereInListItems[] = [ 290 | 'field' => $field, 291 | 'values' => $values, 292 | 'options' => $options, 293 | ]; 294 | return $this; 295 | } 296 | 297 | /** 298 | * @param array|string|\Cake\Database\ExpressionInterface $fields A single field or expressions or a list of them 299 | * that should be not null. 300 | * @return $this 301 | * @see \Cake\ORM\Query::whereNotNull() 302 | */ 303 | public function whereNotNull($fields): self { 304 | $this->_whereNotNullItems[] = [ 305 | 'fields' => $fields, 306 | ]; 307 | return $this; 308 | } 309 | 310 | /** 311 | * @param string $field Field 312 | * @param array $values Array of values 313 | * @param array $options Options 314 | * @return $this 315 | * @see \Cake\ORM\Query::whereNotInList() 316 | */ 317 | public function whereNotInList(string $field, array $values, array $options = []): self { 318 | $this->_whereNotInListItems[] = [ 319 | 'field' => $field, 320 | 'values' => $values, 321 | 'options' => $options, 322 | ]; 323 | return $this; 324 | } 325 | 326 | /** 327 | * @param array|string|\Cake\Database\ExpressionInterface $fields A single field or expressions or a list of them 328 | * that should be null. 329 | * @return $this 330 | * @see \Cake\ORM\Query::whereNull() 331 | */ 332 | public function whereNull($fields): self { 333 | $this->_whereNullItems[] = [ 334 | 'fields' => $fields, 335 | ]; 336 | return $this; 337 | } 338 | 339 | /** 340 | * @param string|array|\Cake\Database\ExpressionInterface|\Closure $conditions The conditions to add with AND. 341 | * @param array $types associative array of type names used to bind values to query 342 | * @return $this 343 | * @see \Cake\ORM\Query::andWhere() 344 | */ 345 | public function andWhere($conditions, array $types = []): self { 346 | $this->_andWhereItems[] = [ 347 | 'conditions' => $conditions, 348 | 'types' => $types, 349 | ]; 350 | return $this; 351 | } 352 | 353 | /** 354 | * Add a condition for a specific URL 355 | * 356 | * @param string|array|\Cake\Database\ExpressionInterface|\Closure|null $conditions The conditions to filter on. 357 | * @param string|array|\Psr\Http\Message\UriInterface|null $url An array specifying any of the following: 358 | * @return $this 359 | */ 360 | public function urlWhere($conditions, $url): self { 361 | $this->_urlWhereItems[] = [ 362 | 'conditions' => $conditions, 363 | 'url' => $url, 364 | ]; 365 | return $this; 366 | } 367 | 368 | } 369 | -------------------------------------------------------------------------------- /src/Table/Tables.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | declare(strict_types = 1); 11 | 12 | namespace DataTables\Table; 13 | 14 | use Cake\Error\FatalErrorException; 15 | use Cake\ORM\Table; 16 | use Cake\ORM\TableRegistry; 17 | 18 | /** 19 | * Class Tables 20 | * 21 | * @author Allan Carvalho 22 | * @license MIT License https://github.com/allanmcarvalho/cakephp-datatables/blob/master/LICENSE 23 | * @link https://github.com/allanmcarvalho/cakephp-datatables 24 | */ 25 | abstract class Tables { 26 | 27 | /** 28 | * The database table name that will be used to load the DataTables ORM table. 29 | * 30 | * @var string 31 | */ 32 | protected $_ormTableName; 33 | 34 | /** 35 | * @var \Cake\ORM\Table 36 | */ 37 | private $_ormTable; 38 | 39 | public function __construct() { 40 | $className = get_called_class(); 41 | $classShortName = explode('\\', get_called_class()); 42 | $classShortName = array_pop($classShortName); 43 | if (substr($classShortName, -6, 6) !== 'Tables') { 44 | throw new FatalErrorException("The class '$className' must have the name ending with 'Tables'"); 45 | } 46 | if (empty($this->_ormTableName)) { 47 | $this->_ormTableName = substr_replace($classShortName, '', -6, 6); 48 | } 49 | } 50 | 51 | /** 52 | * @return \Cake\ORM\Table 53 | */ 54 | public function getOrmTable(): Table { 55 | return TableRegistry::getTableLocator()->get($this->_ormTableName); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/Tools/Builder.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | declare(strict_types = 1); 11 | 12 | namespace DataTables\Tools; 13 | 14 | use Cake\Core\Configure; 15 | use Cake\Error\FatalErrorException; 16 | use Cake\Utility\Inflector; 17 | use DataTables\StorageEngine\StorageEngineInterface; 18 | use DataTables\Table\Columns; 19 | use DataTables\Table\ConfigBundle; 20 | use DataTables\Table\Option\MainOption; 21 | use DataTables\Table\QueryBaseState; 22 | use DataTables\Table\Tables; 23 | use InvalidArgumentException; 24 | 25 | /** 26 | * Class Tools 27 | * 28 | * @author Allan Carvalho 29 | * @license MIT License https://github.com/allanmcarvalho/cakephp-datatables/blob/master/LICENSE 30 | * @link https://github.com/allanmcarvalho/cakephp-datatables 31 | */ 32 | class Builder { 33 | 34 | /** 35 | * Storage a instance of object. 36 | * 37 | * @var self 38 | */ 39 | public static $instance; 40 | 41 | /** 42 | * Return a instance of builder object. 43 | * 44 | * @return \DataTables\Tools\Builder 45 | */ 46 | public static function getInstance(): Builder { 47 | if (static::$instance === null) { 48 | static::$instance = new self(); 49 | } 50 | return static::$instance; 51 | } 52 | 53 | /** 54 | * Get or build a ConfigBundle. 55 | * 56 | * @param string $tableAndConfig A Tables class plus config method that you want to render concatenated by '::'. Eg.: 'Foo::main'. 57 | * @param bool $cache If true will try to get from cache. 58 | * @return \DataTables\Table\ConfigBundle 59 | * @throws \ReflectionException 60 | */ 61 | public function getConfigBundle(string $tableAndConfig, bool $cache = true): ConfigBundle { 62 | $exploded = explode('::', $tableAndConfig); 63 | if (count($exploded) !== 2) { 64 | throw new InvalidArgumentException('Table param must be a concatenation of Tables class and config. Eg.: Foo::method.'); 65 | } 66 | $storageEngine = $this->getStorageEngine(); 67 | $tablesClass = $exploded[0]; 68 | $configMethod = $exploded[1]; 69 | $tablesClassWithNameSpace = Configure::read('App.namespace') . '\\DataTables\\Tables\\' . $tablesClass . 'Tables'; 70 | $md5 = Functions::getInstance()->getClassAndVersionMd5($tablesClassWithNameSpace); 71 | $cacheKey = Inflector::underscore(str_replace('::', '_', $tableAndConfig)); 72 | 73 | $configBundle = null; 74 | if ($cache === true && $storageEngine->exists($cacheKey)) { 75 | /** @var \DataTables\Table\ConfigBundle $configBundle */ 76 | $configBundle = $storageEngine->read($cacheKey); 77 | } 78 | if (empty($configBundle) && !$configBundle instanceof ConfigBundle) { 79 | $configBundle = $this->buildConfigBundle($tablesClassWithNameSpace, $configMethod, $md5); 80 | } 81 | if ($cache && !$storageEngine->save($cacheKey, $configBundle)) { 82 | throw new FatalErrorException('Unable to save the ConfigBundle cache.'); 83 | } 84 | 85 | return $configBundle; 86 | } 87 | 88 | /** 89 | * Build a ConfigBundle class 90 | * 91 | * @param string $tablesClassWithNameSpace Tables class with full namespace. 92 | * @param string $configMethod The method that will be called. 93 | * @param string $md5 Md5 verifier used in the cache. 94 | * @return \DataTables\Table\ConfigBundle 95 | */ 96 | public function buildConfigBundle(string $tablesClassWithNameSpace, string $configMethod, string $md5): ConfigBundle { 97 | $tables = static::getInstance()->buildTables($tablesClassWithNameSpace, $configMethod); 98 | $queryBaseState = static::getInstance()->buildQueryBaseState($tables); 99 | $columns = static::getInstance()->buildColumns($tables); 100 | $options = static::getInstance()->buildOptions($tables); 101 | $configBundle = new ConfigBundle($md5, $queryBaseState, $columns, $options); 102 | $tables->{$configMethod . 'Config'}($configBundle); 103 | return $configBundle; 104 | } 105 | 106 | /** 107 | * Get the Tables class. 108 | * 109 | * @param string $tablesClassWithNameSpace Tables class with full namespace. 110 | * @param string $configMethod The method that will be called. 111 | * @return \DataTables\Table\Tables 112 | */ 113 | public function buildTables(string $tablesClassWithNameSpace, string $configMethod): Tables { 114 | /** @var \DataTables\Table\Tables $tables */ 115 | $tables = new $tablesClassWithNameSpace(); 116 | if (empty($tables)) { 117 | throw new FatalErrorException("Tables class '$tablesClassWithNameSpace' not found."); 118 | } 119 | if (!method_exists($tables, $configMethod . 'Config')) { 120 | throw new FatalErrorException("Config method '{$configMethod}Config' don't exist in '$tablesClassWithNameSpace'."); 121 | } 122 | 123 | return $tables; 124 | } 125 | 126 | /** 127 | * Get the QueryBaseState class used in the DataTables table. 128 | * 129 | * @param \DataTables\Table\Tables $table Tables class instance. 130 | * @return \DataTables\Table\QueryBaseState 131 | */ 132 | private function buildQueryBaseState(Tables $table) { 133 | return new QueryBaseState(); 134 | } 135 | 136 | /** 137 | * Get the Columns class used in the DataTables table. 138 | * 139 | * @param \DataTables\Table\Tables $table Tables class instance. 140 | * @return \DataTables\Table\Columns 141 | */ 142 | private function buildColumns(Tables $table): Columns { 143 | return new Columns($table); 144 | } 145 | 146 | /** 147 | * Get the JsOptions class used in the DataTables table. 148 | * 149 | * @param \DataTables\Table\Tables $table Tables class instance. 150 | * @return \DataTables\Table\Option\MainOption 151 | */ 152 | private function buildOptions(Tables $table): MainOption { 153 | return new MainOption(); 154 | } 155 | 156 | /** 157 | * Get the configured storage engine. 158 | * 159 | * @return \DataTables\StorageEngine\StorageEngineInterface 160 | */ 161 | public function getStorageEngine(): StorageEngineInterface { 162 | $class = Configure::read('DataTables.StorageEngine.class'); 163 | return new $class(); 164 | } 165 | 166 | } 167 | -------------------------------------------------------------------------------- /src/Tools/Functions.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | declare(strict_types = 1); 11 | 12 | namespace DataTables\Tools; 13 | 14 | use Cake\Routing\Router; 15 | use Cake\Utility\Hash; 16 | use Cake\Utility\Inflector; 17 | use InvalidArgumentException; 18 | use ReflectionClass; 19 | 20 | /** 21 | * Class Functions 22 | * 23 | * @author Allan Carvalho 24 | * @license MIT License https://github.com/allanmcarvalho/cakephp-datatables/blob/master/LICENSE 25 | * @link https://github.com/allanmcarvalho/cakephp-datatables 26 | */ 27 | class Functions { 28 | 29 | /** 30 | * Storage a instance of object. 31 | * 32 | * @var self 33 | */ 34 | public static $instance; 35 | 36 | /** 37 | * Return a instance of builder object. 38 | * 39 | * @return \DataTables\Tools\Functions 40 | */ 41 | public static function getInstance(): Functions { 42 | if (static::$instance === null) { 43 | static::$instance = new self(); 44 | } 45 | return static::$instance; 46 | } 47 | 48 | /** 49 | * Get first key of array. 50 | * 51 | * @param array $array 52 | * @return int|string|null 53 | */ 54 | public function arrayKeyFirst(array $array){ 55 | reset($array); 56 | return key($array); 57 | } 58 | 59 | /** 60 | * Get last key of array. 61 | * 62 | * @param array $array 63 | * @return int|string|null 64 | */ 65 | public function arrayKeyLast(array $array){ 66 | end($array); 67 | return key($array); 68 | } 69 | 70 | /** 71 | * Check if passed url is the same as current url. 72 | * 73 | * @param array $url 74 | * @return bool 75 | */ 76 | public function isSameAsCurrentUrl(array $url = []): bool { 77 | $currentUrlMd5 = $this->getUrlMd5( 78 | Router::getRequest()->getParam('controller'), 79 | Router::getRequest()->getParam('action'), 80 | Router::getRequest()->getQuery(), 81 | Router::getRequest()->getParam('prefix'), 82 | Router::getRequest()->getParam('pass') 83 | ); 84 | $controller = Hash::get($url, 'controller', Router::getRequest()->getParam('controller')); 85 | $action = Hash::get($url, 'action', Router::getRequest()->getParam('action')); 86 | $query = Hash::get($url, '?', Router::getRequest()->getQuery()); 87 | $prefix = Hash::get($url, 'prefix', Router::getRequest()->getParam('prefix')); 88 | if (!is_array($query)) { 89 | throw new InvalidArgumentException('Query param must be an array.'); 90 | } 91 | 92 | $url = Hash::remove($url, 'controller'); 93 | $url = Hash::remove($url, 'action'); 94 | $url = Hash::remove($url, '?'); 95 | $url = Hash::remove($url, 'prefix'); 96 | $urlMd5 = $this->getUrlMd5($controller, $action, $query, $prefix, $url); 97 | 98 | return $currentUrlMd5 === $urlMd5; 99 | } 100 | 101 | /** 102 | * Check if passed params are in current url. 103 | * 104 | * @param array $url 105 | * @return bool 106 | */ 107 | public function isInCurrentUrl(array $url = []): bool { 108 | $currentController = Router::getRequest()->getParam('controller'); 109 | $currentAction = Router::getRequest()->getParam('action'); 110 | $currentQuery = Router::getRequest()->getQuery(); 111 | $currentPrefix = Router::getRequest()->getParam('prefix'); 112 | $currentPass = Router::getRequest()->getParam('pass'); 113 | $controller = Hash::get($url, 'controller', $currentController); 114 | $action = Hash::get($url, 'action', $currentAction); 115 | $query = Hash::get($url, '?', []); 116 | $prefix = Hash::get($url, 'prefix', $currentPrefix); 117 | if (!is_array($query)) { 118 | throw new InvalidArgumentException('Query param must be an array.'); 119 | } 120 | $url = Hash::remove($url, 'controller'); 121 | $url = Hash::remove($url, 'action'); 122 | $url = Hash::remove($url, '?'); 123 | $url = Hash::remove($url, 'prefix'); 124 | $pass = $url; 125 | if ($controller !== $currentController || $action !== $currentAction || $prefix !== $currentPrefix) { 126 | return false; 127 | } 128 | foreach ($pass as $key => $passItem) { 129 | if (empty($currentPass[$key]) || (!empty($currentPass[$key]) && $passItem !== $currentPass[$key])) { 130 | return false; 131 | } 132 | } 133 | foreach ($query as $key => $queryItem) { 134 | if (empty($currentQuery[$key]) || (!empty($currentQuery[$key]) && $queryItem !== $currentQuery[$key])) { 135 | return false; 136 | } 137 | } 138 | return true; 139 | } 140 | 141 | /** 142 | * Convert Url in md5 string 143 | * 144 | * @param string $controller 145 | * @param string $action 146 | * @param array $query 147 | * @param string|null $prefix 148 | * @param array $pass 149 | * @return string 150 | */ 151 | private function getUrlMd5(string $controller, string $action, array $query = [], ?string $prefix = null, array $pass = []): string { 152 | $md5Items = []; 153 | $md5Items[] = Inflector::camelize($controller); 154 | $md5Items[] = Inflector::camelize($action); 155 | $md5Items[] = !empty($prefix) ? Inflector::camelize($prefix) : '_EMPTY_'; 156 | ksort($pass); 157 | foreach ($pass as $key => $passItem) { 158 | $md5Items[] = Inflector::camelize("$key=$passItem"); 159 | } 160 | ksort($query); 161 | foreach ($query as $key => $queryItem) { 162 | $md5Items[] = Inflector::camelize("$key=$queryItem"); 163 | } 164 | return md5(implode('::', $md5Items)); 165 | } 166 | 167 | /** 168 | * Return current package version. 169 | * 170 | * @return string 171 | */ 172 | public function getPluginCurrentVersion(): string { 173 | $version = '0'; 174 | $packages = json_decode(file_get_contents(ROOT . DS . 'vendor' . DS . 'composer' . DS . 'installed.json')); 175 | foreach ($packages as $package) { 176 | if ($package->name === 'allanmcarvalho/cakephp-datatables') { 177 | $version = $package->version; 178 | } 179 | } 180 | return $version; 181 | } 182 | 183 | /** 184 | * Return the class md5 185 | * 186 | * @param string $classWithNameSpace Class name with namespace. 187 | * @return string Md5 string 188 | * @throws \ReflectionException 189 | */ 190 | public function getClassMd5(string $classWithNameSpace): string { 191 | return md5_file((new ReflectionClass($classWithNameSpace))->getFileName()); 192 | } 193 | 194 | /** 195 | * @param string $classWithNameSpace 196 | * @throws \ReflectionException 197 | * @return string 198 | */ 199 | public function getClassAndVersionMd5(string $classWithNameSpace): string { 200 | $classMd5 = $this->getClassMd5($classWithNameSpace); 201 | $versionMd5 = md5($this->getPluginCurrentVersion()); 202 | return md5($classMd5 . $versionMd5); 203 | } 204 | 205 | } 206 | -------------------------------------------------------------------------------- /src/Tools/Js.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | declare(strict_types = 1); 11 | 12 | namespace DataTables\Tools; 13 | 14 | use MatthiasMullie\Minify\JS as JsMinify; 15 | 16 | /** 17 | * Class Js 18 | * 19 | * @author Allan Carvalho 20 | * @license MIT License https://github.com/allanmcarvalho/cakephp-datatables/blob/master/LICENSE 21 | * @link https://github.com/allanmcarvalho/cakephp-datatables 22 | */ 23 | class Js { 24 | 25 | /** 26 | * Minify a js script. 27 | * 28 | * @param string $content Js to be minified. 29 | * @return string Minified Js. 30 | */ 31 | public static function minifyFile(string $content): string { 32 | $minifyJs = new JsMinify(); 33 | return $minifyJs->add($content)->minify(); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/Tools/Validator.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | declare(strict_types = 1); 11 | 12 | namespace DataTables\Tools; 13 | 14 | use Cake\Error\FatalErrorException; 15 | use Cake\Utility\Text; 16 | use InvalidArgumentException; 17 | 18 | /** 19 | * Class Validator 20 | * 21 | * @author Allan Carvalho 22 | * @license MIT License https://github.com/allanmcarvalho/cakephp-datatables/blob/master/LICENSE 23 | * @link https://github.com/allanmcarvalho/cakephp-datatables 24 | */ 25 | class Validator { 26 | 27 | /** 28 | * Storage a instance of object. 29 | * 30 | * @var self 31 | */ 32 | public static $instance; 33 | 34 | /** 35 | * Return a instance of builder object. 36 | * 37 | * @return \DataTables\Tools\Validator 38 | */ 39 | public static function getInstance(): Validator { 40 | if (static::$instance === null) { 41 | static::$instance = new self(); 42 | } 43 | return static::$instance; 44 | } 45 | 46 | /** 47 | * Check if the array keys and values are correct. 48 | * 49 | * @param array $array 50 | * @param string|array $allowedKeyTypes A allowed types for array key. 51 | * @param string|array $allowedValueTypes A allowed types for array value. 52 | * @param string|null $inString A string to make the error more friendly. 53 | * @return void 54 | */ 55 | public function checkKeysValueTypesOrFail(?array $array, $allowedKeyTypes = [], $allowedValueTypes = [], string $inString = null): void { 56 | if (empty($array)) { 57 | return; 58 | } 59 | $allowedKeyTypesType = getType($allowedKeyTypes); 60 | if (!in_array($allowedKeyTypesType, ['array', 'string'])) { 61 | throw new FatalErrorException(sprintf('The $keyType type must be an array or string. Found : %s', $allowedKeyTypesType)); 62 | } elseif ($allowedKeyTypesType === 'string') { 63 | $allowedKeyTypes = [$allowedKeyTypes]; 64 | } 65 | $allowedValueTypesType = getType($allowedValueTypes); 66 | if (!in_array($allowedValueTypesType, ['array', 'string'])) { 67 | throw new FatalErrorException(sprintf('The $valueType type must be an array or string. Found : %s', $allowedValueTypesType)); 68 | } elseif ($allowedValueTypesType === 'string') { 69 | $allowedValueTypes = [$allowedValueTypes]; 70 | } 71 | foreach ($array as $key => $value) { 72 | $keyType = getType($key); 73 | $valueType = getType($value); 74 | if (!in_array($keyType, $allowedKeyTypes) && $allowedKeyTypes !== ['*']) { 75 | $needleString = str_replace(' and ', ' or ', Text::toList($allowedKeyTypes)); 76 | throw new InvalidArgumentException("In $inString array, the keys always must be $needleString. key: $key."); 77 | } 78 | if (!in_array($valueType, $allowedValueTypes) && $allowedValueTypes !== ['*']) { 79 | $needleString = str_replace(' and ', ' or ', Text::toList($allowedValueTypes)); 80 | throw new InvalidArgumentException("In $inString array, the record $key isn't $needleString. Found: '$valueType'."); 81 | } 82 | } 83 | } 84 | 85 | /** 86 | * Check if array size is right. 87 | * 88 | * @param array $array The array that will be checked. 89 | * @param int $expected The expected size. 90 | * @param string|null $message A custom exception message. 91 | * @return void 92 | */ 93 | public function checkArraySizeOrFail(array $array, int $expected, ?string $message = null): void { 94 | $size = count($array); 95 | if ($size !== $expected) { 96 | if (empty($message)) { 97 | $message = "Wrong array size. Expected: '$expected'. Found: '$size'."; 98 | } 99 | throw new InvalidArgumentException($message); 100 | } 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/View/Cell/DataTablesCell.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | declare(strict_types = 1); 11 | 12 | namespace DataTables\View\Cell; 13 | 14 | use Cake\View\Cell; 15 | use DataTables\Table\Columns; 16 | 17 | /** 18 | * Class DataTablesCell 19 | * 20 | * @author Allan Carvalho 21 | * @license MIT License https://github.com/allanmcarvalho/cakephp-datatables/blob/master/LICENSE 22 | * @link https://github.com/allanmcarvalho/cakephp-datatables 23 | */ 24 | class DataTablesCell extends Cell { 25 | 26 | /** 27 | * Method that return the table html structure. 28 | * 29 | * @param \DataTables\Table\Columns $columns Config that is a concatenation of Tables class and config method. 30 | * @return void 31 | */ 32 | public function table(Columns $columns): void { 33 | 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/View/Helper/DataTablesHelper.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | declare(strict_types = 1); 11 | 12 | namespace DataTables\View\Helper; 13 | 14 | use Cake\Core\Configure; 15 | use Cake\View\Helper; 16 | use DataTables\Tools\Builder; 17 | 18 | /** 19 | * Class DataTablesHelper 20 | * 21 | * @author Allan Carvalho 22 | * @license MIT License https://github.com/allanmcarvalho/cakephp-datatables/blob/master/LICENSE 23 | * @link https://github.com/allanmcarvalho/cakephp-datatables 24 | */ 25 | class DataTablesHelper extends Helper { 26 | 27 | /** 28 | * Default configuration. 29 | * 30 | * @var array 31 | */ 32 | protected $_defaultConfig = [ 33 | 'cache' => true, 34 | 'minify' => true, 35 | ]; 36 | 37 | /** 38 | * @inheritDoc 39 | */ 40 | public function initialize(array $config): void { 41 | parent::initialize($config); 42 | if (Configure::read('debug') === true) { 43 | $this->setConfig('cache', !(bool)Configure::read('DataTables.StorageEngine.disableWhenDebugOn')); 44 | } 45 | } 46 | 47 | /** 48 | * Render the table html structure of a DataTables configure. 49 | * 50 | * @param string $table A Tables class plus config method that you want to render concatenated by '::'. Eg.: 'Foo::main'. 51 | * @return string 52 | * @throws \ReflectionException 53 | */ 54 | public function renderTable(string $table): string { 55 | $configBundle = Builder::getInstance()->getConfigBundle($table, $this->getConfig('cache')); 56 | return $configBundle->generateTableHtml($this->getView()); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /templates/cell/DataTables/table.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | declare(strict_types = 1); 11 | ?> 12 | 13 |

Tabela

14 | -------------------------------------------------------------------------------- /templates/twig/js/bake/callback_created_cell.twig: -------------------------------------------------------------------------------- 1 | {# 2 | Accessible functions parameters: 3 | - cell (node) - The TD node that has been created. 4 | - cellData (any) - Cell data. If you use columns.render to modify the data, use $(cell).html() to get and modify 5 | the rendered data. The information given here is the original and unmodified data from the data source. 6 | - rowData (any) - Data source object / array for the whole row. 7 | - rowIndex (integer) - DataTables' internal index for the row. 8 | - colIndex (integer) - DataTables' internal index for the column. 9 | #} -------------------------------------------------------------------------------- /templates/twig/js/functions/callback_created_cell.twig: -------------------------------------------------------------------------------- 1 | function (cell, cellData, rowData, rowIndex, colIndex) { 2 | {{ body }} 3 | } 4 | 5 | -------------------------------------------------------------------------------- /tests/Fixture/ArticlesFixture.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | declare(strict_types = 1); 11 | 12 | namespace DataTables\Test\Fixture; 13 | 14 | use Cake\TestSuite\Fixture\TestFixture; 15 | 16 | /** 17 | * Class ArticlesFixture 18 | * 19 | * @author Allan Carvalho 20 | * @license MIT License https://github.com/allanmcarvalho/cakephp-datatables/blob/master/LICENSE 21 | * @link https://github.com/allanmcarvalho/cakephp-datatables 22 | */ 23 | class ArticlesFixture extends TestFixture { 24 | 25 | /** 26 | * Fields 27 | * 28 | * @var array 29 | */ 30 | // phpcs:disable 31 | public $fields = [ 32 | 'id' => ['type' => 'integer', 'length' => 11, 'unsigned' => false, 'null' => false, 'default' => null, 'comment' => '', 'autoIncrement' => true, 'precision' => null], 33 | 'user_id' => ['type' => 'integer', 'length' => 11, 'unsigned' => false, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'autoIncrement' => null], 34 | 'title' => ['type' => 'string', 'length' => 40, 'null' => false, 'default' => null, 'collate' => 'utf8_general_ci', 'comment' => '', 'precision' => null], 35 | 'message' => ['type' => 'text', 'length' => null, 'null' => false, 'default' => null, 'collate' => 'utf8_general_ci', 'comment' => '', 'precision' => null], 36 | 'created' => ['type' => 'datetime', 'length' => null, 'precision' => null, 'null' => false, 'default' => null, 'comment' => ''], 37 | 'modified' => ['type' => 'datetime', 'length' => null, 'precision' => null, 'null' => false, 'default' => null, 'comment' => ''], 38 | '_constraints' => [ 39 | 'primary' => ['type' => 'primary', 'columns' => ['id'], 'length' => []], 40 | 'FK_articles_users' => ['type' => 'foreign', 'columns' => ['user_id'], 'references' => ['users', 'id'], 'update' => 'cascade', 'delete' => 'cascade', 'length' => []], 41 | ], 42 | '_options' => [ 43 | 'engine' => 'InnoDB', 44 | 'collation' => 'utf8_general_ci' 45 | ], 46 | ]; 47 | // phpcs:enable 48 | 49 | /** 50 | * Init method 51 | * 52 | * @return void 53 | */ 54 | public function init(): void { 55 | $this->records = [ 56 | [ 57 | 'id' => 1, 58 | 'user_id' => 1, 59 | 'title' => 'Lorem ipsum dolor sit amet', 60 | 'message' => 'Lorem ipsum dolor sit amet, aliquet feugiat. Convallis morbi fringilla gravida, phasellus feugiat dapibus velit nunc, pulvinar eget sollicitudin venenatis cum nullam, vivamus ut a sed, mollitia lectus. Nulla vestibulum massa neque ut et, id hendrerit sit, feugiat in taciti enim proin nibh, tempor dignissim, rhoncus duis vestibulum nunc mattis convallis.', 61 | 'created' => '2020-04-13 22:33:56', 62 | 'modified' => '2020-04-13 22:33:56', 63 | ], 64 | ]; 65 | parent::init(); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /tests/Fixture/UsersFixture.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | declare(strict_types = 1); 11 | 12 | namespace DataTables\Test\Fixture; 13 | 14 | use Cake\TestSuite\Fixture\TestFixture; 15 | 16 | /** 17 | * Class UsersFixture 18 | * 19 | * @author Allan Carvalho 20 | * @license MIT License https://github.com/allanmcarvalho/cakephp-datatables/blob/master/LICENSE 21 | * @link https://github.com/allanmcarvalho/cakephp-datatables 22 | */ 23 | class UsersFixture extends TestFixture { 24 | 25 | /** 26 | * Fields 27 | * 28 | * @var array 29 | */ 30 | // phpcs:disable 31 | public $fields = [ 32 | 'id' => ['type' => 'integer', 'length' => 11, 'unsigned' => false, 'null' => false, 'default' => null, 'comment' => '', 'autoIncrement' => true, 'precision' => null], 33 | 'name' => ['type' => 'string', 'length' => 45, 'null' => false, 'default' => null, 'collate' => 'utf8_general_ci', 'comment' => '', 'precision' => null], 34 | 'active' => ['type' => 'boolean', 'length' => null, 'null' => false, 'default' => '0', 'comment' => '', 'precision' => null], 35 | 'created' => ['type' => 'datetime', 'length' => null, 'precision' => null, 'null' => false, 'default' => null, 'comment' => ''], 36 | 'modified' => ['type' => 'datetime', 'length' => null, 'precision' => null, 'null' => false, 'default' => null, 'comment' => ''], 37 | '_constraints' => [ 38 | 'primary' => ['type' => 'primary', 'columns' => ['id'], 'length' => []], 39 | ], 40 | '_options' => [ 41 | 'engine' => 'InnoDB', 42 | 'collation' => 'utf8_general_ci' 43 | ], 44 | ]; 45 | // phpcs:enable 46 | /** 47 | * Init method 48 | * 49 | * @return void 50 | */ 51 | public function init(): void { 52 | $this->records = [ 53 | [ 54 | 'id' => 1, 55 | 'name' => 'Lorem ipsum dolor sit amet', 56 | 'active' => 1, 57 | 'created' => '2020-04-13 22:33:28', 58 | 'modified' => '2020-04-13 22:33:28', 59 | ], 60 | ]; 61 | parent::init(); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /tests/TestCase/PluginTest.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | 11 | namespace DataTables\Test\TestCase; 12 | 13 | use Cake\Cache\Cache; 14 | use Cake\Core\Configure; 15 | use Cake\Error\FatalErrorException; 16 | use Cake\TestSuite\TestCase; 17 | use DataTables\Plugin; 18 | use TestApp\Application; 19 | 20 | /** 21 | * Class PluginTest 22 | * 23 | * @author Allan Carvalho 24 | * @license MIT License https://github.com/allanmcarvalho/cakephp-datatables/blob/master/LICENSE 25 | * @link https://github.com/allanmcarvalho/cakephp-datatables 26 | */ 27 | class PluginTest extends TestCase { 28 | 29 | /** 30 | * @return void 31 | */ 32 | public function testBootstrapBasic() { 33 | $plugin = new Plugin(); 34 | $baseApplication = new Application(''); 35 | Configure::write('DataTables', [ 36 | 'test1' => '123', 37 | 'testArray' => [ 38 | 'test2' => 'abc', 39 | ], 40 | ]); 41 | $plugin->bootstrap($baseApplication); 42 | $this->assertEquals('123', Configure::read('DataTables.test1')); 43 | $this->assertEquals('abc', Configure::read('DataTables.testArray.test2')); 44 | $this->assertNotEmpty(Cache::getConfig('_data_tables_config_bundles_')); 45 | } 46 | 47 | /** 48 | * @return void 49 | */ 50 | public function testBootstrapApplicationConfigurationException() { 51 | $plugin = new Plugin(); 52 | $baseApplication = new Application(''); 53 | Configure::write('DataTables', 'test'); 54 | $this->expectException(FatalErrorException::class); 55 | $plugin->bootstrap($baseApplication); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /tests/TestCase/StorageEngine/CacheStorageEngineTest.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | 11 | namespace DataTables\Test\TestCase\StorageEngine; 12 | 13 | use Cake\TestSuite\TestCase; 14 | use DataTables\Plugin; 15 | use DataTables\StorageEngine\CacheStorageEngine; 16 | use DataTables\Table\ConfigBundle; 17 | use DataTables\Tools\Builder; 18 | use InvalidArgumentException; 19 | use TestApp\Application; 20 | use TestApp\DataTables\Tables\CategoriesTables; 21 | 22 | /** 23 | * Class CacheStorageEngineTest 24 | * 25 | * @author Allan Carvalho 26 | * @license MIT License https://github.com/allanmcarvalho/cakephp-datatables/blob/master/LICENSE 27 | * @link https://github.com/allanmcarvalho/cakephp-datatables 28 | */ 29 | class CacheStorageEngineTest extends TestCase { 30 | 31 | /** 32 | * Test subject 33 | * 34 | * @var \DataTables\StorageEngine\CacheStorageEngine 35 | */ 36 | protected $Cache; 37 | 38 | /** 39 | * setUp method 40 | * 41 | * @return void 42 | */ 43 | public function setUp(): void { 44 | parent::setUp(); 45 | $plugin = new Plugin(); 46 | $plugin->bootstrap(new Application('')); 47 | $this->Cache = new CacheStorageEngine(); 48 | } 49 | 50 | /** 51 | * tearDown method 52 | * 53 | * @return void 54 | */ 55 | public function tearDown(): void { 56 | parent::tearDown(); 57 | } 58 | 59 | /** 60 | * @return void 61 | */ 62 | public function testInvalidCacheConfig() { 63 | $this->expectException(InvalidArgumentException::class); 64 | $this->Cache = new CacheStorageEngine('cache_abc'); 65 | } 66 | 67 | /** 68 | * @return void 69 | */ 70 | public function testSave() { 71 | $buildConfig = Builder::getInstance()->buildConfigBundle(CategoriesTables::class, 'main', md5('abc')); 72 | $this->assertTrue($this->Cache->save('abc', $buildConfig)); 73 | } 74 | 75 | /** 76 | * @return void 77 | */ 78 | public function testRead() { 79 | $buildConfig = Builder::getInstance()->buildConfigBundle(CategoriesTables::class, 'main', md5('abc')); 80 | $this->Cache->save('abc', $buildConfig); 81 | $this->assertEmpty($this->Cache->read('def')); 82 | $this->assertInstanceOf(ConfigBundle::class, $this->Cache->read('abc')); 83 | } 84 | 85 | /** 86 | * @return void 87 | */ 88 | public function testDelete() { 89 | $buildConfig = Builder::getInstance()->buildConfigBundle(CategoriesTables::class, 'main', md5('abc')); 90 | $this->Cache->save('abc', $buildConfig); 91 | $this->assertTrue($this->Cache->delete('abc')); 92 | } 93 | 94 | /** 95 | * @return void 96 | */ 97 | public function testExists() { 98 | $buildConfig = Builder::getInstance()->buildConfigBundle(CategoriesTables::class, 'main', md5('abc')); 99 | $this->Cache->save('abc', $buildConfig); 100 | $this->assertFalse($this->Cache->exists('def')); 101 | $this->assertTrue($this->Cache->exists('abc')); 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /tests/TestCase/Table/ColumnTest.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | 11 | namespace DataTables\Test\TestCase\Table; 12 | 13 | use Cake\TestSuite\TestCase; 14 | use DataTables\Table\Column; 15 | use DataTables\Table\Columns; 16 | use DataTables\Table\Tables; 17 | use InvalidArgumentException; 18 | 19 | /** 20 | * Class ColumnTest 21 | * 22 | * @author Allan Carvalho 23 | * @license MIT License https://github.com/allanmcarvalho/cakephp-datatables/blob/master/LICENSE 24 | * @link https://github.com/allanmcarvalho/cakephp-datatables 25 | */ 26 | class ColumnTest extends TestCase { 27 | 28 | /** 29 | * @var string[] 30 | */ 31 | protected $fixtures = [ 32 | 'plugin.DataTables.Articles', 33 | 'plugin.DataTables.Users', 34 | ]; 35 | 36 | /** 37 | * Test subject 38 | * 39 | * @var \DataTables\Table\Columns 40 | */ 41 | protected $Columns; 42 | 43 | /** 44 | * setUp method 45 | * 46 | * @return void 47 | */ 48 | public function setUp(): void { 49 | parent::setUp(); 50 | /** @var \DataTables\Table\Tables $tables */ 51 | $tables = $this->getMockBuilder(Tables::class) 52 | ->setMockClassName('ArticlesTables') 53 | ->getMockForAbstractClass(); 54 | $tables->getOrmTable()->addAssociations([ 55 | 'belongsTo' => [ 56 | 'Users', 57 | ], 58 | ]); 59 | $this->Columns = new Columns($tables); 60 | } 61 | 62 | /** 63 | * tearDown method 64 | * 65 | * @return void 66 | */ 67 | public function tearDown(): void { 68 | unset($this->Columns); 69 | 70 | parent::tearDown(); 71 | } 72 | 73 | /** 74 | * @return void 75 | */ 76 | public function testSimple() { 77 | $col = $this->Columns->addDatabaseColumn('Users.id'); 78 | static::assertEquals(true, $col->isDatabase()); 79 | static::assertEquals('Users.id', $col->getName()); 80 | $col = $this->Columns->addNonDatabaseColumn('Users.test'); 81 | static::assertEquals(false, $col->isDatabase()); 82 | } 83 | 84 | /** 85 | * @return void 86 | */ 87 | public function testCellType() { 88 | $col = $this->Columns->addDatabaseColumn('Users.id'); 89 | $col->setCellType('th'); 90 | static::assertEquals('th', $col->getCellType()); 91 | $col->setCellType('td'); 92 | static::assertEquals('td', $col->getCellType()); 93 | $this->expectException(InvalidArgumentException::class); 94 | $col->setCellType('abc'); 95 | } 96 | 97 | /** 98 | * @return void 99 | */ 100 | public function testClassName() { 101 | $col = $this->Columns->addDatabaseColumn('Users.id'); 102 | static::assertEquals(null, $col->getClassName()); 103 | $col->setClassName('abc'); 104 | static::assertEquals('abc', $col->getClassName()); 105 | } 106 | 107 | /** 108 | * @return void 109 | */ 110 | public function testContentPadding() { 111 | $col = $this->Columns->addDatabaseColumn('Users.id'); 112 | static::assertEquals(null, $col->getContentPadding()); 113 | $col->setContentPadding('abcdfghi'); 114 | static::assertEquals('abcdfghi', $col->getContentPadding()); 115 | } 116 | 117 | /** 118 | * @return void 119 | */ 120 | public function testCreatedCell() { 121 | $col = $this->Columns->addDatabaseColumn('Users.id'); 122 | static::assertEquals(null, $col->getCreatedCell()); 123 | $col->setCreatedCell(); 124 | static::assertEquals([], $col->getCreatedCell()); 125 | $col->setCreatedCell('abc'); 126 | static::assertEquals('abc', $col->getCreatedCell()); 127 | $col->setCreatedCell(['abc' => 1234]); 128 | static::assertEquals(['abc' => 1234], $col->getCreatedCell()); 129 | $this->expectException(InvalidArgumentException::class); 130 | $col->setCreatedCell(12); 131 | } 132 | 133 | /** 134 | * @return void 135 | */ 136 | public function testOrderDataArray() { 137 | $col = $this->Columns->addDatabaseColumn('Users.id'); 138 | static::assertEquals(null, $col->getOrderData()); 139 | $col->setOrderData([]); 140 | static::assertEquals([], $col->getOrderData()); 141 | $col->setOrderData([1, 2, 3]); 142 | static::assertEquals([1, 2, 3], $col->getOrderData()); 143 | $this->expectException(InvalidArgumentException::class); 144 | $col->setOrderData(['abc']); 145 | } 146 | 147 | /** 148 | * @return void 149 | */ 150 | public function testOrderDataInteger() { 151 | $col = $this->Columns->addDatabaseColumn('Users.id'); 152 | $col->setOrderData(1); 153 | static::assertEquals(1, $col->getOrderData()); 154 | $this->expectException(InvalidArgumentException::class); 155 | $col->setOrderData(-1); 156 | } 157 | 158 | /** 159 | * @return void 160 | */ 161 | public function testOrderDataInvalid() { 162 | $col = $this->Columns->addDatabaseColumn('Users.id'); 163 | $this->expectException(InvalidArgumentException::class); 164 | $col->setOrderData(32.21); 165 | } 166 | 167 | /** 168 | * @return void 169 | */ 170 | public function testOrderDataType() { 171 | $col = $this->Columns->addDatabaseColumn('Users.id'); 172 | static::assertEquals(null, $col->getOrderDataType()); 173 | foreach (Column::VALID_ORDER_DATA_TYPES as $type) { 174 | $col->setOrderDataType($type); 175 | static::assertEquals($type, $col->getOrderDataType()); 176 | } 177 | $this->expectException(InvalidArgumentException::class); 178 | $col->setOrderDataType('abc'); 179 | } 180 | 181 | /** 182 | * @return void 183 | */ 184 | public function testOrderSequence() { 185 | $col = $this->Columns->addDatabaseColumn('Users.id'); 186 | static::assertEquals([], $col->getOrderSequence()); 187 | $col->setOrderSequence(['asc']); 188 | static::assertEquals(['asc'], $col->getOrderSequence()); 189 | $col->setOrderSequence(['desc', 'asc']); 190 | static::assertEquals(['desc', 'asc'], $col->getOrderSequence()); 191 | $this->expectException(InvalidArgumentException::class); 192 | $col->setOrderSequence(['desc', 'asc', 'abc']); 193 | } 194 | 195 | /** 196 | * @return void 197 | */ 198 | public function testOrderable() { 199 | $col = $this->Columns->addDatabaseColumn('Users.id'); 200 | static::assertEquals(null, $col->isOrderable()); 201 | $col->setOrderable(true); 202 | static::assertEquals(true, $col->isOrderable()); 203 | $col->setOrderable(false); 204 | static::assertEquals(false, $col->isOrderable()); 205 | $col->setOrderable(null); 206 | static::assertEquals(null, $col->isOrderable()); 207 | } 208 | 209 | /** 210 | * @return void 211 | */ 212 | public function testSearchable() { 213 | $col = $this->Columns->addDatabaseColumn('Users.id'); 214 | static::assertEquals(null, $col->isSearchable()); 215 | $col->setSearchable(true); 216 | static::assertEquals(true, $col->isSearchable()); 217 | $col->setSearchable(false); 218 | static::assertEquals(false, $col->isSearchable()); 219 | $col->setSearchable(null); 220 | static::assertEquals(null, $col->isSearchable()); 221 | } 222 | 223 | /** 224 | * @return void 225 | */ 226 | public function testTitle() { 227 | $col = $this->Columns->addDatabaseColumn('Users.id'); 228 | static::assertEquals('Id', $col->getTitle()); 229 | $col->setTitle('abc'); 230 | static::assertEquals('abc', $col->getTitle()); 231 | $col->setTitle('zzz'); 232 | static::assertEquals('zzz', $col->getTitle()); 233 | } 234 | 235 | /** 236 | * @return void 237 | */ 238 | public function testType() { 239 | $col = $this->Columns->addDatabaseColumn('Users.id'); 240 | static::assertEquals(null, $col->getType()); 241 | foreach (Column::VALID_TYPES as $type) { 242 | $col->setType($type); 243 | static::assertEquals($type, $col->getType()); 244 | } 245 | $this->expectException(InvalidArgumentException::class); 246 | $col->setType('abc'); 247 | } 248 | 249 | /** 250 | * @return void 251 | */ 252 | public function testVisible() { 253 | $col = $this->Columns->addDatabaseColumn('Users.id'); 254 | static::assertEquals(null, $col->isVisible()); 255 | $col->setVisible(true); 256 | static::assertEquals(true, $col->isVisible()); 257 | $col->setVisible(false); 258 | static::assertEquals(false, $col->isVisible()); 259 | $col->setVisible(null); 260 | static::assertEquals(null, $col->isVisible()); 261 | } 262 | 263 | /** 264 | * @return void 265 | */ 266 | public function testWidth() { 267 | $col = $this->Columns->addDatabaseColumn('Users.id'); 268 | static::assertEquals(null, $col->getWidth()); 269 | $col->setWidth('10px'); 270 | static::assertEquals('10px', $col->getWidth()); 271 | $col->setWidth('10%'); 272 | static::assertEquals('10%', $col->getWidth()); 273 | $col->setWidth(null); 274 | static::assertEquals(null, $col->getWidth()); 275 | } 276 | 277 | } 278 | -------------------------------------------------------------------------------- /tests/TestCase/Table/ColumnsTest.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | 11 | namespace DataTables\Test\TestCase\Table; 12 | 13 | use Cake\Error\FatalErrorException; 14 | use Cake\TestSuite\TestCase; 15 | use DataTables\Table\Columns; 16 | use DataTables\Table\Tables; 17 | use InvalidArgumentException; 18 | 19 | /** 20 | * Class ColumnsTest 21 | * 22 | * @author Allan Carvalho 23 | * @license MIT License https://github.com/allanmcarvalho/cakephp-datatables/blob/master/LICENSE 24 | * @link https://github.com/allanmcarvalho/cakephp-datatables 25 | */ 26 | class ColumnsTest extends TestCase { 27 | 28 | /** 29 | * @var string[] 30 | */ 31 | protected $fixtures = [ 32 | 'plugin.DataTables.Articles', 33 | 'plugin.DataTables.Users', 34 | ]; 35 | 36 | /** 37 | * Test subject 38 | * 39 | * @var \DataTables\Table\Columns 40 | */ 41 | protected $Columns; 42 | 43 | /** 44 | * setUp method 45 | * 46 | * @return void 47 | */ 48 | public function setUp(): void { 49 | parent::setUp(); 50 | /** @var \DataTables\Table\Tables $tables */ 51 | $tables = $this->getMockBuilder(Tables::class) 52 | ->setMockClassName('ArticlesTables') 53 | ->getMockForAbstractClass(); 54 | $tables->getOrmTable()->addAssociations([ 55 | 'belongsTo' => [ 56 | 'Users', 57 | ], 58 | ]); 59 | $this->Columns = new Columns($tables); 60 | } 61 | 62 | /** 63 | * tearDown method 64 | * 65 | * @return void 66 | */ 67 | public function tearDown(): void { 68 | unset($this->Columns); 69 | 70 | parent::tearDown(); 71 | } 72 | 73 | /** 74 | * @return void 75 | */ 76 | public function testNonDatabaseColumn() { 77 | $this->Columns->addNonDatabaseColumn('abc'); 78 | $this->Columns->addNonDatabaseColumn('abc2'); 79 | $this->Columns->addNonDatabaseColumn('abc3'); 80 | $this->assertEquals(3, count($this->Columns->getColumns())); 81 | $this->expectException(FatalErrorException::class); 82 | $this->Columns->addNonDatabaseColumn('abc'); 83 | } 84 | 85 | /** 86 | * @return void 87 | */ 88 | public function testDatabaseColumn() { 89 | $this->loadFixtures(); 90 | $this->Columns->addDatabaseColumn('Articles.id'); 91 | $this->Columns->addDatabaseColumn('created'); 92 | $this->Columns->addDatabaseColumn('Articles.title'); 93 | $this->expectException(InvalidArgumentException::class); 94 | $this->Columns->addDatabaseColumn('Articles.abc'); 95 | } 96 | 97 | /** 98 | * @return void 99 | */ 100 | public function testInvalidColumn1() { 101 | $this->loadFixtures(); 102 | $this->expectException(InvalidArgumentException::class); 103 | $this->Columns->addDatabaseColumn('Articles.id.id'); 104 | } 105 | 106 | /** 107 | * @return void 108 | */ 109 | public function testInvalidColumn2() { 110 | $this->loadFixtures(); 111 | $this->expectException(InvalidArgumentException::class); 112 | $this->Columns->addDatabaseColumn('Abc.id'); 113 | } 114 | 115 | /** 116 | * @return void 117 | */ 118 | public function testInvalidColumn3() { 119 | $this->loadFixtures(); 120 | $this->Columns->addDatabaseColumn('Users.id'); 121 | $this->expectException(InvalidArgumentException::class); 122 | $this->Columns->addDatabaseColumn('Abc.id'); 123 | } 124 | 125 | /** 126 | * @return void 127 | */ 128 | public function testInvalidColumn4() { 129 | $this->loadFixtures(); 130 | $this->Columns->addDatabaseColumn('Users.id'); 131 | $this->expectException(InvalidArgumentException::class); 132 | $this->Columns->addDatabaseColumn('Users.test'); 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /tests/TestCase/Table/Option/Callback/MainCallBackTest.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | 11 | namespace DataTables\Test\TestCase\Table\Option\Callback; 12 | 13 | use Cake\Core\Configure; 14 | use Cake\Error\FatalErrorException; 15 | use Cake\TestSuite\TestCase; 16 | use DataTables\Plugin; 17 | use DataTables\Table\Option\CallBack\MainCallBack; 18 | use InvalidArgumentException; 19 | use TestApp\Application; 20 | 21 | /** 22 | * Class MainCallBackTest 23 | * 24 | * @author Allan Carvalho 25 | * @license MIT License https://github.com/allanmcarvalho/cakephp-datatables/blob/master/LICENSE 26 | * @link https://github.com/allanmcarvalho/cakephp-datatables 27 | */ 28 | class MainCallBackTest extends TestCase { 29 | 30 | /** 31 | * setUp method 32 | * 33 | * @return void 34 | */ 35 | public function setUp(): void { 36 | parent::setUp(); 37 | $plugin = new Plugin(); 38 | $plugin->bootstrap(new Application('')); 39 | Configure::write('DataTables.resources.templates', DATA_TABLES_TESTS . 'test_app' . DS . 'templates' . DS . 'data_tables'); 40 | } 41 | 42 | /** 43 | * tearDown method 44 | * 45 | * @return void 46 | */ 47 | public function tearDown(): void { 48 | parent::tearDown(); 49 | } 50 | 51 | /** 52 | * @return void 53 | * @throws \Twig\Error\LoaderError 54 | * @throws \Twig\Error\RuntimeError 55 | * @throws \Twig\Error\SyntaxError 56 | */ 57 | public function testWithFile() { 58 | MainCallBack::destroyAllInstances(); 59 | $createdCellCallback = MainCallBack::getInstance('createdCell', 'Categories', 'main'); 60 | $this->assertNotEmpty($createdCellCallback->render()); 61 | } 62 | 63 | /** 64 | * @return void 65 | * @throws \Twig\Error\LoaderError 66 | * @throws \Twig\Error\RuntimeError 67 | * @throws \Twig\Error\SyntaxError 68 | */ 69 | public function testWithBody() { 70 | MainCallBack::destroyAllInstances(); 71 | $createdCellCallback = MainCallBack::getInstance('createdCell', 'Categories', 'main'); 72 | $this->assertNotEmpty($createdCellCallback->render('abc')); 73 | } 74 | 75 | /** 76 | * @return void 77 | * @throws \Twig\Error\LoaderError 78 | * @throws \Twig\Error\RuntimeError 79 | * @throws \Twig\Error\SyntaxError 80 | */ 81 | public function testWithCache() { 82 | MainCallBack::destroyAllInstances(); 83 | Configure::write('debug', false); 84 | $createdCellCallback = MainCallBack::getInstance('createdCell', 'Categories', 'main'); 85 | $this->assertNotEmpty($createdCellCallback->render('abc')); 86 | } 87 | 88 | /** 89 | * @return void 90 | * @throws \Twig\Error\LoaderError 91 | * @throws \Twig\Error\RuntimeError 92 | * @throws \Twig\Error\SyntaxError 93 | */ 94 | public function testAppTemplateNotFound() { 95 | MainCallBack::destroyAllInstances(); 96 | $this->expectException(FatalErrorException::class); 97 | MainCallBack::getInstance('abc', 'Categories', 'main')->render(); 98 | } 99 | 100 | /** 101 | * @return void 102 | * @throws \Twig\Error\LoaderError 103 | * @throws \Twig\Error\RuntimeError 104 | * @throws \Twig\Error\SyntaxError 105 | */ 106 | public function testPluginTemplateNotFound() { 107 | MainCallBack::destroyAllInstances(); 108 | $this->expectException(FatalErrorException::class); 109 | MainCallBack::getInstance('abc', 'Categories', 'main')->render('abc'); 110 | } 111 | 112 | /** 113 | * @return void 114 | * @throws \Twig\Error\LoaderError 115 | * @throws \Twig\Error\RuntimeError 116 | * @throws \Twig\Error\SyntaxError 117 | */ 118 | public function testInvalidBodyOrParams1() { 119 | MainCallBack::destroyAllInstances(); 120 | $this->expectException(InvalidArgumentException::class); 121 | MainCallBack::getInstance('createdCell', 'Categories', 'main')->render(3); 122 | } 123 | 124 | /** 125 | * @return void 126 | * @throws \Twig\Error\LoaderError 127 | * @throws \Twig\Error\RuntimeError 128 | * @throws \Twig\Error\SyntaxError 129 | */ 130 | public function testInvalidBodyOrParams2() { 131 | MainCallBack::destroyAllInstances(); 132 | $this->expectException(InvalidArgumentException::class); 133 | MainCallBack::getInstance('createdCell', 'Categories', 'main')->render(true); 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /tests/TestCase/Table/Option/MainOptionTest.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | 11 | namespace DataTables\Test\TestCase\Table\Option; 12 | 13 | use Cake\TestSuite\TestCase; 14 | use const JSON_ERROR_NONE; 15 | use DataTables\Table\Option\MainOption; 16 | use Exception; 17 | 18 | /** 19 | * Class MainOptionTest 20 | * 21 | * @author Allan Carvalho 22 | * @license MIT License https://github.com/allanmcarvalho/cakephp-datatables/blob/master/LICENSE 23 | * @link https://github.com/allanmcarvalho/cakephp-datatables 24 | */ 25 | class MainOptionTest extends TestCase { 26 | 27 | /** 28 | * Test subject 29 | * 30 | * @var \DataTables\Table\Option\MainOption 31 | */ 32 | protected $MainOption; 33 | 34 | /** 35 | * setUp method 36 | * 37 | * @return void 38 | */ 39 | public function setUp(): void { 40 | parent::setUp(); 41 | $this->MainOption = new MainOption(); 42 | } 43 | 44 | /** 45 | * tearDown method 46 | * 47 | * @return void 48 | */ 49 | public function tearDown(): void { 50 | unset($this->MainOption); 51 | 52 | parent::tearDown(); 53 | } 54 | 55 | /** 56 | * Check if print all options getter and setter is working 57 | * 58 | * @return void 59 | */ 60 | public function testPrintAllOptions() { 61 | $this->MainOption->setPrintAllOptions(true); 62 | $this->assertEquals(true, $this->MainOption->isPrintAllOptions()); 63 | $this->MainOption->setPrintAllOptions(false); 64 | $this->assertEquals(false, $this->MainOption->isPrintAllOptions()); 65 | } 66 | 67 | /** 68 | * Check if config getter and setter is working 69 | * 70 | * @return void 71 | */ 72 | public function testConfigAndMustPrint() { 73 | $this->MainOption->setConfig('abc', true); 74 | $this->assertEquals(true, $this->MainOption->getConfig('abc')); 75 | $this->assertEquals(true, $this->MainOption->getMustPrint('abc')); 76 | 77 | $this->MainOption->setConfig('def', [], false); 78 | $this->assertEquals([], $this->MainOption->getConfig('def')); 79 | $this->assertEquals(false, $this->MainOption->getMustPrint('def')); 80 | 81 | $this->MainOption->setMustPrint('abc', false); 82 | $this->assertEquals(false, $this->MainOption->getMustPrint('abc')); 83 | 84 | $this->assertEquals('array', getType($this->MainOption->getMustPrint())); 85 | 86 | $this->assertNotEmpty($this->MainOption->getConfig()); 87 | } 88 | 89 | /** 90 | * Check if array and json getter is working 91 | * 92 | * @return void 93 | */ 94 | public function testArrayJson() { 95 | $this->MainOption->setConfig('abc', '1234', false); 96 | $this->MainOption->setPrintAllOptions(true); 97 | $allConfig = $this->MainOption->getConfigAsArray(); 98 | $this->MainOption->setPrintAllOptions(false); 99 | $config = $this->MainOption->getConfigAsArray(); 100 | $this->assertGreaterThanOrEqual(count($config), count($allConfig)); 101 | $this->assertEquals(true, array_key_exists('abc', $allConfig)); 102 | $this->assertEquals(false, array_key_exists('abc', $config)); 103 | 104 | try { 105 | $this->MainOption->getConfigAsJson(); 106 | } catch (Exception $exception) { 107 | 108 | } finally { 109 | $this->assertEquals(true, json_last_error() === JSON_ERROR_NONE); 110 | } 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /tests/TestCase/Table/Option/Section/FeaturesOptionTest.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | 11 | namespace DataTables\Test\TestCase\Table\Option\Section; 12 | 13 | use Cake\Error\FatalErrorException; 14 | use Cake\TestSuite\TestCase; 15 | use DataTables\Table\Option\MainOption; 16 | 17 | class FeaturesOptionTest extends TestCase { 18 | 19 | /** 20 | * Test subject 21 | * 22 | * @var \DataTables\Table\Option\MainOption 23 | */ 24 | protected $MainOption; 25 | 26 | /** 27 | * setUp method 28 | * 29 | * @return void 30 | */ 31 | public function setUp(): void { 32 | parent::setUp(); 33 | $this->MainOption = new MainOption(); 34 | } 35 | 36 | /** 37 | * tearDown method 38 | * 39 | * @return void 40 | */ 41 | public function tearDown(): void { 42 | unset($this->MainOption); 43 | 44 | parent::tearDown(); 45 | } 46 | 47 | /** 48 | * @return void 49 | */ 50 | public function testSimpleOptions() { 51 | $autoWidth = $this->MainOption->Features->isAutoWidth(); 52 | $this->MainOption->Features->setAutoWidth(!$autoWidth); 53 | $this->assertEquals(!$autoWidth, $this->MainOption->Features->isAutoWidth()); 54 | 55 | $deferRender = $this->MainOption->Features->isDeferRender(); 56 | $this->MainOption->Features->setDeferRender(!$deferRender); 57 | $this->assertEquals(!$deferRender, $this->MainOption->Features->isDeferRender()); 58 | 59 | $info = $this->MainOption->Features->isInfo(); 60 | $this->MainOption->Features->setInfo(!$info); 61 | $this->assertEquals(!$info, $this->MainOption->Features->isInfo()); 62 | 63 | $lengthChange = $this->MainOption->Features->isLengthChange(); 64 | $this->MainOption->Features->setLengthChange(!$lengthChange); 65 | $this->assertEquals(!$lengthChange, $this->MainOption->Features->isLengthChange()); 66 | 67 | $ordering = $this->MainOption->Features->isOrdering(); 68 | $this->MainOption->Features->setOrdering(!$ordering); 69 | $this->assertEquals(!$ordering, $this->MainOption->Features->isOrdering()); 70 | 71 | $paging = $this->MainOption->Features->isPaging(); 72 | $this->MainOption->Features->setPaging(!$paging); 73 | $this->assertEquals(!$paging, $this->MainOption->Features->isPaging()); 74 | 75 | $processing = $this->MainOption->Features->isProcessing(); 76 | $this->MainOption->Features->setProcessing(!$processing); 77 | $this->assertEquals(!$processing, $this->MainOption->Features->isProcessing()); 78 | 79 | $scrollX = $this->MainOption->Features->isScrollX(); 80 | $this->MainOption->Features->setScrollX(!$scrollX); 81 | $this->assertEquals(!$scrollX, $this->MainOption->Features->isScrollX()); 82 | 83 | $scrollYOld = $this->MainOption->Features->getScrollY(); 84 | $scrollYOldNew = '200px'; 85 | $this->MainOption->Features->setScrollY($scrollYOldNew); 86 | $this->assertNotEquals($scrollYOld, $this->MainOption->Features->getScrollY()); 87 | $this->assertEquals($scrollYOldNew, $this->MainOption->Features->getScrollY()); 88 | 89 | $searching = $this->MainOption->Features->isSearching(); 90 | $this->MainOption->Features->setSearching(!$searching); 91 | $this->assertEquals(!$searching, $this->MainOption->Features->isSearching()); 92 | 93 | $stateSave = $this->MainOption->Features->isStateSave(); 94 | $this->MainOption->Features->setStateSave(!$stateSave); 95 | $this->assertEquals(!$stateSave, $this->MainOption->Features->isStateSave()); 96 | 97 | $this->assertEquals(true, $this->MainOption->Features->isServerSide()); 98 | $this->MainOption->Features->setServerSide(true); 99 | $this->assertEquals(true, $this->MainOption->Features->isServerSide()); 100 | $this->expectException(FatalErrorException::class); 101 | $this->MainOption->Features->setServerSide(false); 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /tests/TestCase/Table/Option/Section/OptionsOptionTest.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | 11 | namespace DataTables\Test\TestCase\Table\Option\Section; 12 | 13 | use Cake\Error\FatalErrorException; 14 | use Cake\TestSuite\TestCase; 15 | use DataTables\Table\Option\MainOption; 16 | use DataTables\Table\Option\Section\OptionsOption; 17 | use InvalidArgumentException; 18 | 19 | /** 20 | * Class OptionsOptionTest 21 | * 22 | * @author Allan Carvalho 23 | * @license MIT License https://github.com/allanmcarvalho/cakephp-datatables/blob/master/LICENSE 24 | * @link https://github.com/allanmcarvalho/cakephp-datatables 25 | */ 26 | class OptionsOptionTest extends TestCase { 27 | 28 | /** 29 | * Test subject 30 | * 31 | * @var \DataTables\Table\Option\MainOption 32 | */ 33 | protected $MainOption; 34 | 35 | /** 36 | * setUp method 37 | * 38 | * @return void 39 | */ 40 | public function setUp(): void { 41 | parent::setUp(); 42 | $this->MainOption = new MainOption(); 43 | } 44 | 45 | /** 46 | * tearDown method 47 | * 48 | * @return void 49 | */ 50 | public function tearDown(): void { 51 | unset($this->MainOption); 52 | 53 | parent::tearDown(); 54 | } 55 | 56 | /** 57 | * @return void 58 | */ 59 | public function testGetSetDom() { 60 | $newDom = 'rpitfl'; 61 | $this->assertInstanceOf(MainOption::class, $this->MainOption->Options->setDom($newDom)); 62 | $this->assertEquals($newDom, $this->MainOption->Options->getDom()); 63 | } 64 | 65 | /** 66 | * @return void 67 | */ 68 | public function testGetSetOrderFixed() { 69 | $newOrderFixed = [[0, 'asc'], [1, 'desc']]; 70 | $this->MainOption->Options->setOrderFixed($newOrderFixed); 71 | $this->assertEquals($newOrderFixed, $this->MainOption->Options->getOrderFixed()); 72 | 73 | $newOrderFixed = [ 74 | 'pre' => [[0, 'asc'], [1, 'desc']], 75 | 'post' => [[0, 'asc'], [1, 'desc']], 76 | ]; 77 | $this->MainOption->Options->setOrderFixed($newOrderFixed); 78 | $this->assertEquals($newOrderFixed, $this->MainOption->Options->getOrderFixed()); 79 | } 80 | 81 | /** 82 | * @return void 83 | */ 84 | public function testSetOrderFixedDefaultWrongArrayFormat() { 85 | $this->expectException(InvalidArgumentException::class); 86 | $this->MainOption->Options->setOrderFixed(['abc']); 87 | } 88 | 89 | /** 90 | * @return void 91 | */ 92 | public function testSetOrderFixedDefaultWrongArrayFormat1() { 93 | $this->expectException(InvalidArgumentException::class); 94 | $this->MainOption->Options->setOrderFixed(['abc' => []]); 95 | } 96 | 97 | /** 98 | * @return void 99 | */ 100 | public function testSetOrderFixedDefaultWrongArrayFormat2() { 101 | $this->expectException(InvalidArgumentException::class); 102 | $this->MainOption->Options->setOrderFixed(['spre' => ['abc' => 'desc']]); 103 | } 104 | 105 | /** 106 | * @return void 107 | */ 108 | public function testSetOrderFixedDefaultWrongArrayFormat3() { 109 | $this->expectException(InvalidArgumentException::class); 110 | $this->MainOption->Options->setOrderFixed(['pre' => [0 => 'abc']]); 111 | } 112 | 113 | /** 114 | * @return void 115 | */ 116 | public function testSetOrderFixedDefaultWrongSize() { 117 | $this->expectException(InvalidArgumentException::class); 118 | $this->MainOption->Options->setOrderFixed([['abc1', 'abc2', 'abc3']]); 119 | } 120 | 121 | /** 122 | * @return void 123 | */ 124 | public function testSetOrderFixedDefaultWrongParameter1() { 125 | $this->expectException(InvalidArgumentException::class); 126 | $this->MainOption->Options->setOrderFixed([['abc1', 'desc']]); 127 | } 128 | 129 | /** 130 | * @return void 131 | */ 132 | public function testSetOrderFixedDefaultWrongParameter2() { 133 | $this->expectException(InvalidArgumentException::class); 134 | $this->MainOption->Options->setOrderFixed([[0, 'abc']]); 135 | } 136 | 137 | /** 138 | * @return void 139 | */ 140 | public function testSetOrderFixedObjectWrongArrayFormat() { 141 | $this->expectException(InvalidArgumentException::class); 142 | $this->MainOption->Options->setOrderFixed(['abc' => [[0, 'asc']]]); 143 | } 144 | 145 | /** 146 | * @return void 147 | */ 148 | public function testSetOrderFixedObjectWrongSize() { 149 | $this->expectException(InvalidArgumentException::class); 150 | $this->MainOption->Options->setOrderFixed(['pre' => ['abc1', 'abc2', 'abc3']]); 151 | } 152 | 153 | /** 154 | * @return void 155 | */ 156 | public function testSetOrderFixedObjectWrongParameter1() { 157 | $this->expectException(InvalidArgumentException::class); 158 | $this->MainOption->Options->setOrderFixed(['post' => ['abc1', 'asc']]); 159 | } 160 | 161 | /** 162 | * @return void 163 | */ 164 | public function testSetOrderFixedObjectWrongParameter2() { 165 | $this->expectException(InvalidArgumentException::class); 166 | $this->MainOption->Options->setOrderFixed(['pre' => [0, 'abc']]); 167 | } 168 | 169 | /** 170 | * @return void 171 | */ 172 | public function testSetCheckSearchSmart() { 173 | $searchSmart = $this->MainOption->Options->isSearchSmart(); 174 | $this->MainOption->Options->setSearchSmart(!$searchSmart); 175 | $this->assertEquals(!$searchSmart, $this->MainOption->Options->isSearchSmart()); 176 | $this->MainOption->Options->setSearchSmart($searchSmart); 177 | $this->assertEquals($searchSmart, $this->MainOption->Options->isSearchSmart()); 178 | } 179 | 180 | /** 181 | * @return void 182 | */ 183 | public function testGetSetPagingType() { 184 | foreach (OptionsOption::ALLOWED_PAGING_TYPES as $allowedType) { 185 | $this->MainOption->Options->setPagingType($allowedType); 186 | $this->assertEquals($allowedType, $this->MainOption->Options->getPagingType()); 187 | } 188 | $this->expectException(InvalidArgumentException::class); 189 | $this->MainOption->Options->setPagingType('abc'); 190 | } 191 | 192 | /** 193 | * @return void 194 | */ 195 | public function testSetCheckSearchRegex() { 196 | $searchRegex = $this->MainOption->Options->isSearchRegex(); 197 | $this->MainOption->Options->setSearchRegex(!$searchRegex); 198 | $this->assertEquals(!$searchRegex, $this->MainOption->Options->isSearchRegex()); 199 | $this->MainOption->Options->setSearchRegex($searchRegex); 200 | $this->assertEquals($searchRegex, $this->MainOption->Options->isSearchRegex()); 201 | } 202 | 203 | /** 204 | * @return void 205 | */ 206 | public function testSetCheckOrderCellsTop() { 207 | $orderCellsTop = $this->MainOption->Options->isOrderCellsTop(); 208 | $this->MainOption->Options->setOrderCellsTop(!$orderCellsTop); 209 | $this->assertEquals(!$orderCellsTop, $this->MainOption->Options->isOrderCellsTop()); 210 | $this->MainOption->Options->setOrderCellsTop($orderCellsTop); 211 | $this->assertEquals($orderCellsTop, $this->MainOption->Options->isOrderCellsTop()); 212 | } 213 | 214 | /** 215 | * @return void 216 | */ 217 | public function testSetGetSearchSearch() { 218 | $this->assertEquals('', $this->MainOption->Options->getSearchSearch()); 219 | $this->MainOption->Options->setSearchSearch('abc'); 220 | $this->assertEquals('abc', $this->MainOption->Options->getSearchSearch()); 221 | } 222 | 223 | /** 224 | * @return void 225 | */ 226 | public function testSetCheckScrollCollapse() { 227 | $scrollCollapse = $this->MainOption->Options->isScrollCollapse(); 228 | $this->MainOption->Options->setScrollCollapse(!$scrollCollapse); 229 | $this->assertEquals(!$scrollCollapse, $this->MainOption->Options->isScrollCollapse()); 230 | $this->MainOption->Options->setScrollCollapse($scrollCollapse); 231 | $this->assertEquals($scrollCollapse, $this->MainOption->Options->isScrollCollapse()); 232 | } 233 | 234 | /** 235 | * @return void 236 | */ 237 | public function testSetGetRenderer() { 238 | $this->MainOption->Options->setRenderer('bootstrap'); 239 | $this->assertEquals('bootstrap', $this->MainOption->Options->getRenderer()); 240 | $this->MainOption->Options->setRenderer(['header' => 'jqueryui', 'pageButton' => 'bootstrap']); 241 | $this->assertEquals(['header' => 'jqueryui', 'pageButton' => 'bootstrap'], $this->MainOption->Options->getRenderer()); 242 | } 243 | 244 | /** 245 | * @return void 246 | */ 247 | public function testSetGetRendererInvalidType1() { 248 | $this->expectException(InvalidArgumentException::class); 249 | $this->MainOption->Options->setRenderer(1); 250 | } 251 | 252 | /** 253 | * @return void 254 | */ 255 | public function testSetGetRendererInvalidType2() { 256 | $this->expectException(InvalidArgumentException::class); 257 | $this->MainOption->Options->setRenderer([2 => '']); 258 | } 259 | 260 | /** 261 | * @return void 262 | */ 263 | public function testSetGetRendererInvalidType3() { 264 | $this->expectException(InvalidArgumentException::class); 265 | $this->MainOption->Options->setRenderer(['header' => true]); 266 | } 267 | 268 | /** 269 | * @return void 270 | */ 271 | public function testSetGetRendererInvalidKey() { 272 | $this->expectException(InvalidArgumentException::class); 273 | $this->MainOption->Options->setRenderer(['abc' => '']); 274 | } 275 | 276 | /** 277 | * @return void 278 | */ 279 | public function testSetCheckOrderMulti() { 280 | $orderMulti = $this->MainOption->Options->isOrderMulti(); 281 | $this->MainOption->Options->setOrderMulti(!$orderMulti); 282 | $this->assertEquals(!$orderMulti, $this->MainOption->Options->isOrderMulti()); 283 | $this->MainOption->Options->setOrderMulti($orderMulti); 284 | $this->assertEquals($orderMulti, $this->MainOption->Options->isOrderMulti()); 285 | } 286 | 287 | /** 288 | * @return void 289 | */ 290 | public function testGetSetSearchCols() { 291 | $this->MainOption->Options->setSearchCols([null, ['search' => 'abc', 'regex' => true]]); 292 | $this->assertEquals([null, ['search' => 'abc', 'regex' => true]], $this->MainOption->Options->getSearchCols()); 293 | } 294 | 295 | /** 296 | * @return void 297 | */ 298 | public function testGetSetSearchColsInvalidFormat1() { 299 | $this->expectException(InvalidArgumentException::class); 300 | $this->MainOption->Options->setSearchCols([null, ['abc' => 'abc']]); 301 | } 302 | 303 | /** 304 | * @return void 305 | */ 306 | public function testGetSetSearchColsInvalidFormat2() { 307 | $this->expectException(InvalidArgumentException::class); 308 | $this->MainOption->Options->setSearchCols([null, ['regex' => 'abc']]); 309 | } 310 | 311 | /** 312 | * @return void 313 | */ 314 | public function testGetSetSearchColsInvalidFormat3() { 315 | $this->expectException(InvalidArgumentException::class); 316 | $this->MainOption->Options->setSearchCols([null, ['search' => true]]); 317 | } 318 | 319 | /** 320 | * @return void 321 | */ 322 | public function testGetSetDeferLoading() { 323 | $this->MainOption->Options->setDeferLoading(52); 324 | $this->assertEquals(52, $this->MainOption->Options->getDeferLoading()); 325 | $this->MainOption->Options->setDeferLoading([57, 100]); 326 | $this->assertEquals([57, 100], $this->MainOption->Options->getDeferLoading()); 327 | } 328 | 329 | /** 330 | * @return void 331 | */ 332 | public function testGetSetDeferLoadingInvalidFormat1() { 333 | $this->expectException(FatalErrorException::class); 334 | $this->MainOption->Options->setDeferLoading(true); 335 | } 336 | 337 | /** 338 | * @return void 339 | */ 340 | public function testGetSetDeferLoadingInvalidFormat2() { 341 | $this->expectException(InvalidArgumentException::class); 342 | $this->MainOption->Options->setDeferLoading([3, 4, 5]); 343 | } 344 | 345 | /** 346 | * @return void 347 | */ 348 | public function testGetSetDeferLoadingInvalidFormat3() { 349 | $this->expectException(InvalidArgumentException::class); 350 | $this->MainOption->Options->setDeferLoading([true, '2']); 351 | } 352 | 353 | /** 354 | * @return void 355 | */ 356 | public function testGetSetLengthMenu() { 357 | $this->MainOption->Options->setLengthMenu([10, 25, 50, 75, 100]); 358 | $this->assertEquals([10, 25, 50, 75, 100], $this->MainOption->Options->getLengthMenu()); 359 | $this->MainOption->Options->setLengthMenu([[10, 25, 50, -1], [10, 25, 50, 'All']]); 360 | $this->assertEquals([[10, 25, 50, -1], [10, 25, 50, 'All']], $this->MainOption->Options->getLengthMenu()); 361 | } 362 | 363 | /** 364 | * @return void 365 | */ 366 | public function testGetSetLengthMenuInvalidFormat1() { 367 | $this->expectException(FatalErrorException::class); 368 | $this->MainOption->Options->setLengthMenu([[5, 10], ['five', 'ten', 'other']]); 369 | } 370 | 371 | /** 372 | * @return void 373 | */ 374 | public function testGetSetLengthMenuInvalidFormat2() { 375 | $this->expectException(InvalidArgumentException::class); 376 | $this->MainOption->Options->setLengthMenu([['abc' => 5], ['label1' => 'five']]); 377 | } 378 | 379 | /** 380 | * @return void 381 | */ 382 | public function testGetSetLengthMenuInvalidFormat3() { 383 | $this->expectException(InvalidArgumentException::class); 384 | $this->MainOption->Options->setLengthMenu([[5, 'ab' => 10]]); 385 | } 386 | 387 | /** 388 | * @return void 389 | */ 390 | public function testGetSetRowId() { 391 | $this->MainOption->Options->setRowId('abc'); 392 | $this->assertEquals('abc', $this->MainOption->Options->getRowId()); 393 | } 394 | 395 | /** 396 | * @return void 397 | */ 398 | public function testSetCheckSearchCaseInsensitive() { 399 | $searchCaseInsensitive = $this->MainOption->Options->isSearchCaseInsensitive(); 400 | $this->MainOption->Options->setSearchCaseInsensitive(!$searchCaseInsensitive); 401 | $this->assertEquals(!$searchCaseInsensitive, $this->MainOption->Options->isSearchCaseInsensitive()); 402 | $this->MainOption->Options->setSearchCaseInsensitive($searchCaseInsensitive); 403 | $this->assertEquals($searchCaseInsensitive, $this->MainOption->Options->isSearchCaseInsensitive()); 404 | } 405 | 406 | /** 407 | * @return void 408 | */ 409 | public function testGetSetStateDuration() { 410 | $this->MainOption->Options->setStateDuration(1234); 411 | $this->assertEquals(1234, $this->MainOption->Options->getStateDuration()); 412 | } 413 | 414 | /** 415 | * @return void 416 | */ 417 | public function testGetSetStateDurationInvalid() { 418 | $this->expectException(InvalidArgumentException::class); 419 | $this->MainOption->Options->setStateDuration(-10); 420 | } 421 | 422 | /** 423 | * @return void 424 | */ 425 | public function testGetSetPageLength() { 426 | $this->MainOption->Options->setPageLength(15); 427 | $this->assertEquals(15, $this->MainOption->Options->getPageLength()); 428 | } 429 | 430 | /** 431 | * @return void 432 | */ 433 | public function testGetSetPageLengthInvalid() { 434 | $this->expectException(InvalidArgumentException::class); 435 | $this->MainOption->Options->setPageLength(-10); 436 | } 437 | 438 | /** 439 | * @return void 440 | */ 441 | public function testGetSetOrder() { 442 | $this->MainOption->Options->setOrder([[0, 'asc'], [1, 'desc']]); 443 | $this->assertEquals([[0, 'asc'], [1, 'desc']], $this->MainOption->Options->getOrder()); 444 | } 445 | 446 | /** 447 | * @return void 448 | */ 449 | public function testGetSetOrderInvalid1() { 450 | $this->expectException(InvalidArgumentException::class); 451 | $this->MainOption->Options->setOrder([0, 'asc']); 452 | } 453 | 454 | /** 455 | * @return void 456 | */ 457 | public function testGetSetOrderInvalid2() { 458 | $this->expectException(InvalidArgumentException::class); 459 | $this->MainOption->Options->setOrder([[0, 'asc', 2]]); 460 | } 461 | 462 | /** 463 | * @return void 464 | */ 465 | public function testGetSetOrderInvalid3() { 466 | $this->expectException(InvalidArgumentException::class); 467 | $this->MainOption->Options->setOrder([[0, true]]); 468 | } 469 | 470 | /** 471 | * @return void 472 | */ 473 | public function testGetSetOrderInvalid4() { 474 | $this->expectException(InvalidArgumentException::class); 475 | $this->MainOption->Options->setOrder([['as', 'as']]); 476 | } 477 | 478 | /** 479 | * @return void 480 | */ 481 | public function testGetSetOrderInvalid5() { 482 | $this->expectException(InvalidArgumentException::class); 483 | $this->MainOption->Options->setOrder([[1, 3]]); 484 | } 485 | 486 | /** 487 | * @return void 488 | */ 489 | public function testGetSetOrderInvalid6() { 490 | $this->expectException(InvalidArgumentException::class); 491 | $this->MainOption->Options->setOrder([[0, 'abc']]); 492 | } 493 | 494 | /** 495 | * @return void 496 | */ 497 | public function testSetCheckRetrieve() { 498 | $retrieve = $this->MainOption->Options->isRetrieve(); 499 | $this->MainOption->Options->setRetrieve(!$retrieve); 500 | $this->assertEquals(!$retrieve, $this->MainOption->Options->isRetrieve()); 501 | $this->MainOption->Options->setRetrieve($retrieve); 502 | $this->assertEquals($retrieve, $this->MainOption->Options->isRetrieve()); 503 | } 504 | 505 | /** 506 | * @return void 507 | */ 508 | public function testGetSetSearchDelay() { 509 | $this->MainOption->Options->setSearchDelay(456); 510 | $this->assertEquals(456, $this->MainOption->Options->getSearchDelay()); 511 | } 512 | 513 | /** 514 | * @return void 515 | */ 516 | public function testGetSetSearchDelayInvalid() { 517 | $this->expectException(InvalidArgumentException::class); 518 | $this->MainOption->Options->setSearchDelay(-10); 519 | } 520 | 521 | /** 522 | * @return void 523 | */ 524 | public function testSetCheckDestroy() { 525 | $destroy = $this->MainOption->Options->isDestroy(); 526 | $this->MainOption->Options->setDestroy(!$destroy); 527 | $this->assertEquals(!$destroy, $this->MainOption->Options->isDestroy()); 528 | $this->MainOption->Options->setDestroy($destroy); 529 | $this->assertEquals($destroy, $this->MainOption->Options->isDestroy()); 530 | } 531 | 532 | /** 533 | * @return void 534 | */ 535 | public function testGetSetTabIndex() { 536 | $this->MainOption->Options->setTabIndex(24); 537 | $this->assertEquals(24, $this->MainOption->Options->getTabIndex()); 538 | } 539 | 540 | /** 541 | * @return void 542 | */ 543 | public function testGetSetDisplayStart() { 544 | $this->MainOption->Options->setDisplayStart(16); 545 | $this->assertEquals(16, $this->MainOption->Options->getDisplayStart()); 546 | } 547 | 548 | /** 549 | * @return void 550 | */ 551 | public function testGetSetStripeClasses() { 552 | $this->MainOption->Options->setStripeClasses(['cssClass1', 'cssClass2']); 553 | $this->assertEquals(['cssClass1', 'cssClass2'], $this->MainOption->Options->getStripeClasses()); 554 | } 555 | 556 | /** 557 | * @return void 558 | */ 559 | public function testGetSetStripeClassesInvalid1() { 560 | $this->expectException(InvalidArgumentException::class); 561 | $this->MainOption->Options->setStripeClasses(['abc ' => 'cssClass2']); 562 | } 563 | 564 | /** 565 | * @return void 566 | */ 567 | public function testGetSetStripeClassesInvalid2() { 568 | $this->expectException(InvalidArgumentException::class); 569 | $this->MainOption->Options->setStripeClasses([true]); 570 | } 571 | 572 | /** 573 | * @return void 574 | */ 575 | public function testSetCheckOrderClasses() { 576 | $orderClasses = $this->MainOption->Options->isOrderClasses(); 577 | $this->MainOption->Options->setOrderClasses(!$orderClasses); 578 | $this->assertEquals(!$orderClasses, $this->MainOption->Options->isOrderClasses()); 579 | $this->MainOption->Options->setOrderClasses($orderClasses); 580 | $this->assertEquals($orderClasses, $this->MainOption->Options->isOrderClasses()); 581 | } 582 | 583 | } 584 | -------------------------------------------------------------------------------- /tests/TestCase/Table/TablesTest.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | 11 | namespace DataTables\Test\TestCase\Table; 12 | 13 | use Cake\Error\FatalErrorException; 14 | use Cake\TestSuite\TestCase; 15 | use DataTables\Table\Tables; 16 | 17 | /** 18 | * Class TablesTest 19 | * 20 | * @author Allan Carvalho 21 | * @license MIT License https://github.com/allanmcarvalho/cakephp-datatables/blob/master/LICENSE 22 | * @link https://github.com/allanmcarvalho/cakephp-datatables 23 | */ 24 | class TablesTest extends TestCase { 25 | 26 | /** 27 | * @return void 28 | */ 29 | public function testWrongClassName() { 30 | $this->expectException(FatalErrorException::class); 31 | $this->getMockBuilder(Tables::class) 32 | ->setMockClassName('ArticlesAbc') 33 | ->getMockForAbstractClass(); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /tests/TestCase/Tools/FunctionsTest.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | 11 | namespace DataTables\Test\TestCase\Tools; 12 | 13 | use Cake\TestSuite\TestCase; 14 | use DataTables\Tools\Functions; 15 | 16 | /** 17 | * Class FunctionsTest 18 | * 19 | * @author Allan Carvalho 20 | * @license MIT License https://github.com/allanmcarvalho/cakephp-datatables/blob/master/LICENSE 21 | * @link https://github.com/allanmcarvalho/cakephp-datatables 22 | */ 23 | class FunctionsTest extends TestCase { 24 | 25 | /** 26 | * @return void 27 | */ 28 | public function testArrayKeyFirst() { 29 | $result = Functions::getInstance()->arrayKeyFirst(['abc' => 'a', 'as', 'ass']); 30 | $this->assertEquals('abc', $result); 31 | } 32 | 33 | /** 34 | * @return void 35 | */ 36 | public function testArrayKeyLast() { 37 | $result = Functions::getInstance()->arrayKeyLast(['abc' => 'a', 'as', 'z' => 'ass']); 38 | $this->assertEquals('z', $result); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /tests/TestCase/Tools/JsTest.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | 11 | namespace DataTables\Test\TestCase\Tools; 12 | 13 | use Cake\TestSuite\TestCase; 14 | use DataTables\Tools\Js; 15 | 16 | /** 17 | * Class JsTest 18 | * 19 | * @author Allan Carvalho 20 | * @license MIT License https://github.com/allanmcarvalho/cakephp-datatables/blob/master/LICENSE 21 | * @link https://github.com/allanmcarvalho/cakephp-datatables 22 | */ 23 | class JsTest extends TestCase { 24 | 25 | /** 26 | * @return void 27 | */ 28 | public function testMinify() { 29 | $js = 'function { 30 | var abc = 123; 31 | }'; 32 | $lenBefore = 47; 33 | $lenAfter = 21; 34 | $this->assertEquals($lenBefore, strlen($js)); 35 | $this->assertEquals($lenAfter, strlen(Js::minifyFile($js))); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /tests/TestCase/Tools/ValidatorTest.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | 11 | namespace DataTables\Test\TestCase\Tools; 12 | 13 | use Cake\Error\FatalErrorException; 14 | use Cake\TestSuite\TestCase; 15 | use DataTables\Tools\Validator; 16 | use InvalidArgumentException; 17 | 18 | class ValidatorTest extends TestCase { 19 | 20 | /** 21 | * @return void 22 | */ 23 | public function testKeysValueTypesOrFailInvalid1() { 24 | $this->expectException(FatalErrorException::class); 25 | Validator::getInstance()->checkKeysValueTypesOrFail(['abc'], 1, 'array'); 26 | } 27 | 28 | /** 29 | * @return void 30 | */ 31 | public function testKeysValueTypesOrFailInvalid2() { 32 | $this->expectException(FatalErrorException::class); 33 | Validator::getInstance()->checkKeysValueTypesOrFail(['abc'], 'integer', 1); 34 | } 35 | 36 | /** 37 | * @return void 38 | */ 39 | public function testKeysValueTypesOrFailInvalid3() { 40 | $this->expectException(InvalidArgumentException::class); 41 | Validator::getInstance()->checkKeysValueTypesOrFail(['abc'], 'string', 'string'); 42 | } 43 | 44 | /** 45 | * @return void 46 | */ 47 | public function testKeysValueTypesOrFailInvalid4() { 48 | $this->expectException(InvalidArgumentException::class); 49 | Validator::getInstance()->checkKeysValueTypesOrFail([[]], 'integer', 'string'); 50 | } 51 | 52 | /** 53 | * @return void 54 | */ 55 | public function testArraySizeOrFail() { 56 | $this->expectException(InvalidArgumentException::class); 57 | Validator::getInstance()->checkArraySizeOrFail(['a', 'b', 'c'], 2); 58 | } 59 | 60 | /** 61 | * @return void 62 | */ 63 | public function testArraySizeOrFail1() { 64 | $this->expectException(InvalidArgumentException::class); 65 | Validator::getInstance()->checkArraySizeOrFail(['a', 'b', 'c'], 2, 'CustomMessage'); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /tests/TestCase/View/Cell/DataTablesCellTest.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | declare(strict_types = 1); 11 | 12 | namespace DataTables\Test\TestCase\View\Cell; 13 | 14 | use Cake\TestSuite\TestCase; 15 | use DataTables\View\Cell\DataTablesCell; 16 | 17 | /** 18 | * Class DataTablesCellTest 19 | * 20 | * @author Allan Carvalho 21 | * @license MIT License https://github.com/allanmcarvalho/cakephp-datatables/blob/master/LICENSE 22 | * @link https://github.com/allanmcarvalho/cakephp-datatables 23 | */ 24 | class DataTablesCellTest extends TestCase { 25 | 26 | /** 27 | * Request mock 28 | * 29 | * @var \Cake\Http\ServerRequest|\PHPUnit\Framework\MockObject\MockObject 30 | */ 31 | protected $request; 32 | 33 | /** 34 | * Response mock 35 | * 36 | * @var \Cake\Http\Response|\PHPUnit\Framework\MockObject\MockObject 37 | */ 38 | protected $response; 39 | 40 | /** 41 | * Test subject 42 | * 43 | * @var \DataTables\View\Cell\DataTablesCell 44 | */ 45 | protected $DataTables; 46 | 47 | /** 48 | * setUp method 49 | * 50 | * @return void 51 | */ 52 | public function setUp(): void { 53 | parent::setUp(); 54 | $this->request = $this->getMockBuilder('Cake\Http\ServerRequest')->getMock(); 55 | $this->response = $this->getMockBuilder('Cake\Http\Response')->getMock(); 56 | $this->DataTables = new DataTablesCell($this->request, $this->response); 57 | } 58 | 59 | /** 60 | * tearDown method 61 | * 62 | * @return void 63 | */ 64 | public function tearDown(): void { 65 | unset($this->DataTables); 66 | 67 | parent::tearDown(); 68 | } 69 | 70 | /** 71 | * Test table method 72 | * 73 | * @return void 74 | */ 75 | public function testTable(): void { 76 | $this->assertEquals(1, 1); 77 | //$this->markTestIncomplete('Not implemented yet.'); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /tests/TestCase/View/Helper/DataTablesHelperTest.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | declare(strict_types = 1); 11 | 12 | namespace DataTables\Test\TestCase\View\Helper; 13 | 14 | use Cake\TestSuite\TestCase; 15 | use Cake\View\View; 16 | use DataTables\View\Helper\DataTablesHelper; 17 | 18 | /** 19 | * Class DataTablesHelperTest 20 | * 21 | * @author Allan Carvalho 22 | * @license MIT License https://github.com/allanmcarvalho/cakephp-datatables/blob/master/LICENSE 23 | * @link https://github.com/allanmcarvalho/cakephp-datatables 24 | */ 25 | class DataTablesHelperTest extends TestCase { 26 | 27 | /** 28 | * Test subject 29 | * 30 | * @var \DataTables\View\Helper\DataTablesHelper 31 | */ 32 | protected $DataTables; 33 | 34 | /** 35 | * setUp method 36 | * 37 | * @return void 38 | */ 39 | public function setUp(): void { 40 | parent::setUp(); 41 | $view = new View(); 42 | $this->DataTables = new DataTablesHelper($view); 43 | } 44 | 45 | /** 46 | * tearDown method 47 | * 48 | * @return void 49 | */ 50 | public function tearDown(): void { 51 | unset($this->DataTables); 52 | 53 | parent::tearDown(); 54 | } 55 | 56 | /** 57 | * Test renderTable method 58 | * 59 | * @return void 60 | */ 61 | public function testRenderTable(): void { 62 | $this->assertEquals(1, 1); 63 | //$this->markTestIncomplete('Not implemented yet.'); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | declare(strict_types = 1); 11 | 12 | /** 13 | * Test suite bootstrap for DataTables. 14 | * 15 | * This function is used to find the location of CakePHP whether CakePHP 16 | * has been installed as a dependency of the plugin, or the plugin is itself 17 | * installed as a dependency of an application. 18 | */ 19 | $findRoot = function ($root) { 20 | do { 21 | $lastRoot = $root; 22 | $root = dirname($root); 23 | if (is_dir($root . '/vendor/cakephp/cakephp')) { 24 | return $root; 25 | } 26 | } while ($root !== $lastRoot); 27 | 28 | throw new Exception('Cannot find the root of the application, unable to run tests'); 29 | }; 30 | $root = $findRoot(__FILE__); 31 | unset($findRoot); 32 | 33 | if (file_exists($root . DS . 'config' . DS . 'paths.php')) { 34 | require_once $root . DS . 'config' . DS . 'paths.php'; 35 | } 36 | 37 | chdir($root); 38 | require_once $root . '/vendor/autoload.php'; 39 | 40 | /** 41 | * Define fallback values for required constants and configuration. 42 | * To customize constants and configuration remove this require 43 | * and define the data required by your plugin here. 44 | */ 45 | require_once $root . '/vendor/cakephp/cakephp/tests/bootstrap.php'; 46 | 47 | class_alias(TestApp\Controller\AppController::class, 'App\Controller\AppController'); 48 | if (file_exists($root . '/config/bootstrap.php')) { 49 | require $root . '/config/bootstrap.php'; 50 | return; 51 | } 52 | -------------------------------------------------------------------------------- /tests/phpstan.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | level: 8 3 | autoload_files: 4 | - bootstrap.php 5 | checkMissingIterableValueType: false 6 | checkGenericClassInNonGenericObjectType: false 7 | ignoreErrors: 8 | -------------------------------------------------------------------------------- /tests/test_app/src/Application.php: -------------------------------------------------------------------------------- 1 | add(new RoutingMiddleware($this)); 18 | 19 | return $middlewareQueue; 20 | } 21 | 22 | /** 23 | * @return void 24 | */ 25 | public function bootstrap(): void { 26 | $this->addPlugin(Plugin::class); 27 | parent::bootstrap(); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /tests/test_app/src/Controller/AppController.php: -------------------------------------------------------------------------------- 1 | loadComponent('Flash'); 17 | } 18 | 19 | /** 20 | * @param \Cake\Event\EventInterface $event 21 | * 22 | * @return \Cake\Http\Response|null|void 23 | */ 24 | public function beforeRender(EventInterface $event) { 25 | parent::beforeRender($event); 26 | 27 | $this->viewBuilder()->setHelpers(['Tools.Time', 'Tools.Format']); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /tests/test_app/src/DataTables/Tables/CategoriesTables.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | declare(strict_types = 1); 11 | 12 | namespace TestApp\DataTables\Tables; 13 | 14 | use DataTables\Table\ConfigBundle; 15 | use DataTables\Table\Tables; 16 | 17 | /** 18 | * Class CategoriesTables 19 | * 20 | * @author Allan Carvalho 21 | * @license MIT License https://github.com/allanmcarvalho/cakephp-datatables/blob/master/LICENSE 22 | * @link https://github.com/allanmcarvalho/cakephp-datatables 23 | */ 24 | class CategoriesTables extends Tables { 25 | 26 | /** 27 | * @var string 28 | */ 29 | protected $_ormTableName = 'Categories'; 30 | 31 | /** 32 | * @param \DataTables\Table\ConfigBundle $configBundle 33 | * @return void 34 | */ 35 | public function mainConfig(ConfigBundle $configBundle): void { 36 | 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /tests/test_app/src/View/AppView.php: -------------------------------------------------------------------------------- 1 |