From f1c59f7591427dd655530b12f166b86122223a37 Mon Sep 17 00:00:00 2001 From: josef Date: Wed, 27 Mar 2024 13:02:42 +0100 Subject: [PATCH 01/38] add phpstan config #3 --- phpstan.neon | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 phpstan.neon diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..04e3e63 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,4 @@ +parameters: + level: 9 + scanDirectories: + - vendor From 3f9e7360a6f39bc2c340f0d7fe332f72184b52de Mon Sep 17 00:00:00 2001 From: josef Date: Wed, 27 Mar 2024 13:03:36 +0100 Subject: [PATCH 02/38] define php >=7.4, <8.4 requirement #3 --- composer.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 9890f5a..0550096 100644 --- a/composer.json +++ b/composer.json @@ -5,7 +5,7 @@ "license": "GPL-3.0-or-later", "minimum-stability": "stable", "require": { - "php": "^7.2", + "php": ">=7.4, <8.4", "cakephp/cakephp": "^2.4", "composer/installers": "^1.9" }, @@ -13,6 +13,8 @@ "installer-name": "Filter" }, "config": { - "vendor-dir": "Vendor/" + "allow-plugins": { + "composer/installers": true + } } } From 931819ec39acc71c0425819fa0bc4ea22fd34496 Mon Sep 17 00:00:00 2001 From: josef Date: Wed, 27 Mar 2024 13:04:36 +0100 Subject: [PATCH 03/38] do not keep composer.lock in repository #3 --- .gitignore | 1 + composer.lock | 223 -------------------------------------------------- 2 files changed, 1 insertion(+), 223 deletions(-) delete mode 100644 composer.lock diff --git a/.gitignore b/.gitignore index 82dcd5b..d5ca5c3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /.buildpath /.project /Vendor/ +/composer.lock diff --git a/composer.lock b/composer.lock deleted file mode 100644 index 2bfd683..0000000 --- a/composer.lock +++ /dev/null @@ -1,223 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "282714e63af34398436c701d896aa2db", - "packages": [ - { - "name": "cakephp/cakephp", - "version": "2.10.24", - "source": { - "type": "git", - "url": "https://github.com/cakephp/cakephp.git", - "reference": "cf14e6546ec44e3369e3531add11fdb946656280" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/cakephp/cakephp/zipball/cf14e6546ec44e3369e3531add11fdb946656280", - "reference": "cf14e6546ec44e3369e3531add11fdb946656280", - "shasum": "" - }, - "require": { - "php": ">=5.3.0,<8.0.0" - }, - "require-dev": { - "cakephp/cakephp-codesniffer": "^1.0.0", - "phpunit/phpunit": "^3.7" - }, - "suggest": { - "ext-mcrypt": "You need to install ext-openssl or ext-mcrypt to use AES-256 encryption", - "ext-openssl": "You need to install ext-openssl or ext-mcrypt to use AES-256 encryption" - }, - "bin": [ - "lib/Cake/Console/cake" - ], - "type": "library", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "CakePHP Community", - "homepage": "https://github.com/cakephp/cakephp/graphs/contributors" - } - ], - "description": "The CakePHP framework", - "homepage": "https://cakephp.org", - "keywords": [ - "framework" - ], - "support": { - "forum": "https://stackoverflow.com/tags/cakephp", - "irc": "irc://irc.freenode.org/cakephp", - "issues": "https://github.com/cakephp/cakephp/issues", - "source": "https://github.com/cakephp/cakephp" - }, - "time": "2020-12-16T02:47:53+00:00" - }, - { - "name": "composer/installers", - "version": "v1.11.0", - "source": { - "type": "git", - "url": "https://github.com/composer/installers.git", - "reference": "ae03311f45dfe194412081526be2e003960df74b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/installers/zipball/ae03311f45dfe194412081526be2e003960df74b", - "reference": "ae03311f45dfe194412081526be2e003960df74b", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0 || ^2.0" - }, - "replace": { - "roundcube/plugin-installer": "*", - "shama/baton": "*" - }, - "require-dev": { - "composer/composer": "1.6.* || ^2.0", - "composer/semver": "^1 || ^3", - "phpstan/phpstan": "^0.12.55", - "phpstan/phpstan-phpunit": "^0.12.16", - "symfony/phpunit-bridge": "^4.2 || ^5", - "symfony/process": "^2.3" - }, - "type": "composer-plugin", - "extra": { - "class": "Composer\\Installers\\Plugin", - "branch-alias": { - "dev-main": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\Installers\\": "src/Composer/Installers" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Kyle Robinson Young", - "email": "kyle@dontkry.com", - "homepage": "https://github.com/shama" - } - ], - "description": "A multi-framework Composer library installer", - "homepage": "https://composer.github.io/installers/", - "keywords": [ - "Craft", - "Dolibarr", - "Eliasis", - "Hurad", - "ImageCMS", - "Kanboard", - "Lan Management System", - "MODX Evo", - "MantisBT", - "Mautic", - "Maya", - "OXID", - "Plentymarkets", - "Porto", - "RadPHP", - "SMF", - "Starbug", - "Thelia", - "Whmcs", - "WolfCMS", - "agl", - "aimeos", - "annotatecms", - "attogram", - "bitrix", - "cakephp", - "chef", - "cockpit", - "codeigniter", - "concrete5", - "croogo", - "dokuwiki", - "drupal", - "eZ Platform", - "elgg", - "expressionengine", - "fuelphp", - "grav", - "installer", - "itop", - "joomla", - "known", - "kohana", - "laravel", - "lavalite", - "lithium", - "magento", - "majima", - "mako", - "mediawiki", - "miaoxing", - "modulework", - "modx", - "moodle", - "osclass", - "phpbb", - "piwik", - "ppi", - "processwire", - "puppet", - "pxcms", - "reindex", - "roundcube", - "shopware", - "silverstripe", - "sydes", - "sylius", - "symfony", - "tastyigniter", - "typo3", - "wordpress", - "yawik", - "zend", - "zikula" - ], - "support": { - "issues": "https://github.com/composer/installers/issues", - "source": "https://github.com/composer/installers/tree/v1.11.0" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2021-04-28T06:42:17+00:00" - } - ], - "packages-dev": [], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": "^7.2" - }, - "platform-dev": [], - "plugin-api-version": "2.0.0" -} From ae7fc37fabbea30325a71e8f4c5e77f1e83a85fd Mon Sep 17 00:00:00 2001 From: Josef Stich Date: Wed, 27 Mar 2024 13:41:58 +0100 Subject: [PATCH 04/38] code reformatting #3 --- .editorconfig | 16 + .gitignore | 3 +- Controller/Component/FilterComponent.php | 688 ++++---- Controller/FilterAppController.php | 20 +- Model/Behavior/FilteredBehavior.php | 865 +++++----- Model/FilterAppModel.php | 20 +- Test/Case/AllTest.php | 32 +- .../Component/FilterComponentTest.php | 1245 +++++++-------- Test/Case/MockObjects/Document.php | 8 +- Test/Case/MockObjects/Document2.php | 18 +- Test/Case/MockObjects/Document3.php | 32 +- Test/Case/MockObjects/DocumentCategory.php | 16 +- .../MockObjects/DocumentTestsController.php | 18 +- Test/Case/MockObjects/Item.php | 4 +- Test/Case/MockObjects/Metadata.php | 4 +- .../Model/Behaviors/FilteredBehaviorTest.php | 1393 ++++++++--------- Test/Fixture/DocumentCategoryFixture.php | 48 +- Test/Fixture/DocumentFixture.php | 60 +- Test/Fixture/ItemFixture.php | 58 +- Test/Fixture/MetadataFixture.php | 52 +- View/Elements/filter_form_begin.ctp | 57 +- View/Elements/filter_form_end.ctp | 26 +- View/Elements/filter_form_fields.ctp | 43 +- View/Helper/FilterHelper.php | 161 +- 24 files changed, 2303 insertions(+), 2584 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..1d896fa --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +trim_trailing_whitespace = true + +# 4 space indentation +[*.{php,ctp}] +indent_style = space +indent_size = 4 \ No newline at end of file diff --git a/.gitignore b/.gitignore index d5ca5c3..97728e5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ +/.idea/ /.settings/ /.buildpath /.project -/Vendor/ +/vendor/ /composer.lock diff --git a/Controller/Component/FilterComponent.php b/Controller/Component/FilterComponent.php index 0e6a8fb..32c380f 100644 --- a/Controller/Component/FilterComponent.php +++ b/Controller/Component/FilterComponent.php @@ -1,15 +1,15 @@ - - Multi-licensed under: - MPL - LGPL - GPL -*/ + * CakePHP Filter Plugin + * + * Copyright (C) 2009-3827 dr. Hannibal Lecter / lecterror + * + * + * Multi-licensed under: + * MPL + * LGPL + * GPL + */ App::import('Component', 'Session'); App::import('Behavior', 'Filter.Filtered'); @@ -20,370 +20,306 @@ */ class FilterComponent extends Component { - public $components = array('Session'); - - public $settings = array(); - public $nopersist = array(); - public $formData = array(); - protected $_request_settings = array(); - - public function __construct(ComponentCollection $collection, $settings = array()) - { - parent::__construct($collection, $settings); - $this->_request_settings = $settings; - } - - public function initialize(Controller $controller) - { - if (!isset($controller->filters)) - { - return; - } - - $this->__updatePersistence($controller, $this->_request_settings); - $this->settings[$controller->name] = $controller->filters; - - if (!isset($this->settings[$controller->name][$controller->request->action])) - { - return; - } - - $settings = $this->settings[$controller->name][$controller->request->action]; - - foreach ($settings as $model => $filter) - { - if (!isset($controller->{$model})) - { - trigger_error(__('Filter model not found: %s', $model)); - continue; - } - - $controller->$model->Behaviors->attach('Filter.Filtered', $filter); - } - } - - public function startup(Controller $controller) - { - if (!isset($this->settings[$controller->name][$controller->request->action])) - { - return; - } - - $settings = $this->settings[$controller->name][$controller->request->action]; - - if (!in_array('Filter.Filter', $controller->helpers)) - { - $controller->helpers[] = 'Filter.Filter'; - } - - $sessionKey = sprintf('FilterPlugin.Filters.%s.%s', $controller->name, $controller->request->action); - $filterFormId = $controller->request->query('filterFormId'); - if ($controller->request->is('get') && !empty($filterFormId)) - { - $this->formData = $controller->request->query('data'); - } - elseif (!$controller->request->is('post') || !isset($controller->request->data['Filter']['filterFormId'])) - { - $persistedData = array(); - - if ($this->Session->check($sessionKey)) - { - $persistedData = $this->Session->read($sessionKey); - } - - if (empty($persistedData)) - { - return; - } - - $this->formData = $persistedData; - } - else - { - $this->formData = $controller->request->data; - if ($this->Session->started()) - { - $this->Session->write($sessionKey, $this->formData); - } - } - - foreach ($settings as $model => $options) - { - if (!isset($controller->{$model})) - { - trigger_error(__('Filter model not found: %s', $model)); - continue; - } - - $controller->$model->setFilterValues($this->formData); - } - } - - public function beforeRender(Controller $controller) - { - if (!isset($this->settings[$controller->name][$controller->request->action])) - { - return; - } - - $models = $this->settings[$controller->name][$controller->request->action]; - $viewFilterParams = array(); - - foreach ($models as $model => $fields) - { - if (!isset($controller->$model)) - { - trigger_error(__('Filter model not found: %s', $model)); - continue; - } - - foreach ($fields as $field => $settings) - { - if (!is_array($settings)) - { - $field = $settings; - $settings = array(); - } - - if (!isset($settings['required'])) - { - $settings['required'] = false; - } - - if (!isset($settings['type'])) - { - $settings['type'] = 'text'; - } - - $options = array(); - - $fieldName = $field; - $fieldModel = $model; - - if (strpos($field, '.') !== false) - { - list($fieldModel, $fieldName) = explode('.', $field); - } - - if (!empty($this->formData)) - { - if (isset($this->formData[$fieldModel][$fieldName])) - { - $options['value'] = $this->formData[$fieldModel][$fieldName]; - - if ($options['value']) - { - $options['class'] = 'filter-active'; - } - } - } - - if (isset($settings['inputOptions'])) - { - if (!is_array($settings['inputOptions'])) - { - $settings['inputOptions'] = array($settings['inputOptions']); - } - - $options = array_merge($options, $settings['inputOptions']); - } - - if (isset($settings['label'])) - { - $options['label'] = $settings['label']; - } - - switch ($settings['type']) - { - case 'text': - $options['type'] = 'text'; - break; - - case 'select': - $options['type'] = 'select'; - - $selectOptions = array(); - $workingModel = ClassRegistry::init($fieldModel); - - if (isset($settings['selectOptions'])) - { - $selectOptions = $settings['selectOptions']; - } - - if (isset($settings['selector'])) - { - if (!method_exists($workingModel, $settings['selector'])) - { - trigger_error - ( - __( - 'Selector method "%s" not found in model "%s" for field "%s"!', - $settings['selector'], - $fieldModel, - $fieldName - ) - ); - return; - } - - $selectorName = $settings['selector']; - $options['options'] = $workingModel->$selectorName($selectOptions); - } - else - { - if ($fieldModel == $model) - { - $options['options'] = $workingModel->find - ( - 'list', - array_merge - ( - $selectOptions, - array - ( - 'nofilter' => true, - 'fields' => array($fieldName, $fieldName), - ) - ) - ); - } - else - { - $options['options'] = $workingModel->find('list', array_merge($selectOptions, array('nofilter' => true))); - } - } - - if (!$settings['required']) - { - $options['empty'] = ''; - } - - if (isset($settings['multiple'])) - { - $options['multiple'] = $settings['multiple']; - } - - break; - - case 'checkbox': - $options['type'] = 'checkbox'; - - if (isset($options['value'])) - { - $options['checked'] = !!$options['value']; - unset($options['value']); - } - else if (isset($settings['default'])) - { - $options['checked'] = !!$settings['default']; - } - break; - - default: - continue 2; - } - - // if no value has been set, show the default one - if (!isset($options['value']) && - isset($settings['default']) && - $options['type'] != 'checkbox') - { - $options['value'] = $settings['default']; - } - - $viewFilterParams[] = array - ( - 'name' => sprintf('%s.%s', $fieldModel, $fieldName), - 'options' => $options - ); - } - } - - if (!empty($this->settings['add_filter_value_to_title']) && - array_search($controller->action, $this->settings['add_filter_value_to_title']) !== false) - { - $title = $controller->viewVars['title_for_layout']; - foreach ($viewFilterParams as $viewFilterParam) - { - if (!empty($viewFilterParam['options']['class']) && - $viewFilterParam['options']['class'] == 'filter-active') - { - $titleValue = $viewFilterParam['options']['value']; - if ($viewFilterParam['options']['type'] == 'select') - { - $titleValue = $viewFilterParam['options']['options'][$titleValue]; - } - $title .= ' - ' . $titleValue; - } - } - $controller->set('title_for_layout', $title); - } - $controller->set('viewFilterParams', $viewFilterParams); - } - - private function __updatePersistence($controller, $settings) - { - if ($this->Session->check('FilterPlugin.NoPersist')) - { - $this->nopersist = $this->Session->read('FilterPlugin.NoPersist'); - } - - if (isset($settings['nopersist'])) - { - $this->nopersist[$controller->name] = $settings['nopersist']; - if ($this->Session->started()) - { - $this->Session->write('FilterPlugin.NoPersist', $this->nopersist); - } - } - else if (isset($this->nopersist[$controller->name])) - { - unset($this->nopersist[$controller->name]); - if ($this->Session->started()) - { - $this->Session->write('FilterPlugin.NoPersist', $this->nopersist); - } - } - - if (!empty($this->nopersist)) - { - foreach ($this->nopersist as $nopersistController => $actions) - { - if (is_string($actions)) - { - $actions = array($actions); - } - else if ($actions === true) - { - $actions = array(); - } - - if (empty($actions) && $controller->name != $nopersistController) - { - if ($this->Session->check(sprintf('FilterPlugin.Filters.%s', $nopersistController))) - { - $this->Session->delete(sprintf('FilterPlugin.Filters.%s', $nopersistController)); - continue; - } - } - - foreach ($actions as $action) - { - if ($controller->name == $nopersistController && $action == $controller->request->action) - { - continue; - } - - if ($this->Session->check(sprintf('FilterPlugin.Filters.%s.%s', $nopersistController, $action))) - { - $this->Session->delete(sprintf('FilterPlugin.Filters.%s.%s', $nopersistController, $action)); - } - } - } - } - } - - public function shutdown(Controller $controller) - { - } + public $components = array('Session'); + + public $settings = array(); + public $nopersist = array(); + public $formData = array(); + protected $_request_settings = array(); + + public function __construct(ComponentCollection $collection, $settings = array()) + { + parent::__construct($collection, $settings); + $this->_request_settings = $settings; + } + + public function initialize(Controller $controller) + { + if (!isset($controller->filters)) { + return; + } + + $this->__updatePersistence($controller, $this->_request_settings); + $this->settings[$controller->name] = $controller->filters; + + if (!isset($this->settings[$controller->name][$controller->request->action])) { + return; + } + + $settings = $this->settings[$controller->name][$controller->request->action]; + + foreach ($settings as $model => $filter) { + if (!isset($controller->{$model})) { + trigger_error(__('Filter model not found: %s', $model)); + continue; + } + + $controller->$model->Behaviors->attach('Filter.Filtered', $filter); + } + } + + public function startup(Controller $controller) + { + if (!isset($this->settings[$controller->name][$controller->request->action])) { + return; + } + + $settings = $this->settings[$controller->name][$controller->request->action]; + + if (!in_array('Filter.Filter', $controller->helpers)) { + $controller->helpers[] = 'Filter.Filter'; + } + + $sessionKey = sprintf('FilterPlugin.Filters.%s.%s', $controller->name, $controller->request->action); + $filterFormId = $controller->request->query('filterFormId'); + if ($controller->request->is('get') && !empty($filterFormId)) { + $this->formData = $controller->request->query('data'); + } elseif (!$controller->request->is('post') || !isset($controller->request->data['Filter']['filterFormId'])) { + $persistedData = array(); + + if ($this->Session->check($sessionKey)) { + $persistedData = $this->Session->read($sessionKey); + } + + if (empty($persistedData)) { + return; + } + + $this->formData = $persistedData; + } else { + $this->formData = $controller->request->data; + if ($this->Session->started()) { + $this->Session->write($sessionKey, $this->formData); + } + } + + foreach ($settings as $model => $options) { + if (!isset($controller->{$model})) { + trigger_error(__('Filter model not found: %s', $model)); + continue; + } + + $controller->$model->setFilterValues($this->formData); + } + } + + public function beforeRender(Controller $controller) + { + if (!isset($this->settings[$controller->name][$controller->request->action])) { + return; + } + + $models = $this->settings[$controller->name][$controller->request->action]; + $viewFilterParams = array(); + + foreach ($models as $model => $fields) { + if (!isset($controller->$model)) { + trigger_error(__('Filter model not found: %s', $model)); + continue; + } + + foreach ($fields as $field => $settings) { + if (!is_array($settings)) { + $field = $settings; + $settings = array(); + } + + if (!isset($settings['required'])) { + $settings['required'] = false; + } + + if (!isset($settings['type'])) { + $settings['type'] = 'text'; + } + + $options = array(); + + $fieldName = $field; + $fieldModel = $model; + + if (strpos($field, '.') !== false) { + list($fieldModel, $fieldName) = explode('.', $field); + } + + if (!empty($this->formData)) { + if (isset($this->formData[$fieldModel][$fieldName])) { + $options['value'] = $this->formData[$fieldModel][$fieldName]; + + if ($options['value']) { + $options['class'] = 'filter-active'; + } + } + } + + if (isset($settings['inputOptions'])) { + if (!is_array($settings['inputOptions'])) { + $settings['inputOptions'] = array($settings['inputOptions']); + } + + $options = array_merge($options, $settings['inputOptions']); + } + + if (isset($settings['label'])) { + $options['label'] = $settings['label']; + } + + switch ($settings['type']) { + case 'text': + $options['type'] = 'text'; + break; + + case 'select': + $options['type'] = 'select'; + + $selectOptions = array(); + $workingModel = ClassRegistry::init($fieldModel); + + if (isset($settings['selectOptions'])) { + $selectOptions = $settings['selectOptions']; + } + + if (isset($settings['selector'])) { + if (!method_exists($workingModel, $settings['selector'])) { + trigger_error + ( + __( + 'Selector method "%s" not found in model "%s" for field "%s"!', + $settings['selector'], + $fieldModel, + $fieldName + ) + ); + return; + } + + $selectorName = $settings['selector']; + $options['options'] = $workingModel->$selectorName($selectOptions); + } else { + if ($fieldModel == $model) { + $options['options'] = $workingModel->find + ( + 'list', + array_merge + ( + $selectOptions, + array + ( + 'nofilter' => true, + 'fields' => array($fieldName, $fieldName), + ) + ) + ); + } else { + $options['options'] = $workingModel->find('list', array_merge($selectOptions, array('nofilter' => true))); + } + } + + if (!$settings['required']) { + $options['empty'] = ''; + } + + if (isset($settings['multiple'])) { + $options['multiple'] = $settings['multiple']; + } + + break; + + case 'checkbox': + $options['type'] = 'checkbox'; + + if (isset($options['value'])) { + $options['checked'] = !!$options['value']; + unset($options['value']); + } else if (isset($settings['default'])) { + $options['checked'] = !!$settings['default']; + } + break; + + default: + continue 2; + } + + // if no value has been set, show the default one + if (!isset($options['value']) && + isset($settings['default']) && + $options['type'] != 'checkbox') { + $options['value'] = $settings['default']; + } + + $viewFilterParams[] = array + ( + 'name' => sprintf('%s.%s', $fieldModel, $fieldName), + 'options' => $options + ); + } + } + + if (!empty($this->settings['add_filter_value_to_title']) + && array_search($controller->action, $this->settings['add_filter_value_to_title']) !== false + ) { + $title = $controller->viewVars['title_for_layout']; + foreach ($viewFilterParams as $viewFilterParam) { + if (!empty($viewFilterParam['options']['class']) && + $viewFilterParam['options']['class'] == 'filter-active') { + $titleValue = $viewFilterParam['options']['value']; + if ($viewFilterParam['options']['type'] == 'select') { + $titleValue = $viewFilterParam['options']['options'][$titleValue]; + } + $title .= ' - ' . $titleValue; + } + } + $controller->set('title_for_layout', $title); + } + $controller->set('viewFilterParams', $viewFilterParams); + } + + private function __updatePersistence($controller, $settings) + { + if ($this->Session->check('FilterPlugin.NoPersist')) { + $this->nopersist = $this->Session->read('FilterPlugin.NoPersist'); + } + + if (isset($settings['nopersist'])) { + $this->nopersist[$controller->name] = $settings['nopersist']; + if ($this->Session->started()) { + $this->Session->write('FilterPlugin.NoPersist', $this->nopersist); + } + } else if (isset($this->nopersist[$controller->name])) { + unset($this->nopersist[$controller->name]); + if ($this->Session->started()) { + $this->Session->write('FilterPlugin.NoPersist', $this->nopersist); + } + } + + if (!empty($this->nopersist)) { + foreach ($this->nopersist as $nopersistController => $actions) { + if (is_string($actions)) { + $actions = array($actions); + } else if ($actions === true) { + $actions = array(); + } + + if (empty($actions) && $controller->name != $nopersistController) { + if ($this->Session->check(sprintf('FilterPlugin.Filters.%s', $nopersistController))) { + $this->Session->delete(sprintf('FilterPlugin.Filters.%s', $nopersistController)); + continue; + } + } + + foreach ($actions as $action) { + if ($controller->name == $nopersistController && $action == $controller->request->action) { + continue; + } + + if ($this->Session->check(sprintf('FilterPlugin.Filters.%s.%s', $nopersistController, $action))) { + $this->Session->delete(sprintf('FilterPlugin.Filters.%s.%s', $nopersistController, $action)); + } + } + } + } + } + + public function shutdown(Controller $controller) + { + } } diff --git a/Controller/FilterAppController.php b/Controller/FilterAppController.php index 50f98c9..b79de86 100644 --- a/Controller/FilterAppController.php +++ b/Controller/FilterAppController.php @@ -1,15 +1,15 @@ - - Multi-licensed under: - MPL - LGPL - GPL -*/ + * CakePHP Filter Plugin + * + * Copyright (C) 2009-3827 dr. Hannibal Lecter / lecterror + * + * + * Multi-licensed under: + * MPL + * LGPL + * GPL + */ class FilterAppController extends AppController { diff --git a/Model/Behavior/FilteredBehavior.php b/Model/Behavior/FilteredBehavior.php index 1ff8579..805d9c0 100644 --- a/Model/Behavior/FilteredBehavior.php +++ b/Model/Behavior/FilteredBehavior.php @@ -1,464 +1,419 @@ - - Multi-licensed under: - MPL - LGPL - GPL -*/ + * CakePHP Filter Plugin + * + * Copyright (C) 2009-3827 dr. Hannibal Lecter / lecterror + * + * + * Multi-licensed under: + * MPL + * LGPL + * GPL + */ App::uses('Sanitize', 'Utility'); class FilteredBehavior extends ModelBehavior { - /** - * Keeps current values after filter form post. - * - * @var array - */ - protected $_filterValues = array(); - - public function setup(Model $Model, $settings = array()) - { - foreach ($settings as $key => $value) - { - if (!is_array($value)) - { - $key = $value; - $value = array(); - } - - $this->settings[$Model->alias][$key] = array_merge - ( - array - ( - 'type' => 'text', - 'condition' => 'like', - 'required' => false, - 'selectOptions' => array() - ), - $value - ); - } - - $this->_filterValues[$Model->alias] = array(); - } - - public function beforeFind(Model $Model, $query) - { - if (isset($query['nofilter']) && $query['nofilter'] === true) - { - return $query; - } - $callbackOptions = array(); - if (method_exists($Model, 'beforeDataFilter')) - { - $callbackOptions['values'] = $this->_filterValues[$Model->alias]; - $callbackOptions['settings'] = $this->settings[$Model->alias]; - - if (!$Model->beforeDataFilter($query, $callbackOptions)) - { - return $query; - } - } - - if (!isset($this->settings[$Model->alias])) - { - return $query; - } - - $settings = $this->settings[$Model->alias]; - $values = $this->_filterValues[$Model->alias]; - - foreach ($settings as $field => $options) - { - $this->addFieldToFilter($Model, $query, $settings, $values, $field, $options); - } - - if (method_exists($Model, 'afterDataFilter')) - { - $callbackOptions['values'] = $this->_filterValues[$Model->alias]; - $callbackOptions['settings'] = $this->settings[$Model->alias]; - - $result = $Model->afterDataFilter($query, $callbackOptions); - - if (is_array($result)) - { - $query = $result; - } - } - - return $query; - } - - protected function addFieldToFilter(&$Model, &$query, $settings, $values, $field, $field_options) - { - $configurationModelName = $Model->alias; - $configurationFieldName = $field; - - if (strpos($field, '.') !== false) - { - list($configurationModelName, $configurationFieldName) = explode('.', $field); - } - - if (!isset($values[$configurationModelName][$configurationFieldName]) && isset($field_options['default'])) - { - $values[$configurationModelName][$configurationFieldName] = $field_options['default']; - } - - if ($field_options['required'] && !isset($values[$configurationModelName][$configurationFieldName])) - { - // TODO: implement a bit of a user friendly handling of this scenario.. - trigger_error(__('No value present for required field %s and default value not present', $field)); - return; - } - - if (!isset($values[$configurationModelName][$configurationFieldName]) || (empty($values[$configurationModelName][$configurationFieldName]) && $values[$configurationModelName][$configurationFieldName] != 0)) - { - // no value to filter with, just skip this field - return; - } - - // the value we get as condition and where it comes from is not the same as the - // model and field we're using to filter the data - $filterFieldName = $configurationFieldName; - $filterModelName = $configurationModelName; - $linkModelName = null; - $relationType = null; - - if ($configurationModelName != $Model->alias) - { - $relationTypes = array('hasMany', 'hasOne', 'belongsTo', 'hasAndBelongsToMany'); - - foreach ($relationTypes as $type) - { - if ($type == 'hasAndBelongsToMany') { - if (!empty($Model->{$configurationModelName})) { - $configurationModelAlias = $Model->{$configurationModelName}->alias; - if (!empty($Model->{$type}[$configurationModelAlias])) { - $linkModelName = $Model->{$type}[$configurationModelAlias]['with']; - } - } - } - if (isset($Model->{$type}) && isset($Model->{$type}[$configurationModelName])) - { - $filterModelName = 'Filter'.$configurationModelName; - $relationType = $type; - break; - } - } - } - - if (isset($field_options['filterField'])) - { - if (strpos($field_options['filterField'], '.') !== false) - { - list($filterModelName, $filterFieldName) = explode('.', $field_options['filterField']); - - if ($filterModelName != $Model->alias) - { - $filterModelName = 'Filter'.$filterModelName; - } - } - else - { - $filterModelName = $Model->alias; - $filterFieldName = $field_options['filterField']; - } - } - - $realFilterField = sprintf('%s.%s', $filterModelName, $filterFieldName); - - if (isset($Model->{$relationType}) && isset($Model->{$relationType}[$configurationModelName])) - { - $relatedModel = $Model->{$configurationModelName}; - $relatedModelAlias = 'Filter'.$relatedModel->alias; - - if (!Set::matches(sprintf('/joins[alias=%s]', $relatedModelAlias), $query)) - { - $joinStatements = $this->buildFilterJoin($Model, $relatedModel, $linkModelName); - foreach ($joinStatements as $joinStatement) - { - $query['joins'][] = $joinStatement; - } - } - } - - $this->buildFilterConditions - ( - $query, - $realFilterField, - $field_options, - $values[$configurationModelName][$configurationFieldName] - ); - } - - /** - * Build join conditions from Model to relatedModel. - * - * @param Model $Model - * @param Model $relatedModel - * @return array Cake join array - */ - protected function buildFilterJoin(Model &$Model, Model &$relatedModel, $linkModelName) - { - $conditions = array(); - $relationTypes = array('hasMany', 'hasOne', 'belongsTo', 'hasAndBelongsToMany'); - - $relatedModelAlias = null; - $relationType = null; - $linkModelAlias = null; - - foreach ($relationTypes as $type) - { - if (isset($Model->{$type}) && isset($Model->{$type}[$relatedModel->alias])) - { - if (!empty($linkModelName)) - { - $linkModelAlias = $Model->{$linkModelName}->alias; - } - $relatedModelAlias = 'Filter'.$relatedModel->alias; - $relationType = $type; - break; - } - } - $linkConditions = array(); - if (isset($Model->{$relationType}[$relatedModel->alias]['foreignKey']) - && $Model->{$relationType}[$relatedModel->alias]['foreignKey']) - { - if ($relationType == 'belongsTo') - { - $conditions[] = sprintf - ( - '%s.%s = %s.%s', - $Model->alias, $Model->{$relationType}[$relatedModel->alias]['foreignKey'], - $relatedModelAlias, $relatedModel->primaryKey - ); - } - else if (in_array($relationType, array('hasMany', 'hasOne'))) - { - $conditions[] = sprintf - ( - '%s.%s = %s.%s', - $Model->alias, $Model->primaryKey, - $relatedModelAlias, $Model->{$relationType}[$relatedModel->alias]['foreignKey'] - ); - } - else if ($relationType == 'hasAndBelongsToMany') - { - $conditions[] = sprintf - ( - '%s.%s = %s.%s', - $Model->{$relationType}[$relatedModel->alias]['with'], $Model->{$relationType}[$relatedModel->alias]['associationForeignKey'], - $relatedModelAlias, $relatedModel->primaryKey - ); - - $linkConditions[] = sprintf - ( - '%s.%s = %s.%s', - $Model->alias, $Model->primaryKey, - $linkModelAlias, $Model->{$relationType}[$relatedModel->alias]['foreignKey'] - ); - } - } - - // merge any custom conditions from the relation, but change - // the alias to our $relatedModelAlias - if (isset($Model->{$relationType}[$relatedModel->alias]['conditions']) && - !empty($Model->{$relationType}[$relatedModel->alias]['conditions'])) - { - $customConditions = $Model->{$relationType}[$relatedModel->alias]['conditions']; - - if (!is_array($Model->{$relationType}[$relatedModel->alias]['conditions'])) - { - $customConditions = array($customConditions); - } - - $filterConditions = preg_replace(sprintf('#(?alias), $relatedModelAlias, $customConditions); - $conditions = array_merge($conditions, $filterConditions); - } - - $return = array - ( - array - ( - 'table' => $relatedModel->table, - 'alias' => $relatedModelAlias, - 'type' => 'LEFT', - 'conditions' => $conditions, - ) - ); - - if (!empty($linkModelName)) - { - $return = array - ( - array - ( - 'table' => $Model->{$linkModelName}->table, - 'alias' => $linkModelAlias, - 'type' => 'LEFT', - 'conditions' => $linkConditions, - ), - array - ( - 'table' => $relatedModel->table, - 'alias' => $relatedModelAlias, - 'type' => 'LEFT', - 'conditions' => $conditions, - ) - ); - } - return $return; - } - - /** - * Build query conditions and add them to $query. - * - * @param array $query Cake query array. - * @param string $field Filter field. - * @param array $options Configuration options for this field. - * @param mixed $value Field value. - */ - protected function buildFilterConditions(array &$query, $field, $options, $value) - { - $conditionFieldFormats = array - ( - 'like' => '%s like', - 'ilike' => '%s ilike', - 'contains' => '%s like', - 'startswith' => '%s like', - 'endswith' => '%s like', - 'equal' => '%s', - 'equals' => '%s', - '=' => '%s', - ); - $conditionValueFormats = array - ( - 'like' => '%%%s%%', - 'ilike' => '%%%s%%', - 'contains' => '%%%s%%', - 'startswith' => '%s%%', - 'endswith' => '%%%s', - 'equal' => '%s', - 'equals' => '%s', - '=' => '%s', - ); - - switch ($options['type']) - { - case 'text': - if (strlen(trim(strval($value))) == 0) - { - break; - } - - $condition = $options['condition']; - - switch ($condition) - { - case 'like': - case 'ilike': - case 'contains': - case 'startswith': - case 'endswith': - case 'equal': - case 'equals': - case '=': - $formattedField = sprintf($conditionFieldFormats[$condition], $field); - $formattedValue = sprintf($conditionValueFormats[$condition], $value); - - $query['conditions'][$formattedField] = $formattedValue; - break; - default: - { - $query['conditions'][$field.' '.$condition] = $value; - } - break; - } - - break; - case 'select': - if (is_string($value) && strlen(trim(strval($value))) == 0) - { - break; - } - - $query['conditions'][$field] = $value; - break; - case 'checkbox': - $query['conditions'][$field] = $value; - break; - } - } - - /** - * Makes a string SQL-safe. - * - * @param string $string String to sanitize. - * @param string $connection Database connection being used. - * @return string SQL safe string. - */ - private function __escape($string, $connection = 'default') - { - if (is_numeric($string) || $string === null || is_bool($string)) { - return $string; - } - $db = ConnectionManager::getDataSource($connection); - $string = $db->value($string, 'string'); - $start = 1; - if ($string{0} === 'N') { - $start = 2; - } - return substr(substr($string, $start), 0, -1); - } - - /** - * Makes an array SQL-safe. - * - * @param string|array $data Data to sanitize. - * @param string $options DB connection being used. - * @return mixed Sanitized data. - */ - private function __clean($data, $connection = 'default') - { - if (empty($data)) { - return $data; - } - if (is_array($data)) { - foreach ($data as $key => $val) { - $data[$key] = $this->__clean($val, $connection); - } - return $data; - } - return $this->__escape($data, $connection); - } - - /** - * Sets filter values. - * - * @param Model $Model Current model. - * @param array $values Filter values. - */ - public function setFilterValues(&$Model, $values = array()) - { - $values = $this->__clean($values, $Model->useDbConfig); - $this->_filterValues[$Model->alias] = array_merge($this->_filterValues[$Model->alias], (array)$values); - } - - /** - * Gets filter values. - * - * @param Model $Model Current model. - * @return array - */ - public function getFilterValues($Model) - { - return $this->_filterValues; - } + /** + * Keeps current values after filter form post. + * + * @var array + */ + protected $_filterValues = array(); + + public function setup(Model $Model, $settings = array()) + { + foreach ($settings as $key => $value) { + if (!is_array($value)) { + $key = $value; + $value = array(); + } + + $this->settings[$Model->alias][$key] = array_merge( + array( + 'type' => 'text', + 'condition' => 'like', + 'required' => false, + 'selectOptions' => array() + ), + $value + ); + } + + $this->_filterValues[$Model->alias] = array(); + } + + public function beforeFind(Model $Model, $query) + { + if (isset($query['nofilter']) && $query['nofilter'] === true) { + return $query; + } + $callbackOptions = array(); + if (method_exists($Model, 'beforeDataFilter')) { + $callbackOptions['values'] = $this->_filterValues[$Model->alias]; + $callbackOptions['settings'] = $this->settings[$Model->alias]; + + if (!$Model->beforeDataFilter($query, $callbackOptions)) { + return $query; + } + } + + if (!isset($this->settings[$Model->alias])) { + return $query; + } + + $settings = $this->settings[$Model->alias]; + $values = $this->_filterValues[$Model->alias]; + + foreach ($settings as $field => $options) { + $this->addFieldToFilter($Model, $query, $settings, $values, $field, $options); + } + + if (method_exists($Model, 'afterDataFilter')) { + $callbackOptions['values'] = $this->_filterValues[$Model->alias]; + $callbackOptions['settings'] = $this->settings[$Model->alias]; + + $result = $Model->afterDataFilter($query, $callbackOptions); + + if (is_array($result)) { + $query = $result; + } + } + + return $query; + } + + protected function addFieldToFilter(&$Model, &$query, $settings, $values, $field, $field_options) + { + $configurationModelName = $Model->alias; + $configurationFieldName = $field; + + if (strpos($field, '.') !== false) { + list($configurationModelName, $configurationFieldName) = explode('.', $field); + } + + if (!isset($values[$configurationModelName][$configurationFieldName]) && isset($field_options['default'])) { + $values[$configurationModelName][$configurationFieldName] = $field_options['default']; + } + + if ($field_options['required'] && !isset($values[$configurationModelName][$configurationFieldName])) { + // TODO: implement a bit of a user friendly handling of this scenario.. + trigger_error(__('No value present for required field %s and default value not present', $field)); + return; + } + + if (!isset($values[$configurationModelName][$configurationFieldName]) || (empty($values[$configurationModelName][$configurationFieldName]) && $values[$configurationModelName][$configurationFieldName] != 0)) { + // no value to filter with, just skip this field + return; + } + + // the value we get as condition and where it comes from is not the same as the + // model and field we're using to filter the data + $filterFieldName = $configurationFieldName; + $filterModelName = $configurationModelName; + $linkModelName = null; + $relationType = null; + + if ($configurationModelName != $Model->alias) { + $relationTypes = array('hasMany', 'hasOne', 'belongsTo', 'hasAndBelongsToMany'); + + foreach ($relationTypes as $type) { + if ($type == 'hasAndBelongsToMany') { + if (!empty($Model->{$configurationModelName})) { + $configurationModelAlias = $Model->{$configurationModelName}->alias; + if (!empty($Model->{$type}[$configurationModelAlias])) { + $linkModelName = $Model->{$type}[$configurationModelAlias]['with']; + } + } + } + if (isset($Model->{$type}) && isset($Model->{$type}[$configurationModelName])) { + $filterModelName = 'Filter' . $configurationModelName; + $relationType = $type; + break; + } + } + } + + if (isset($field_options['filterField'])) { + if (strpos($field_options['filterField'], '.') !== false) { + list($filterModelName, $filterFieldName) = explode('.', $field_options['filterField']); + + if ($filterModelName != $Model->alias) { + $filterModelName = 'Filter' . $filterModelName; + } + } else { + $filterModelName = $Model->alias; + $filterFieldName = $field_options['filterField']; + } + } + + $realFilterField = sprintf('%s.%s', $filterModelName, $filterFieldName); + + if (isset($Model->{$relationType}) && isset($Model->{$relationType}[$configurationModelName])) { + $relatedModel = $Model->{$configurationModelName}; + $relatedModelAlias = 'Filter' . $relatedModel->alias; + + if (!Set::matches(sprintf('/joins[alias=%s]', $relatedModelAlias), $query)) { + $joinStatements = $this->buildFilterJoin($Model, $relatedModel, $linkModelName); + foreach ($joinStatements as $joinStatement) { + $query['joins'][] = $joinStatement; + } + } + } + + $this->buildFilterConditions( + $query, + $realFilterField, + $field_options, + $values[$configurationModelName][$configurationFieldName] + ); + } + + /** + * Build join conditions from Model to relatedModel. + * + * @param Model $Model + * @param Model $relatedModel + * @return array Cake join array + */ + protected function buildFilterJoin(Model &$Model, Model &$relatedModel, $linkModelName) + { + $conditions = array(); + $relationTypes = array('hasMany', 'hasOne', 'belongsTo', 'hasAndBelongsToMany'); + + $relatedModelAlias = null; + $relationType = null; + $linkModelAlias = null; + + foreach ($relationTypes as $type) { + if (isset($Model->{$type}) && isset($Model->{$type}[$relatedModel->alias])) { + if (!empty($linkModelName)) { + $linkModelAlias = $Model->{$linkModelName}->alias; + } + $relatedModelAlias = 'Filter' . $relatedModel->alias; + $relationType = $type; + break; + } + } + $linkConditions = array(); + if (isset($Model->{$relationType}[$relatedModel->alias]['foreignKey']) + && $Model->{$relationType}[$relatedModel->alias]['foreignKey']) { + if ($relationType == 'belongsTo') { + $conditions[] = sprintf + ( + '%s.%s = %s.%s', + $Model->alias, $Model->{$relationType}[$relatedModel->alias]['foreignKey'], + $relatedModelAlias, $relatedModel->primaryKey + ); + } else if (in_array($relationType, array('hasMany', 'hasOne'))) { + $conditions[] = sprintf + ( + '%s.%s = %s.%s', + $Model->alias, $Model->primaryKey, + $relatedModelAlias, $Model->{$relationType}[$relatedModel->alias]['foreignKey'] + ); + } else if ($relationType == 'hasAndBelongsToMany') { + $conditions[] = sprintf + ( + '%s.%s = %s.%s', + $Model->{$relationType}[$relatedModel->alias]['with'], $Model->{$relationType}[$relatedModel->alias]['associationForeignKey'], + $relatedModelAlias, $relatedModel->primaryKey + ); + + $linkConditions[] = sprintf + ( + '%s.%s = %s.%s', + $Model->alias, $Model->primaryKey, + $linkModelAlias, $Model->{$relationType}[$relatedModel->alias]['foreignKey'] + ); + } + } + + // merge any custom conditions from the relation, but change + // the alias to our $relatedModelAlias + if (isset($Model->{$relationType}[$relatedModel->alias]['conditions']) && + !empty($Model->{$relationType}[$relatedModel->alias]['conditions'])) { + $customConditions = $Model->{$relationType}[$relatedModel->alias]['conditions']; + + if (!is_array($Model->{$relationType}[$relatedModel->alias]['conditions'])) { + $customConditions = array($customConditions); + } + + $filterConditions = preg_replace(sprintf('#(?alias), $relatedModelAlias, $customConditions); + $conditions = array_merge($conditions, $filterConditions); + } + + $return = array + ( + array + ( + 'table' => $relatedModel->table, + 'alias' => $relatedModelAlias, + 'type' => 'LEFT', + 'conditions' => $conditions, + ) + ); + + if (!empty($linkModelName)) { + $return = array + ( + array + ( + 'table' => $Model->{$linkModelName}->table, + 'alias' => $linkModelAlias, + 'type' => 'LEFT', + 'conditions' => $linkConditions, + ), + array + ( + 'table' => $relatedModel->table, + 'alias' => $relatedModelAlias, + 'type' => 'LEFT', + 'conditions' => $conditions, + ) + ); + } + return $return; + } + + /** + * Build query conditions and add them to $query. + * + * @param array $query Cake query array. + * @param string $field Filter field. + * @param array $options Configuration options for this field. + * @param mixed $value Field value. + */ + protected function buildFilterConditions(array &$query, $field, $options, $value) + { + $conditionFieldFormats = array ( + 'like' => '%s like', + 'ilike' => '%s ilike', + 'contains' => '%s like', + 'startswith' => '%s like', + 'endswith' => '%s like', + 'equal' => '%s', + 'equals' => '%s', + '=' => '%s', + ); + $conditionValueFormats = array ( + 'like' => '%%%s%%', + 'ilike' => '%%%s%%', + 'contains' => '%%%s%%', + 'startswith' => '%s%%', + 'endswith' => '%%%s', + 'equal' => '%s', + 'equals' => '%s', + '=' => '%s', + ); + + switch ($options['type']) { + case 'text': + if (strlen(trim(strval($value))) == 0) { + break; + } + + $condition = $options['condition']; + + switch ($condition) { + case 'like': + case 'ilike': + case 'contains': + case 'startswith': + case 'endswith': + case 'equal': + case 'equals': + case '=': + $formattedField = sprintf($conditionFieldFormats[$condition], $field); + $formattedValue = sprintf($conditionValueFormats[$condition], $value); + + $query['conditions'][$formattedField] = $formattedValue; + break; + default: + { + $query['conditions'][$field . ' ' . $condition] = $value; + } + break; + } + + break; + case 'select': + if (is_string($value) && strlen(trim(strval($value))) == 0) { + break; + } + + $query['conditions'][$field] = $value; + break; + case 'checkbox': + $query['conditions'][$field] = $value; + break; + } + } + + /** + * Makes a string SQL-safe. + * + * @param string $string String to sanitize. + * @param string $connection Database connection being used. + * @return string SQL safe string. + */ + private function __escape($string, $connection = 'default') + { + if (is_numeric($string) || $string === null || is_bool($string)) { + return $string; + } + $db = ConnectionManager::getDataSource($connection); + $string = $db->value($string, 'string'); + $start = 1; + if ($string{0} === 'N') { + $start = 2; + } + return substr(substr($string, $start), 0, -1); + } + + /** + * Makes an array SQL-safe. + * + * @param string|array $data Data to sanitize. + * @param string $options DB connection being used. + * @return mixed Sanitized data. + */ + private function __clean($data, $connection = 'default') + { + if (empty($data)) { + return $data; + } + if (is_array($data)) { + foreach ($data as $key => $val) { + $data[$key] = $this->__clean($val, $connection); + } + return $data; + } + return $this->__escape($data, $connection); + } + + /** + * Sets filter values. + * + * @param Model $Model Current model. + * @param array $values Filter values. + */ + public function setFilterValues(&$Model, $values = array()) + { + $values = $this->__clean($values, $Model->useDbConfig); + $this->_filterValues[$Model->alias] = array_merge($this->_filterValues[$Model->alias], (array)$values); + } + + /** + * Gets filter values. + * + * @param Model $Model Current model. + * @return array + */ + public function getFilterValues($Model) + { + return $this->_filterValues; + } } diff --git a/Model/FilterAppModel.php b/Model/FilterAppModel.php index 0c66581..a980edd 100644 --- a/Model/FilterAppModel.php +++ b/Model/FilterAppModel.php @@ -1,15 +1,15 @@ - - Multi-licensed under: - MPL - LGPL - GPL -*/ + * CakePHP Filter Plugin + * + * Copyright (C) 2009-3827 dr. Hannibal Lecter / lecterror + * + * + * Multi-licensed under: + * MPL + * LGPL + * GPL + */ class FilterAppModel extends AppModel { diff --git a/Test/Case/AllTest.php b/Test/Case/AllTest.php index 09702ea..a3d0209 100644 --- a/Test/Case/AllTest.php +++ b/Test/Case/AllTest.php @@ -1,25 +1,25 @@ - - Multi-licensed under: - MPL - LGPL - GPL -*/ + * CakePHP Filter Plugin + * + * Copyright (C) 2009-3827 dr. Hannibal Lecter / lecterror + * + * + * Multi-licensed under: + * MPL + * LGPL + * GPL + */ App::uses('CakePlugin', 'Core'); class AllFilterTests extends CakeTestSuite { - public static function suite() - { - $suite = new CakeTestSuite('All FilterPlugin tests'); + public static function suite() + { + $suite = new CakeTestSuite('All FilterPlugin tests'); - $suite->addTestDirectoryRecursive(CakePlugin::path('Filter').'Test'.DS.'Case'); + $suite->addTestDirectoryRecursive(CakePlugin::path('Filter') . 'Test' . DS . 'Case'); - return $suite; - } + return $suite; + } } diff --git a/Test/Case/Controller/Component/FilterComponentTest.php b/Test/Case/Controller/Component/FilterComponentTest.php index 3e88749..b5c53c6 100644 --- a/Test/Case/Controller/Component/FilterComponentTest.php +++ b/Test/Case/Controller/Component/FilterComponentTest.php @@ -1,15 +1,15 @@ - - Multi-licensed under: - MPL - LGPL - GPL -*/ + * CakePHP Filter Plugin + * + * Copyright (C) 2009-3827 dr. Hannibal Lecter / lecterror + * + * + * Multi-licensed under: + * MPL + * LGPL + * GPL + */ App::uses('Router', 'Routing'); App::uses('Component', 'Filter.Filter'); @@ -23,662 +23,571 @@ class FilterComponentTest extends CakeTestCase { - public $fixtures = array - ( - 'plugin.filter.document_category', - 'plugin.filter.document', - 'plugin.filter.item', - 'plugin.filter.metadata', - ); - - public $Controller = null; - - public function startTest($method) - { - Router::connect('/', array('controller' => 'document_tests', 'action' => 'index')); - $request = new CakeRequest('/'); - $request->addParams(Router::parse('/')); - $this->Controller = new DocumentTestsController($request); - $this->Controller->uses = array('Document'); - - if (array_search($method, array('testPersistence')) !== false) - { - $this->Controller->components = array - ( - 'Session', - 'Filter.Filter' => array('nopersist' => true) - ); - } - else - { - $this->Controller->components = array - ( - 'Session', - 'Filter.Filter' - ); - } - - $this->Controller->constructClasses(); - $this->Controller->Session->destroy(); - $this->Controller->Components->trigger('initialize', array($this->Controller)); - } - - public function endTest($method) - { - $this->Controller->Session->destroy(); - $this->Controller = null; - } - - /** - * Test bailing out when no filters are present. - */ - public function testNoFilters() - { - $this->Controller->Components->trigger('initialize', array($this->Controller)); - $this->assertEmpty($this->Controller->Filter->settings); - $this->assertFalse($this->Controller->Document->Behaviors->enabled('Filtered')); - - $this->Controller->Components->trigger('startup', array($this->Controller)); - $this->assertFalse(in_array('Filter.Filter', $this->Controller->helpers)); - } - - /** - * Test bailing out when a filter model can't be found - * or when the current action has no filters. - */ - public function testNoModelPresentOrNoActionFilters() - { - $testSettings = array - ( - 'index' => array - ( - 'DocumentArse' => array - ( - 'DocumentFeck.drink' => array('type' => 'irrelevant') - ) - ) - ); - - $this->setExpectedException('PHPUnit_Framework_Error_Notice'); - $this->Controller->filters = $testSettings; - $this->Controller->Components->trigger('initialize', array($this->Controller)); - - $testSettings = array - ( - 'someotheraction' => array - ( - 'Document' => array - ( - 'Document.title' => array('type' => 'text') - ) - ) - ); - - - $this->Controller->filters = $testSettings; - $this->Controller->Components->trigger('initialize', array($this->Controller)); - $this->assertFalse($this->Controller->Document->Behaviors->enabled('Filtered')); - - $testSettings = array - ( - 'index' => array - ( - 'Document' => array - ( - 'Document.title' => array('type' => 'text') - ) - ), - ); - - $this->Controller->filters = $testSettings; - $this->Controller->Components->trigger('initialize', array($this->Controller)); - $this->assertTrue($this->Controller->Document->Behaviors->enabled('Filtered')); - } - - /** - * Test basic filter settings. - */ - public function testBasicFilters() - { - $testSettings = array - ( - 'index' => array - ( - 'Document' => array - ( - 'Document.title' => array('type' => 'text') - ) - ) - ); - $this->Controller->filters = $testSettings; - - $expected = array - ( - $this->Controller->name => $testSettings - ); - - $this->Controller->Components->trigger('initialize', array($this->Controller)); - $this->assertEquals($expected, $this->Controller->Filter->settings); - } - - /** - * Test running a component with no filter data. - */ - public function testEmptyStartup() - { - $testSettings = array - ( - 'index' => array - ( - 'Document' => array - ( - 'Document.title' => array('type' => 'text') - ) - ) - ); - $this->Controller->filters = $testSettings; - - $this->Controller->Components->trigger('initialize', array($this->Controller)); - $this->Controller->Components->trigger('startup', array($this->Controller)); - $this->assertTrue(in_array('Filter.Filter', $this->Controller->helpers)); - } - - /** - * Test loading filter data from session (both full and empty). - */ - public function testSessionStartupData() - { - $testSettings = array - ( - 'index' => array - ( - 'Document' => array - ( - 'Document.title' => array('type' => 'text') - ), - 'FakeNonexistant' => array - ( - 'drink' => array('type' => 'select') - ) - ) - ); - $this->Controller->filters = $testSettings; - - $sessionKey = sprintf('FilterPlugin.Filters.%s.%s', $this->Controller->name, $this->Controller->action); - - $filterValues = array(); - $this->Controller->Session->write($sessionKey, $filterValues); - $this->setExpectedException('PHPUnit_Framework_Error_Notice'); - $this->Controller->Components->trigger('initialize', array($this->Controller)); - - $this->setExpectedException('PHPUnit_Framework_Error_Notice'); - $this->Controller->Components->trigger('startup', array($this->Controller)); - $actualFilterValues = $this->Controller->Document->getFilterValues(); - $this->assertEquals - ( - $filterValues, - $actualFilterValues[$this->Controller->Document->alias] - ); - - $filterValues = array('Document' => array('title' => 'in')); - $this->Controller->Session->write($sessionKey, $filterValues); - - $this->Controller->Components->trigger('startup', array($this->Controller)); - $actualFilterValues = $this->Controller->Document->getFilterValues(); - $this->assertEquals - ( - $filterValues, - $actualFilterValues[$this->Controller->Document->alias] - ); - - $this->Controller->Session->delete($sessionKey); - } - - /** - * Test loading filter data from a post request. - */ - public function testPostStartupData() - { - $_SERVER['REQUEST_METHOD'] = 'POST'; - - $testSettings = array - ( - 'index' => array - ( - 'Document' => array - ( - 'Document.title' => array('type' => 'text') - ), - ) - ); - - $this->Controller->filters = $testSettings; - - $filterValues = array('Document' => array('title' => 'in'), 'Filter' => array('filterFormId' => 'Document')); - $this->Controller->data = $filterValues; - - $this->Controller->Components->trigger('initialize', array($this->Controller)); - $this->Controller->Components->trigger('startup', array($this->Controller)); - - $sessionKey = sprintf('FilterPlugin.Filters.%s.%s', $this->Controller->name, $this->Controller->action); - $sessionData = $this->Controller->Session->read($sessionKey); - $this->assertEquals($filterValues, $sessionData); - - $actualFilterValues = $this->Controller->Document->getFilterValues(); - $this->assertEquals - ( - $filterValues, - $actualFilterValues[$this->Controller->Document->alias] - ); - } - - /** - * Test exiting beforeRender when in an action with no settings. - */ - public function testBeforeRenderAbort() - { - $testSettings = array - ( - 'veryMuchNotIndex' => array - ( - 'Document' => array - ( - 'Document.title' => array('type' => 'text') - ) - ) - ); - $this->Controller->filters = $testSettings; - - $this->Controller->Components->trigger('initialize', array($this->Controller)); - $this->Controller->Components->trigger('startup', array($this->Controller)); - $this->Controller->Components->trigger('beforeRender', array($this->Controller)); - - $this->assertFalse(isset($this->Controller->viewVars['viewFilterParams'])); - } - - /** - * Test triggering an error when the plugin runs into a setting - * for filtering a model which cannot be found. - */ - public function testNoModelFound() - { - $testSettings = array - ( - 'index' => array - ( - 'ThisModelDoesNotExist' => array - ( - 'ThisModelDoesNotExist.title' => array('type' => 'text') - ) - ) - ); - $this->Controller->filters = $testSettings; - - $this->setExpectedException('PHPUnit_Framework_Error_Notice'); - $this->Controller->Components->trigger('initialize', array($this->Controller)); - - //$this->expectError(); - $this->Controller->Components->trigger('startup', array($this->Controller)); - - $this->setExpectedException('PHPUnit_Framework_Error_Notice'); - $this->Controller->Components->trigger('beforeRender', array($this->Controller)); - } - - /** - * Test the view variable generation for very basic filtering. - * Also tests model name detection and custom label. - */ - public function testBasicViewInfo() - { - $testSettings = array - ( - 'index' => array - ( - 'Document' => array - ( - 'title', - 'DocumentCategory.id' => array('type' => 'select', 'label' => 'Category'), - ) - ) - ); - $this->Controller->filters = $testSettings; - - $this->Controller->Components->trigger('initialize', array($this->Controller)); - $this->Controller->Components->trigger('startup', array($this->Controller)); - $this->Controller->Components->trigger('beforeRender', array($this->Controller)); - - $expected = array - ( - array('name' => 'Document.title', 'options' => array('type' => 'text')), - array - ( - 'name' => 'DocumentCategory.id', - 'options' => array - ( - 'type' => 'select', - 'options' => array - ( - 1 => 'Testing Doc', - 2 => 'Imaginary Spec', - 3 => 'Nonexistant data', - 4 => 'Illegal explosives DIY', - 5 => 'Father Ted', - ), - 'empty' => false, - 'label' => 'Category', - ) - ), - ); - - $this->assertEquals($expected, $this->Controller->viewVars['viewFilterParams']); - } - - /** - * Test passing additional inputOptions to the form - * helper, used to customize search form. - */ - public function testAdditionalInputOptions() - { - $testSettings = array - ( - 'index' => array - ( - 'Document' => array - ( - 'title' => array('inputOptions' => 'disabled'), - 'DocumentCategory.id' => array - ( - 'type' => 'select', - 'label' => 'Category', - 'inputOptions' => array('class' => 'important') - ), - ) - ) - ); - $this->Controller->filters = $testSettings; - - $this->Controller->Components->trigger('initialize', array($this->Controller)); - $this->Controller->Components->trigger('startup', array($this->Controller)); - $this->Controller->Components->trigger('beforeRender', array($this->Controller)); - - $expected = array - ( - array - ( - 'name' => 'Document.title', - 'options' => array - ( - 'type' => 'text', - 'disabled' - ) - ), - array - ( - 'name' => 'DocumentCategory.id', - 'options' => array - ( - 'type' => 'select', - 'options' => array - ( - 1 => 'Testing Doc', - 2 => 'Imaginary Spec', - 3 => 'Nonexistant data', - 4 => 'Illegal explosives DIY', - 5 => 'Father Ted', - ), - 'empty' => false, - 'label' => 'Category', - 'class' => 'important', - ) - ), - ); - - $this->assertEquals($expected, $this->Controller->viewVars['viewFilterParams']); - } - - /** - * Test data fetching for select input when custom selector - * and custom options are provided. - */ - public function testCustomSelector() - { - $testSettings = array - ( - 'index' => array - ( - 'Document' => array - ( - 'DocumentCategory.id' => array - ( - 'type' => 'select', - 'label' => 'Category', - 'selector' => 'customSelector', - 'selectOptions' => array('conditions' => array('DocumentCategory.description LIKE' => '%!%')), - ), - ) - ) - ); - $this->Controller->filters = $testSettings; - - $this->Controller->Components->trigger('initialize', array($this->Controller)); - $this->Controller->Components->trigger('startup', array($this->Controller)); - $this->Controller->Components->trigger('beforeRender', array($this->Controller)); - - $expected = array - ( - array - ( - 'name' => 'DocumentCategory.id', - 'options' => array - ( - 'type' => 'select', - 'options' => array - ( - 1 => 'Testing Doc', - 5 => 'Father Ted', - ), - 'empty' => false, - 'label' => 'Category', - ) - ), - ); - - $this->assertEquals($expected, $this->Controller->viewVars['viewFilterParams']); - } - - /** - * Test checkbox input filtering. - */ - public function testCheckboxOptions() - { - $testSettings = array - ( - 'index' => array - ( - 'Document' => array - ( - 'Document.is_private' => array - ( - 'type' => 'checkbox', - 'label' => 'Private?', - 'default' => true, - ), - ) - ) - ); - $this->Controller->filters = $testSettings; - - $this->Controller->Components->trigger('initialize', array($this->Controller)); - $this->Controller->Components->trigger('startup', array($this->Controller)); - $this->Controller->Components->trigger('beforeRender', array($this->Controller)); - - $expected = array - ( - array - ( - 'name' => 'Document.is_private', - 'options' => array - ( - 'type' => 'checkbox', - 'checked' => true, - 'label' => 'Private?', - ) - ), - ); - - $this->assertEquals($expected, $this->Controller->viewVars['viewFilterParams']); - } - - /** - * Test basic filter settings. - */ - public function testSelectMultiple() - { - $testSettings = array - ( - 'index' => array - ( - 'Document' => array - ( - 'DocumentCategory.id' => array - ( - 'type' => 'select', - 'multiple' => true, - ) - ) - ) - ); - $this->Controller->filters = $testSettings; - - $expected = array - ( - $this->Controller->name => $testSettings - ); - - $this->Controller->Components->trigger('initialize', array($this->Controller)); - $this->assertEquals($expected, $this->Controller->Filter->settings); - } - - /** - * Test select input for the model filtered. - */ - public function testSelectInputFromSameModel() - { - $testSettings = array - ( - 'index' => array - ( - 'Document' => array - ( - 'Document.title' => array - ( - 'type' => 'select', - ), - ) - ) - ); - $this->Controller->filters = $testSettings; - - $this->Controller->Components->trigger('initialize', array($this->Controller)); - $this->Controller->Components->trigger('startup', array($this->Controller)); - $this->Controller->Components->trigger('beforeRender', array($this->Controller)); - - $expected = array - ( - array - ( - 'name' => 'Document.title', - 'options' => array - ( - 'type' => 'select', - 'options' => array - ( - 'Testing Doc' => 'Testing Doc', - 'Imaginary Spec' => 'Imaginary Spec', - 'Nonexistant data' => 'Nonexistant data', - 'Illegal explosives DIY' => 'Illegal explosives DIY', - 'Father Ted' => 'Father Ted', - 'Duplicate title' => 'Duplicate title', - ), - 'empty' => '', - ) - ), - ); - - $this->assertEquals($expected, $this->Controller->viewVars['viewFilterParams']); - } - - /** - * Test disabling persistence for single action - * and for the entire controller. - */ - public function testPersistence() - { - $testSettings = array - ( - 'index' => array - ( - 'Document' => array - ( - 'Document.title' => array('type' => 'text') - ), - ) - ); - $this->Controller->filters = $testSettings; - - $sessionKey = sprintf('FilterPlugin.Filters.%s.%s', 'SomeOtherController', $this->Controller->action); - $filterValues = array('Document' => array('title' => 'in'), 'Filter' => array('filterFormId' => 'Document')); - $this->Controller->Session->write($sessionKey, $filterValues); - - $sessionKey = sprintf('FilterPlugin.Filters.%s.%s', $this->Controller->name, $this->Controller->action); - $filterValues = array('Document' => array('title' => 'in'), 'Filter' => array('filterFormId' => 'Document')); - $this->Controller->Session->write($sessionKey, $filterValues); - - $this->Controller->Filter->nopersist = array(); - $this->Controller->Filter->nopersist[$this->Controller->name] = true; - $this->Controller->Filter->nopersist['SomeOtherController'] = true; - - $this->Controller->Components->trigger('initialize', array($this->Controller)); - $this->Controller->Components->trigger('startup', array($this->Controller)); - - $expected = array($this->Controller->name => array($this->Controller->action => $filterValues)); - $this->assertEquals($expected, $this->Controller->Session->read('FilterPlugin.Filters')); - } - - /** - * Test whether filtering by belongsTo model text field - * works correctly. - */ - public function testBelongsToFilteringByText() - { - $testSettings = array - ( - 'index' => array - ( - 'Document' => array - ( - 'DocumentCategory.title' => array('type' => 'text') - ), - ) - ); - $this->Controller->filters = $testSettings; - - $this->Controller->Components->trigger('initialize', array($this->Controller)); - $this->Controller->Components->trigger('startup', array($this->Controller)); - $this->Controller->Components->trigger('beforeRender', array($this->Controller)); - - $expected = array - ( - array - ( - 'name' => 'DocumentCategory.title', - 'options' => array - ( - 'type' => 'text', - ) - ), - ); - - $this->assertEquals($expected, $this->Controller->viewVars['viewFilterParams']); - } + public $fixtures = array + ( + 'plugin.filter.document_category', + 'plugin.filter.document', + 'plugin.filter.item', + 'plugin.filter.metadata', + ); + + public $Controller = null; + + public function startTest($method) + { + Router::connect('/', array('controller' => 'document_tests', 'action' => 'index')); + $request = new CakeRequest('/'); + $request->addParams(Router::parse('/')); + $this->Controller = new DocumentTestsController($request); + $this->Controller->uses = array('Document'); + + if (array_search($method, array('testPersistence')) !== false) { + $this->Controller->components = array( + 'Session', + 'Filter.Filter' => array('nopersist' => true) + ); + } else { + $this->Controller->components = array( + 'Session', + 'Filter.Filter' + ); + } + + $this->Controller->constructClasses(); + $this->Controller->Session->destroy(); + $this->Controller->Components->trigger('initialize', array($this->Controller)); + } + + public function endTest($method) + { + $this->Controller->Session->destroy(); + $this->Controller = null; + } + + /** + * Test bailing out when no filters are present. + */ + public function testNoFilters() + { + $this->Controller->Components->trigger('initialize', array($this->Controller)); + $this->assertEmpty($this->Controller->Filter->settings); + $this->assertFalse($this->Controller->Document->Behaviors->enabled('Filtered')); + + $this->Controller->Components->trigger('startup', array($this->Controller)); + $this->assertFalse(in_array('Filter.Filter', $this->Controller->helpers)); + } + + /** + * Test bailing out when a filter model can't be found + * or when the current action has no filters. + */ + public function testNoModelPresentOrNoActionFilters() + { + $testSettings = array( + 'index' => array( + 'DocumentArse' => array( + 'DocumentFeck.drink' => array('type' => 'irrelevant') + ) + ) + ); + + $this->setExpectedException('PHPUnit_Framework_Error_Notice'); + $this->Controller->filters = $testSettings; + $this->Controller->Components->trigger('initialize', array($this->Controller)); + + $testSettings = array( + 'someotheraction' => array( + 'Document' => array( + 'Document.title' => array('type' => 'text') + ) + ) + ); + + + $this->Controller->filters = $testSettings; + $this->Controller->Components->trigger('initialize', array($this->Controller)); + $this->assertFalse($this->Controller->Document->Behaviors->enabled('Filtered')); + + $testSettings = array( + 'index' => array( + 'Document' => array( + 'Document.title' => array('type' => 'text') + ) + ), + ); + + $this->Controller->filters = $testSettings; + $this->Controller->Components->trigger('initialize', array($this->Controller)); + $this->assertTrue($this->Controller->Document->Behaviors->enabled('Filtered')); + } + + /** + * Test basic filter settings. + */ + public function testBasicFilters() + { + $testSettings = array( + 'index' => array( + 'Document' => array( + 'Document.title' => array('type' => 'text') + ) + ) + ); + $this->Controller->filters = $testSettings; + + $expected = array( + $this->Controller->name => $testSettings + ); + + $this->Controller->Components->trigger('initialize', array($this->Controller)); + $this->assertEquals($expected, $this->Controller->Filter->settings); + } + + /** + * Test running a component with no filter data. + */ + public function testEmptyStartup() + { + $testSettings = array( + 'index' => array( + 'Document' => array( + 'Document.title' => array('type' => 'text') + ) + ) + ); + $this->Controller->filters = $testSettings; + + $this->Controller->Components->trigger('initialize', array($this->Controller)); + $this->Controller->Components->trigger('startup', array($this->Controller)); + $this->assertTrue(in_array('Filter.Filter', $this->Controller->helpers)); + } + + /** + * Test loading filter data from session (both full and empty). + */ + public function testSessionStartupData() + { + $testSettings = array( + 'index' => array( + 'Document' => array( + 'Document.title' => array('type' => 'text') + ), + 'FakeNonexistant' => array( + 'drink' => array('type' => 'select') + ) + ) + ); + $this->Controller->filters = $testSettings; + + $sessionKey = sprintf('FilterPlugin.Filters.%s.%s', $this->Controller->name, $this->Controller->action); + + $filterValues = array(); + $this->Controller->Session->write($sessionKey, $filterValues); + $this->setExpectedException('PHPUnit_Framework_Error_Notice'); + $this->Controller->Components->trigger('initialize', array($this->Controller)); + + $this->setExpectedException('PHPUnit_Framework_Error_Notice'); + $this->Controller->Components->trigger('startup', array($this->Controller)); + $actualFilterValues = $this->Controller->Document->getFilterValues(); + $this->assertEquals( + $filterValues, + $actualFilterValues[$this->Controller->Document->alias] + ); + + $filterValues = array('Document' => array('title' => 'in')); + $this->Controller->Session->write($sessionKey, $filterValues); + + $this->Controller->Components->trigger('startup', array($this->Controller)); + $actualFilterValues = $this->Controller->Document->getFilterValues(); + $this->assertEquals( + $filterValues, + $actualFilterValues[$this->Controller->Document->alias] + ); + + $this->Controller->Session->delete($sessionKey); + } + + /** + * Test loading filter data from a post request. + */ + public function testPostStartupData() + { + $_SERVER['REQUEST_METHOD'] = 'POST'; + + $testSettings = array( + 'index' => array( + 'Document' => array( + 'Document.title' => array('type' => 'text') + ), + ) + ); + + $this->Controller->filters = $testSettings; + + $filterValues = array('Document' => array('title' => 'in'), 'Filter' => array('filterFormId' => 'Document')); + $this->Controller->data = $filterValues; + + $this->Controller->Components->trigger('initialize', array($this->Controller)); + $this->Controller->Components->trigger('startup', array($this->Controller)); + + $sessionKey = sprintf('FilterPlugin.Filters.%s.%s', $this->Controller->name, $this->Controller->action); + $sessionData = $this->Controller->Session->read($sessionKey); + $this->assertEquals($filterValues, $sessionData); + + $actualFilterValues = $this->Controller->Document->getFilterValues(); + $this->assertEquals( + $filterValues, + $actualFilterValues[$this->Controller->Document->alias] + ); + } + + /** + * Test exiting beforeRender when in an action with no settings. + */ + public function testBeforeRenderAbort() + { + $testSettings = array( + 'veryMuchNotIndex' => array( + 'Document' => array( + 'Document.title' => array('type' => 'text') + ) + ) + ); + $this->Controller->filters = $testSettings; + + $this->Controller->Components->trigger('initialize', array($this->Controller)); + $this->Controller->Components->trigger('startup', array($this->Controller)); + $this->Controller->Components->trigger('beforeRender', array($this->Controller)); + + $this->assertFalse(isset($this->Controller->viewVars['viewFilterParams'])); + } + + /** + * Test triggering an error when the plugin runs into a setting + * for filtering a model which cannot be found. + */ + public function testNoModelFound() + { + $testSettings = array( + 'index' => array( + 'ThisModelDoesNotExist' => array( + 'ThisModelDoesNotExist.title' => array('type' => 'text') + ) + ) + ); + $this->Controller->filters = $testSettings; + + $this->setExpectedException('PHPUnit_Framework_Error_Notice'); + $this->Controller->Components->trigger('initialize', array($this->Controller)); + + //$this->expectError(); + $this->Controller->Components->trigger('startup', array($this->Controller)); + + $this->setExpectedException('PHPUnit_Framework_Error_Notice'); + $this->Controller->Components->trigger('beforeRender', array($this->Controller)); + } + + /** + * Test the view variable generation for very basic filtering. + * Also tests model name detection and custom label. + */ + public function testBasicViewInfo() + { + $testSettings = array( + 'index' => array( + 'Document' => array( + 'title', + 'DocumentCategory.id' => array('type' => 'select', 'label' => 'Category'), + ) + ) + ); + $this->Controller->filters = $testSettings; + + $this->Controller->Components->trigger('initialize', array($this->Controller)); + $this->Controller->Components->trigger('startup', array($this->Controller)); + $this->Controller->Components->trigger('beforeRender', array($this->Controller)); + + $expected = array( + array('name' => 'Document.title', 'options' => array('type' => 'text')), + array( + 'name' => 'DocumentCategory.id', + 'options' => array( + 'type' => 'select', + 'options' => array( + 1 => 'Testing Doc', + 2 => 'Imaginary Spec', + 3 => 'Nonexistant data', + 4 => 'Illegal explosives DIY', + 5 => 'Father Ted', + ), + 'empty' => false, + 'label' => 'Category', + ) + ), + ); + + $this->assertEquals($expected, $this->Controller->viewVars['viewFilterParams']); + } + + /** + * Test passing additional inputOptions to the form + * helper, used to customize search form. + */ + public function testAdditionalInputOptions() + { + $testSettings = array( + 'index' => array( + 'Document' => array( + 'title' => array('inputOptions' => 'disabled'), + 'DocumentCategory.id' => array( + 'type' => 'select', + 'label' => 'Category', + 'inputOptions' => array('class' => 'important') + ), + ) + ) + ); + $this->Controller->filters = $testSettings; + + $this->Controller->Components->trigger('initialize', array($this->Controller)); + $this->Controller->Components->trigger('startup', array($this->Controller)); + $this->Controller->Components->trigger('beforeRender', array($this->Controller)); + + $expected = array( + array( + 'name' => 'Document.title', + 'options' => array( + 'type' => 'text', + 'disabled' + ) + ), + array( + 'name' => 'DocumentCategory.id', + 'options' => array( + 'type' => 'select', + 'options' => array( + 1 => 'Testing Doc', + 2 => 'Imaginary Spec', + 3 => 'Nonexistant data', + 4 => 'Illegal explosives DIY', + 5 => 'Father Ted', + ), + 'empty' => false, + 'label' => 'Category', + 'class' => 'important', + ) + ), + ); + + $this->assertEquals($expected, $this->Controller->viewVars['viewFilterParams']); + } + + /** + * Test data fetching for select input when custom selector + * and custom options are provided. + */ + public function testCustomSelector() + { + $testSettings = array( + 'index' => array( + 'Document' => array( + 'DocumentCategory.id' => array( + 'type' => 'select', + 'label' => 'Category', + 'selector' => 'customSelector', + 'selectOptions' => array('conditions' => array('DocumentCategory.description LIKE' => '%!%')), + ), + ) + ) + ); + $this->Controller->filters = $testSettings; + + $this->Controller->Components->trigger('initialize', array($this->Controller)); + $this->Controller->Components->trigger('startup', array($this->Controller)); + $this->Controller->Components->trigger('beforeRender', array($this->Controller)); + + $expected = array( + array( + 'name' => 'DocumentCategory.id', + 'options' => array( + 'type' => 'select', + 'options' => array( + 1 => 'Testing Doc', + 5 => 'Father Ted', + ), + 'empty' => false, + 'label' => 'Category', + ) + ), + ); + + $this->assertEquals($expected, $this->Controller->viewVars['viewFilterParams']); + } + + /** + * Test checkbox input filtering. + */ + public function testCheckboxOptions() + { + $testSettings = array( + 'index' => array( + 'Document' => array( + 'Document.is_private' => array( + 'type' => 'checkbox', + 'label' => 'Private?', + 'default' => true, + ), + ) + ) + ); + $this->Controller->filters = $testSettings; + + $this->Controller->Components->trigger('initialize', array($this->Controller)); + $this->Controller->Components->trigger('startup', array($this->Controller)); + $this->Controller->Components->trigger('beforeRender', array($this->Controller)); + + $expected = array( + array( + 'name' => 'Document.is_private', + 'options' => array( + 'type' => 'checkbox', + 'checked' => true, + 'label' => 'Private?', + ) + ), + ); + + $this->assertEquals($expected, $this->Controller->viewVars['viewFilterParams']); + } + + /** + * Test basic filter settings. + */ + public function testSelectMultiple() + { + $testSettings = array( + 'index' => array( + 'Document' => array( + 'DocumentCategory.id' => array( + 'type' => 'select', + 'multiple' => true, + ) + ) + ) + ); + $this->Controller->filters = $testSettings; + + $expected = array( + $this->Controller->name => $testSettings + ); + + $this->Controller->Components->trigger('initialize', array($this->Controller)); + $this->assertEquals($expected, $this->Controller->Filter->settings); + } + + /** + * Test select input for the model filtered. + */ + public function testSelectInputFromSameModel() + { + $testSettings = array( + 'index' => array( + 'Document' => array( + 'Document.title' => array( + 'type' => 'select', + ), + ) + ) + ); + $this->Controller->filters = $testSettings; + + $this->Controller->Components->trigger('initialize', array($this->Controller)); + $this->Controller->Components->trigger('startup', array($this->Controller)); + $this->Controller->Components->trigger('beforeRender', array($this->Controller)); + + $expected = array( + array( + 'name' => 'Document.title', + 'options' => array( + 'type' => 'select', + 'options' => array( + 'Testing Doc' => 'Testing Doc', + 'Imaginary Spec' => 'Imaginary Spec', + 'Nonexistant data' => 'Nonexistant data', + 'Illegal explosives DIY' => 'Illegal explosives DIY', + 'Father Ted' => 'Father Ted', + 'Duplicate title' => 'Duplicate title', + ), + 'empty' => '', + ) + ), + ); + + $this->assertEquals($expected, $this->Controller->viewVars['viewFilterParams']); + } + + /** + * Test disabling persistence for single action + * and for the entire controller. + */ + public function testPersistence() + { + $testSettings = array( + 'index' => array( + 'Document' => array( + 'Document.title' => array('type' => 'text') + ), + ) + ); + $this->Controller->filters = $testSettings; + + $sessionKey = sprintf('FilterPlugin.Filters.%s.%s', 'SomeOtherController', $this->Controller->action); + $filterValues = array('Document' => array('title' => 'in'), 'Filter' => array('filterFormId' => 'Document')); + $this->Controller->Session->write($sessionKey, $filterValues); + + $sessionKey = sprintf('FilterPlugin.Filters.%s.%s', $this->Controller->name, $this->Controller->action); + $filterValues = array('Document' => array('title' => 'in'), 'Filter' => array('filterFormId' => 'Document')); + $this->Controller->Session->write($sessionKey, $filterValues); + + $this->Controller->Filter->nopersist = array(); + $this->Controller->Filter->nopersist[$this->Controller->name] = true; + $this->Controller->Filter->nopersist['SomeOtherController'] = true; + + $this->Controller->Components->trigger('initialize', array($this->Controller)); + $this->Controller->Components->trigger('startup', array($this->Controller)); + + $expected = array($this->Controller->name => array($this->Controller->action => $filterValues)); + $this->assertEquals($expected, $this->Controller->Session->read('FilterPlugin.Filters')); + } + + /** + * Test whether filtering by belongsTo model text field + * works correctly. + */ + public function testBelongsToFilteringByText() + { + $testSettings = array( + 'index' => array( + 'Document' => array( + 'DocumentCategory.title' => array('type' => 'text') + ), + ) + ); + $this->Controller->filters = $testSettings; + + $this->Controller->Components->trigger('initialize', array($this->Controller)); + $this->Controller->Components->trigger('startup', array($this->Controller)); + $this->Controller->Components->trigger('beforeRender', array($this->Controller)); + + $expected = array( + array( + 'name' => 'DocumentCategory.title', + 'options' => array( + 'type' => 'text', + ) + ), + ); + + $this->assertEquals($expected, $this->Controller->viewVars['viewFilterParams']); + } } diff --git a/Test/Case/MockObjects/Document.php b/Test/Case/MockObjects/Document.php index fec3818..c8856ed 100644 --- a/Test/Case/MockObjects/Document.php +++ b/Test/Case/MockObjects/Document.php @@ -2,8 +2,8 @@ class Document extends CakeTestModel { - public $name = 'Document'; - public $belongsTo = array('DocumentCategory'); - public $hasMany = array('Item'); - public $hasOne = array('Metadata'); + public $name = 'Document'; + public $belongsTo = array('DocumentCategory'); + public $hasMany = array('Item'); + public $hasOne = array('Metadata'); } diff --git a/Test/Case/MockObjects/Document2.php b/Test/Case/MockObjects/Document2.php index 28af3d5..f2d0c92 100644 --- a/Test/Case/MockObjects/Document2.php +++ b/Test/Case/MockObjects/Document2.php @@ -2,15 +2,15 @@ class Document2 extends CakeTestModel { - public $name = 'Document'; - public $alias = 'Document'; - public $belongsTo = array('DocumentCategory'); - public $hasMany = array('Item'); + public $name = 'Document'; + public $alias = 'Document'; + public $belongsTo = array('DocumentCategory'); + public $hasMany = array('Item'); - public $returnValue = false; + public $returnValue = false; - public function beforeDataFilter($query, $options) - { - return $this->returnValue; - } + public function beforeDataFilter($query, $options) + { + return $this->returnValue; + } } diff --git a/Test/Case/MockObjects/Document3.php b/Test/Case/MockObjects/Document3.php index a74ea53..220dd63 100644 --- a/Test/Case/MockObjects/Document3.php +++ b/Test/Case/MockObjects/Document3.php @@ -2,25 +2,23 @@ class Document3 extends CakeTestModel { - public $name = 'Document'; - public $alias = 'Document'; - public $belongsTo = array('DocumentCategory'); - public $hasMany = array('Item'); + public $name = 'Document'; + public $alias = 'Document'; + public $belongsTo = array('DocumentCategory'); + public $hasMany = array('Item'); - public $itemToUnset = null; + public $itemToUnset = null; - public function afterDataFilter($query, $options) - { - if (!is_string($this->itemToUnset)) - { - return $query; - } + public function afterDataFilter($query, $options) + { + if (!is_string($this->itemToUnset)) { + return $query; + } - if (isset($query['conditions'][$this->itemToUnset])) - { - unset($query['conditions'][$this->itemToUnset]); - } + if (isset($query['conditions'][$this->itemToUnset])) { + unset($query['conditions'][$this->itemToUnset]); + } - return $query; - } + return $query; + } } diff --git a/Test/Case/MockObjects/DocumentCategory.php b/Test/Case/MockObjects/DocumentCategory.php index b53814d..14d5e2c 100644 --- a/Test/Case/MockObjects/DocumentCategory.php +++ b/Test/Case/MockObjects/DocumentCategory.php @@ -2,14 +2,14 @@ class DocumentCategory extends CakeTestModel { - public $name = 'DocumentCategory'; - public $hasMany = array('Document'); + public $name = 'DocumentCategory'; + public $hasMany = array('Document'); - public function customSelector($options = array()) - { - $options['conditions']['DocumentCategory.title LIKE'] = '%T%'; - $options['nofilter'] = true; + public function customSelector($options = array()) + { + $options['conditions']['DocumentCategory.title LIKE'] = '%T%'; + $options['nofilter'] = true; - return $this->find('list', $options); - } + return $this->find('list', $options); + } } diff --git a/Test/Case/MockObjects/DocumentTestsController.php b/Test/Case/MockObjects/DocumentTestsController.php index 68842ce..620e8f6 100644 --- a/Test/Case/MockObjects/DocumentTestsController.php +++ b/Test/Case/MockObjects/DocumentTestsController.php @@ -4,15 +4,15 @@ class DocumentTestsController extends Controller { - public $name = 'DocumentTests'; + public $name = 'DocumentTests'; - public function index() - { - } + public function index() + { + } - // must override this or the tests never complete.. - // @TODO: mock partial? - public function redirect($url, $status = null, $exit = true) - { - } + // must override this or the tests never complete.. + // @TODO: mock partial? + public function redirect($url, $status = null, $exit = true) + { + } } diff --git a/Test/Case/MockObjects/Item.php b/Test/Case/MockObjects/Item.php index 64f109f..ffb28af 100644 --- a/Test/Case/MockObjects/Item.php +++ b/Test/Case/MockObjects/Item.php @@ -2,6 +2,6 @@ class Item extends CakeTestModel { - public $name = 'Item'; - public $belongsTo = array('Document'); + public $name = 'Item'; + public $belongsTo = array('Document'); } diff --git a/Test/Case/MockObjects/Metadata.php b/Test/Case/MockObjects/Metadata.php index cfdcd59..c9bf609 100644 --- a/Test/Case/MockObjects/Metadata.php +++ b/Test/Case/MockObjects/Metadata.php @@ -2,6 +2,6 @@ class Metadata extends CakeTestModel { - public $name = 'Metadata'; - public $hasOne = array('Document'); + public $name = 'Metadata'; + public $hasOne = array('Document'); } diff --git a/Test/Case/Model/Behaviors/FilteredBehaviorTest.php b/Test/Case/Model/Behaviors/FilteredBehaviorTest.php index 086d691..6409821 100644 --- a/Test/Case/Model/Behaviors/FilteredBehaviorTest.php +++ b/Test/Case/Model/Behaviors/FilteredBehaviorTest.php @@ -1,15 +1,15 @@ - - Multi-licensed under: - MPL - LGPL - GPL -*/ + * CakePHP Filter Plugin + * + * Copyright (C) 2009-3827 dr. Hannibal Lecter / lecterror + * + * + * Multi-licensed under: + * MPL + * LGPL + * GPL + */ App::import('Core', array('AppModel', 'Model')); App::uses('Document', 'Filter.Test/Case/MockObjects'); @@ -22,727 +22,654 @@ class FilteredBehaviorTest extends CakeTestCase { - public $fixtures = array - ( - 'plugin.filter.document_category', - 'plugin.filter.document', - 'plugin.filter.item', - 'plugin.filter.metadata', - ); - - public $Document = null; - - public function startTest($model) - { - $this->Document = ClassRegistry::init('Document'); - } - - public function endTest($model) - { - $this->Document = null; - } - - /** - * Detach and re-attach the behavior to reset the options. - * - * @param array $options Behavior options. - */ - protected function _reattachBehavior($options = array()) - { - $this->Document->Behaviors->detach('Filtered'); - $this->Document->Behaviors->attach('Filter.Filtered', $options); - } - - /** - * Test attaching without options. - */ - public function testBlankAttaching() - { - $this->Document->Behaviors->attach('Filter.Filtered'); - $this->assertTrue($this->Document->Behaviors->enabled('Filtered')); - } - - /** - * Test attaching with options. - */ - public function testInitSettings() - { - $testOptions = array - ( - 'Document.title' => array('type' => 'text', 'condition' => 'like'), - 'DocumentCategory.id' => array('type' => 'select', 'filterField' => 'document_category_id'), - 'Document.is_private' => array('type' => 'checkbox', 'label' => 'Private?') - ); - $this->_reattachBehavior($testOptions); - - $expected = array - ( - 'Document.title' => array('type' => 'text', 'condition' => 'like', 'required' => false, 'selectOptions' => array()), - 'DocumentCategory.id' => array('type' => 'select', 'filterField' => 'document_category_id', 'condition' => 'like', 'required' => false, 'selectOptions' => array()), - 'Document.is_private' => array('type' => 'checkbox', 'label' => 'Private?', 'condition' => 'like', 'required' => false, 'selectOptions' => array()) - ); - $this->assertEquals($expected, $this->Document->Behaviors->Filtered->settings[$this->Document->alias]); - } - - /** - * Test init settings when only a single field is given, with no extra options. - */ - public function testInitSettingsSingle() - { - $testOptions = array('Document.title'); - $this->_reattachBehavior($testOptions); - - $expected = array - ( - 'Document.title' => array('type' => 'text', 'condition' => 'like', 'required' => false, 'selectOptions' => array()), - ); - $this->assertEquals($expected, $this->Document->Behaviors->Filtered->settings[$this->Document->alias]); - } - - /** - * Test setting the filter values for future queries. - */ - public function testSetFilterValues() - { - $testOptions = array - ( - 'Document.title' => array('type' => 'text', 'condition' => 'like', 'required' => true), - 'DocumentCategory.id' => array('type' => 'select', 'filterField' => 'document_category_id'), - 'Document.is_private' => array('type' => 'checkbox', 'label' => 'Private?') - ); - - $this->_reattachBehavior($testOptions); - - $filterValues = array - ( - 'Document' => array('title' => 'in', 'is_private' => 0), - 'DocumentCategory' => array('id' => 1) - ); - - $this->Document->setFilterValues($filterValues); - $actualFilterValues = $this->Document->getFilterValues(); - $this->assertEquals($filterValues, $actualFilterValues[$this->Document->alias]); - } - - /** - * Test detecting an error in options - when a field is 'required' but no value is given for it. - */ - public function testLoadingRequiredFieldValueMissing() - { - $testOptions = array - ( - 'Document.title' => array('type' => 'text', 'condition' => 'like', 'required' => true), - 'DocumentCategory.id' => array('type' => 'select', 'filterField' => 'document_category_id'), - 'Document.is_private' => array('type' => 'checkbox', 'label' => 'Private?') - ); - $this->_reattachBehavior($testOptions); - - $filterValues = array - ( - 'Document' => array('is_private' => 0), - 'DocumentCategory' => array('id' => 1) - ); - $this->Document->setFilterValues($filterValues); - - $this->setExpectedException('PHPUnit_Framework_Error_Notice'); - $this->Document->find('first'); - } - - /** - * Test filtering with conditions from current model and belongsTo model. - */ - public function testFilteringBelongsTo() - { - $testOptions = array - ( - 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), - 'DocumentCategory.id' => array('type' => 'select') - ); - $this->_reattachBehavior($testOptions); - - $filterValues = array - ( - 'Document' => array('title' => 'in'), - 'DocumentCategory' => array('id' => 1) - ); - $this->Document->setFilterValues($filterValues); - - $expected = array - ( - array('Document' => array('id' => 1, 'title' => 'Testing Doc', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-06-28 10:39:23', 'updated' => '2010-06-29 11:22:48')), - array('Document' => array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-03-28 12:19:13', 'updated' => '2010-04-29 11:23:44')) - ); - - $result = $this->Document->find('all', array('recursive' => -1)); - $this->assertEquals($expected, $result); - } - - public function testFilteringBelongsToTextField() - { - $testOptions = array - ( - 'DocumentCategory.title' => array('type' => 'text') - ); - $this->_reattachBehavior($testOptions); - - $filterValues = array - ( - 'DocumentCategory' => array('title' => 'spec') - ); - $this->Document->setFilterValues($filterValues); - - $expected = array - ( - array('Document' => array('id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0, 'created' => '2009-01-13 05:15:03', 'updated' => '2010-12-05 03:24:15')) - ); - - $result = $this->Document->find('all', array('recursive' => -1)); - $this->assertEquals($expected, $result); - } - - /** - * Test filtering with conditions from current model and belongsTo model, - * same as testFilteringBelongsTo() except for a change in filterField format. - */ - public function testFilteringBelongsToFilterFieldTest() - { - $testOptions = array - ( - 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), - 'DocumentCategory.id' => array('type' => 'select', 'filterField' => 'Document.document_category_id') - ); - $this->_reattachBehavior($testOptions); - - $filterValues = array - ( - 'Document' => array('title' => 'in'), - 'DocumentCategory' => array('id' => 1) - ); - $this->Document->setFilterValues($filterValues); - - $expected = array - ( - array('Document' => array('id' => 1, 'title' => 'Testing Doc', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-06-28 10:39:23', 'updated' => '2010-06-29 11:22:48')), - array('Document' => array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-03-28 12:19:13', 'updated' => '2010-04-29 11:23:44')) - ); - - $result = $this->Document->find('all', array('recursive' => -1)); - $this->assertEquals($expected, $result); - } - - /** - * Test various conditions for the type 'text' in filtering (less than, equal, like, etc..) - */ - public function testFilteringBelongsToDifferentConditions() - { - $testOptions = array - ( - 'title' => array('type' => 'text', 'condition' => '='), - 'DocumentCategory.id' => array('type' => 'select') - ); - $this->_reattachBehavior($testOptions); - - $filterValues = array - ( - 'Document' => array('title' => 'Illegal explosives DIY'), - 'DocumentCategory' => array('id' => '') - ); - $this->Document->setFilterValues($filterValues); - - $expected = array - ( - array('Document' => array('id' => 4, 'title' => 'Illegal explosives DIY', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 1, 'created' => '2010-01-08 05:15:03', 'updated' => '2010-05-22 03:15:24')), - ); - - $result = $this->Document->find('all', array('recursive' => -1)); - $this->assertEquals($expected, $result); - - $testOptions = array - ( - 'id' => array('type' => 'text', 'condition' => '>='), - 'created' => array('type' => 'text', 'condition' => '<=') - ); - $this->_reattachBehavior($testOptions); - - $filterValues = array - ( - 'Document' => array('id' => 3, 'created' => '2010-03-01') - ); - $this->Document->setFilterValues($filterValues); - - $expected = array - ( - array('Document' => array('id' => 4, 'title' => 'Illegal explosives DIY', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 1, 'created' => '2010-01-08 05:15:03', 'updated' => '2010-05-22 03:15:24')), - array('Document' => array('id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0, 'created' => '2009-01-13 05:15:03', 'updated' => '2010-12-05 03:24:15')), - array('Document' => array('id' => 6, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0, 'created' => '2009-01-13 05:15:03', 'updated' => '2010-12-05 03:24:15')), - array('Document' => array('id' => 7, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0, 'created' => '2009-01-13 05:15:03', 'updated' => '2010-12-05 03:24:15')), - ); - - $result = $this->Document->find('all', array('recursive' => -1)); - $this->assertEquals($expected, $result); - } - - /** - * Test filtering with conditions on current model, the belongsTo model - * and hasMany model (behavior adds an INNER JOIN in query). - */ - public function testFilteringBelongsToAndHasMany() - { - $testOptions = array - ( - 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), - 'DocumentCategory.id' => array('type' => 'select'), - 'Document.is_private' => array('type' => 'checkbox', 'label' => 'Private?'), - 'Item.code' => array('type' => 'text'), - ); - $this->_reattachBehavior($testOptions); - - $filterValues = array - ( - 'Document' => array('title' => 'in', 'is_private' => 0), - 'DocumentCategory' => array('id' => 1), - 'Item' => array('code' => '04') - ); - $this->Document->setFilterValues($filterValues); - - $expected = array - ( - array - ( - 'Document' => array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-03-28 12:19:13', 'updated' => '2010-04-29 11:23:44'), - 'DocumentCategory' => array('id' => 1, 'title' => 'Testing Doc', 'description' => 'It\'s a bleeding test doc!'), - 'Metadata' => array('id' => 2, 'document_id' => 2, 'weight' => 0, 'size' => 45, 'permissions' => 'rw-------'), - 'Item' => array - ( - array('id' => 4, 'document_id' => 2, 'code' => 'The item #01'), - array('id' => 5, 'document_id' => 2, 'code' => 'The item #02'), - array('id' => 6, 'document_id' => 2, 'code' => 'The item #03'), - array('id' => 7, 'document_id' => 2, 'code' => 'The item #04') - ) - ) - ); - - $result = $this->Document->find('all'); - $this->assertEquals($expected, $result); - - $expected = array - ( - array - ( - 'Document' => array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-03-28 12:19:13', 'updated' => '2010-04-29 11:23:44'), - 'DocumentCategory' => array('id' => 1, 'title' => 'Testing Doc', 'description' => 'It\'s a bleeding test doc!'), - 'Metadata' => array('id' => 2, 'document_id' => 2, 'weight' => 0, 'size' => 45, 'permissions' => 'rw-------'), - ) - ); - - $result = $this->Document->find('all', array('recursive' => 0)); - $this->assertEquals($expected, $result); - - $this->Document->unbindModel(array('hasMany' => array('Item')), false); - $this->Document->bindModel(array('hasMany' => array('Item')), false); - - $result = $this->Document->find('all', array('recursive' => 0)); - $this->assertEquals($expected, $result); - - $expected = array - ( - array - ( - 'Document' => array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-03-28 12:19:13', 'updated' => '2010-04-29 11:23:44') - ) - ); - - $result = $this->Document->find('all', array('recursive' => -1)); - $this->assertEquals($expected, $result); - } - - /** - * Test filtering with join which has some custom - * condition in the relation (both string and array). - */ - public function testCustomJoinConditions() - { - $testOptions = array - ( - 'Metadata.weight' => array('type' => 'text', 'condition' => '>'), - ); - $this->_reattachBehavior($testOptions); - - $filterValues = array - ( - 'Metadata' => array('weight' => 3), - ); - $this->Document->setFilterValues($filterValues); - - $expected = array - ( - array - ( - 'Document' => array('id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0, 'created' => '2009-01-13 05:15:03', 'updated' => '2010-12-05 03:24:15'), - 'Metadata' => array('id' => 5, 'document_id' => 5, 'weight' => 4, 'size' => 790, 'permissions' => 'rw-rw-r--'), - ) - ); - - $this->Document->recursive = -1; - $oldConditions = $this->Document->hasOne['Metadata']['conditions']; - $this->Document->hasOne['Metadata']['conditions'] = array('Metadata.size > 500'); - $this->Document->Behaviors->attach('Containable'); - - $result = $this->Document->find('all', array('contain' => array('Metadata'))); - $this->assertEquals($expected, $result); - - $this->Document->hasOne['Metadata']['conditions'] = 'Metadata.size > 500'; - $result = $this->Document->find('all', array('contain' => array('Metadata'))); - $this->assertEquals($expected, $result); - - $this->Document->hasOne['Metadata']['conditions'] = $oldConditions; - $this->Document->Behaviors->detach('Containable'); - } - - /** - * Test for any possible conflicts with Containable behavior. - */ - public function testFilteringBelongsToAndHasManyWithContainable() - { - $testOptions = array - ( - 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), - 'DocumentCategory.id' => array('type' => 'select'), - 'Document.is_private' => array('type' => 'checkbox', 'label' => 'Private?'), - 'Item.code' => array('type' => 'text'), - ); - - $this->_reattachBehavior($testOptions); - $this->Document->Behaviors->attach('Containable'); - - $filterValues = array - ( - 'Document' => array('title' => 'in', 'is_private' => 0), - 'DocumentCategory' => array('id' => 1), - 'Item' => array('code' => '04') - ); - $this->Document->setFilterValues($filterValues); - - $expected = array - ( - array - ( - 'Document' => array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-03-28 12:19:13', 'updated' => '2010-04-29 11:23:44'), - 'DocumentCategory' => array('id' => 1, 'title' => 'Testing Doc', 'description' => 'It\'s a bleeding test doc!'), - 'Item' => array - ( - array('id' => 4, 'document_id' => 2, 'code' => 'The item #01'), - array('id' => 5, 'document_id' => 2, 'code' => 'The item #02'), - array('id' => 6, 'document_id' => 2, 'code' => 'The item #03'), - array('id' => 7, 'document_id' => 2, 'code' => 'The item #04') - ) - ) - ); - - $result = $this->Document->find('all', array('contain' => array('DocumentCategory', 'Item'))); - $this->assertEquals($expected, $result); - - $expected = array - ( - array - ( - 'Document' => array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-03-28 12:19:13', 'updated' => '2010-04-29 11:23:44'), - 'DocumentCategory' => array('id' => 1, 'title' => 'Testing Doc', 'description' => 'It\'s a bleeding test doc!'), - ) - ); - - $result = $this->Document->find('all', array('contain' => array('DocumentCategory'))); - $this->assertEquals($expected, $result); - - $expected = array - ( - array - ( - 'Document' => array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-03-28 12:19:13', 'updated' => '2010-04-29 11:23:44'), - ) - ); - - $result = $this->Document->find('all', array('contain' => array())); - $this->assertEquals($expected, $result); - - $this->Document->Behaviors->detach('Containable'); - } - - /** - * Test filtering by text input with hasOne relation. - */ - public function testHasOneAndHasManyWithTextSearch() - { - $testOptions = array - ( - 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), - 'Item.code' => array('type' => 'text'), - 'Metadata.size' => array('type' => 'text', 'condition' => '='), - ); - - $filterValues = array - ( - 'Document' => array('title' => 'in'), - 'Item' => array('code' => '04'), - 'Metadata' => array('size' => 45), - ); - - $expected = array - ( - array - ( - 'Document' => array('id' => 2, 'title' => 'Imaginary Spec'), - ) - ); - - $this->_reattachBehavior($testOptions); - $this->Document->setFilterValues($filterValues); - - $this->Document->recursive = -1; - $result = $this->Document->find('all', array('fields' => array('Document.id', 'Document.title'))); - $this->assertEquals($expected, $result); - } - - /** - * Test filtering with Containable and hasOne Model.field. - */ - public function testHasOneWithContainable() - { - $testOptions = array - ( - 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), - 'Item.code' => array('type' => 'text'), - 'Metadata.size' => array('type' => 'text', 'condition' => '='), - ); - - $filterValues = array - ( - 'Document' => array('title' => 'in'), - 'Item' => array('code' => '04'), - 'Metadata' => array('size' => 45), - ); - - $expected = array - ( - array - ( - 'Document' => array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-03-28 12:19:13', 'updated' => '2010-04-29 11:23:44'), - 'Metadata' => array('id' => 2, 'document_id' => 2, 'weight' => 0, 'size' => 45, 'permissions' => 'rw-------'), - 'Item' => array - ( - array('id' => 4, 'document_id' => 2, 'code' => 'The item #01'), - array('id' => 5, 'document_id' => 2, 'code' => 'The item #02'), - array('id' => 6, 'document_id' => 2, 'code' => 'The item #03'), - array('id' => 7, 'document_id' => 2, 'code' => 'The item #04') - ) - ) - ); - - // containable first, filtered second - $this->Document->Behaviors->attach('Containable'); - $this->_reattachBehavior($testOptions); - $this->Document->setFilterValues($filterValues); - $result = $this->Document->find('all', array('contain' => array('Metadata', 'Item'))); - $this->assertEquals($expected, $result); - $this->Document->Behaviors->detach('Containable'); - - // filtered first, containable second - $this->_reattachBehavior($testOptions); - $this->Document->setFilterValues($filterValues); - $this->Document->Behaviors->attach('Containable'); - $result = $this->Document->find('all', array('contain' => array('Metadata', 'Item'))); - $this->assertEquals($expected, $result); - $this->Document->Behaviors->detach('Containable'); - } - - /** - * Test filtering when a join is already present in the query, - * this should prevent duplicate joins and query errors. - */ - public function testJoinAlreadyPresent() - { - $testOptions = array - ( - 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), - 'Item.code' => array('type' => 'text'), - 'Metadata.size' => array('type' => 'text', 'condition' => '='), - ); - - $filterValues = array - ( - 'Document' => array('title' => 'in'), - 'Item' => array('code' => '04'), - 'Metadata' => array('size' => 45), - ); - - $expected = array - ( - array - ( - 'Document' => array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-03-28 12:19:13', 'updated' => '2010-04-29 11:23:44'), - 'DocumentCategory' => array('id' => 1, 'title' => 'Testing Doc', 'description' => 'It\'s a bleeding test doc!'), - 'Metadata' => array('id' => 2, 'document_id' => 2, 'weight' => 0, 'size' => 45, 'permissions' => 'rw-------'), - 'Item' => array - ( - array('id' => 4, 'document_id' => 2, 'code' => 'The item #01'), - array('id' => 5, 'document_id' => 2, 'code' => 'The item #02'), - array('id' => 6, 'document_id' => 2, 'code' => 'The item #03'), - array('id' => 7, 'document_id' => 2, 'code' => 'The item #04') - ) - ) - ); - - $customJoin = array(); - $customJoin[] = array - ( - 'table' => 'items', - 'alias' => 'FilterItem', - 'type' => 'INNER', - 'conditions' => 'Document.id = FilterItem.document_id', - ); - - $this->_reattachBehavior($testOptions); - $this->Document->setFilterValues($filterValues); - $result = $this->Document->find('all', array('joins' => $customJoin, 'recursive' => 1)); - $this->assertEquals($expected, $result); - } - - /** - * Test the 'nofilter' query param. - */ - public function testNofilterFindParam() - { - $testOptions = array - ( - 'Document.title' => array('type' => 'text', 'condition' => 'like'), - 'DocumentCategory.id' => array('type' => 'select'), - 'Document.is_private' => array('type' => 'checkbox', 'label' => 'Private?', 'default' => 0) - ); - $this->_reattachBehavior($testOptions); - - - $filterValues = array - ( - 'DocumentCategory' => array('id' => 2), - 'Document' => array('title' => '') - ); - $this->Document->setFilterValues($filterValues); - - $expected = array - ( - array('Document' => array('id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0, 'created' => '2009-01-13 05:15:03', 'updated' => '2010-12-05 03:24:15')) - ); - - $result = $this->Document->find('all', array('recursive' => -1, 'nofilter' => true)); - $this->assertNotEquals($expected, $result); - - $result = $this->Document->find('all', array('recursive' => -1, 'nofilter' => 'true')); - $this->assertEquals($expected, $result); - } - - /** - * Test bailing out if no settings exist for the current model. - */ - public function testExitWhenNoSettings() - { - $this->Document->DocumentCategory->Behaviors->attach('Filter.Filtered'); - - $this->assertFalse(isset($this->Document->DocumentCategory->Behaviors->Filtered->settings[$this->Document->DocumentCategory->alias])); - - $filterValues = array - ( - 'DocumentCategory' => array('id' => 2) - ); - $this->Document->DocumentCategory->setFilterValues($filterValues); - - $expected = array - ( - array('DocumentCategory' => array('id' => 1, 'title' => 'Testing Doc', 'description' => 'It\'s a bleeding test doc!')), - array('DocumentCategory' => array('id' => 2, 'title' => 'Imaginary Spec', 'description' => 'This doc does not exist')), - array('DocumentCategory' => array('id' => 3, 'title' => 'Nonexistant data', 'description' => 'This doc is probably empty')), - array('DocumentCategory' => array('id' => 4, 'title' => 'Illegal explosives DIY', 'description' => 'Viva la revolucion!')), - array('DocumentCategory' => array('id' => 5, 'title' => 'Father Ted', 'description' => 'Feck! Drink! Arse! Girls!')) - ); - - $result = $this->Document->DocumentCategory->find('all', array('recursive' => -1)); - $this->assertEquals($expected, $result); - - $this->Document->DocumentCategory->Behaviors->detach('Filtered'); - } - - /** - * Test beforeDataFilter() callback, used to cancel filtering if necessary. - */ - public function testBeforeDataFilterCallbackCancel() - { - $this->Document = ClassRegistry::init('Document2'); - - $testOptions = array - ( - 'Document.title' => array('type' => 'text', 'condition' => 'like'), - 'DocumentCategory.id' => array('type' => 'select'), - 'Document.is_private' => array('type' => 'checkbox', 'label' => 'Private?') - ); - $this->_reattachBehavior($testOptions); - - - $filterValues = array - ( - 'DocumentCategory' => array('id' => 2) - ); - $this->Document->setFilterValues($filterValues); - - $expected = array - ( - array('Document' => array('id' => 1, 'title' => 'Testing Doc', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-06-28 10:39:23', 'updated' => '2010-06-29 11:22:48')), - array('Document' => array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-03-28 12:19:13', 'updated' => '2010-04-29 11:23:44')), - array('Document' => array('id' => 3, 'title' => 'Nonexistant data', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-04-28 11:12:33', 'updated' => '2010-05-05 15:03:24')), - array('Document' => array('id' => 4, 'title' => 'Illegal explosives DIY', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 1, 'created' => '2010-01-08 05:15:03', 'updated' => '2010-05-22 03:15:24')), - array('Document' => array('id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0, 'created' => '2009-01-13 05:15:03', 'updated' => '2010-12-05 03:24:15')), - array('Document' => array('id' => 6, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0, 'created' => '2009-01-13 05:15:03', 'updated' => '2010-12-05 03:24:15')), - array('Document' => array('id' => 7, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0, 'created' => '2009-01-13 05:15:03', 'updated' => '2010-12-05 03:24:15')), - ); - - $result = $this->Document->find('all', array('recursive' => -1)); - $this->assertEquals($expected, $result); - } - - /** - * Test afterDataFilter() callback, used to modify the conditions after - * filter conditions have been applied. - */ - public function testAfterDataFilterCallbackQueryChange() - { - $this->Document = ClassRegistry::init('Document3'); - $this->Document->itemToUnset = 'FilterDocumentCategory.id'; - - $testOptions = array - ( - 'Document.title' => array('type' => 'text', 'condition' => 'like'), - 'DocumentCategory.id' => array('type' => 'select'), - 'Document.is_private' => array('type' => 'checkbox', 'label' => 'Private?') - ); - $this->_reattachBehavior($testOptions); - - - $filterValues = array - ( - 'DocumentCategory' => array('id' => 2) - ); - $this->Document->setFilterValues($filterValues); - - $expected = array - ( - array('Document' => array('id' => 1, 'title' => 'Testing Doc', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-06-28 10:39:23', 'updated' => '2010-06-29 11:22:48')), - array('Document' => array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-03-28 12:19:13', 'updated' => '2010-04-29 11:23:44')), - array('Document' => array('id' => 3, 'title' => 'Nonexistant data', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-04-28 11:12:33', 'updated' => '2010-05-05 15:03:24')), - array('Document' => array('id' => 4, 'title' => 'Illegal explosives DIY', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 1, 'created' => '2010-01-08 05:15:03', 'updated' => '2010-05-22 03:15:24')), - array('Document' => array('id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0, 'created' => '2009-01-13 05:15:03', 'updated' => '2010-12-05 03:24:15')), - array('Document' => array('id' => 6, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0, 'created' => '2009-01-13 05:15:03', 'updated' => '2010-12-05 03:24:15')), - array('Document' => array('id' => 7, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0, 'created' => '2009-01-13 05:15:03', 'updated' => '2010-12-05 03:24:15')), - ); - - $result = $this->Document->find('all', array('recursive' => -1)); - $this->assertEquals($expected, $result); - } + public $fixtures = array + ( + 'plugin.filter.document_category', + 'plugin.filter.document', + 'plugin.filter.item', + 'plugin.filter.metadata', + ); + + public $Document = null; + + public function startTest($model) + { + $this->Document = ClassRegistry::init('Document'); + } + + public function endTest($model) + { + $this->Document = null; + } + + /** + * Detach and re-attach the behavior to reset the options. + * + * @param array $options Behavior options. + */ + protected function _reattachBehavior($options = array()) + { + $this->Document->Behaviors->detach('Filtered'); + $this->Document->Behaviors->attach('Filter.Filtered', $options); + } + + /** + * Test attaching without options. + */ + public function testBlankAttaching() + { + $this->Document->Behaviors->attach('Filter.Filtered'); + $this->assertTrue($this->Document->Behaviors->enabled('Filtered')); + } + + /** + * Test attaching with options. + */ + public function testInitSettings() + { + $testOptions = array( + 'Document.title' => array('type' => 'text', 'condition' => 'like'), + 'DocumentCategory.id' => array('type' => 'select', 'filterField' => 'document_category_id'), + 'Document.is_private' => array('type' => 'checkbox', 'label' => 'Private?') + ); + $this->_reattachBehavior($testOptions); + + $expected = array( + 'Document.title' => array('type' => 'text', 'condition' => 'like', 'required' => false, 'selectOptions' => array()), + 'DocumentCategory.id' => array('type' => 'select', 'filterField' => 'document_category_id', 'condition' => 'like', 'required' => false, 'selectOptions' => array()), + 'Document.is_private' => array('type' => 'checkbox', 'label' => 'Private?', 'condition' => 'like', 'required' => false, 'selectOptions' => array()) + ); + $this->assertEquals($expected, $this->Document->Behaviors->Filtered->settings[$this->Document->alias]); + } + + /** + * Test init settings when only a single field is given, with no extra options. + */ + public function testInitSettingsSingle() + { + $testOptions = array('Document.title'); + $this->_reattachBehavior($testOptions); + + $expected = array( + 'Document.title' => array('type' => 'text', 'condition' => 'like', 'required' => false, 'selectOptions' => array()), + ); + $this->assertEquals($expected, $this->Document->Behaviors->Filtered->settings[$this->Document->alias]); + } + + /** + * Test setting the filter values for future queries. + */ + public function testSetFilterValues() + { + $testOptions = array( + 'Document.title' => array('type' => 'text', 'condition' => 'like', 'required' => true), + 'DocumentCategory.id' => array('type' => 'select', 'filterField' => 'document_category_id'), + 'Document.is_private' => array('type' => 'checkbox', 'label' => 'Private?') + ); + + $this->_reattachBehavior($testOptions); + + $filterValues = array( + 'Document' => array('title' => 'in', 'is_private' => 0), + 'DocumentCategory' => array('id' => 1) + ); + + $this->Document->setFilterValues($filterValues); + $actualFilterValues = $this->Document->getFilterValues(); + $this->assertEquals($filterValues, $actualFilterValues[$this->Document->alias]); + } + + /** + * Test detecting an error in options - when a field is 'required' but no value is given for it. + */ + public function testLoadingRequiredFieldValueMissing() + { + $testOptions = array( + 'Document.title' => array('type' => 'text', 'condition' => 'like', 'required' => true), + 'DocumentCategory.id' => array('type' => 'select', 'filterField' => 'document_category_id'), + 'Document.is_private' => array('type' => 'checkbox', 'label' => 'Private?') + ); + $this->_reattachBehavior($testOptions); + + $filterValues = array( + 'Document' => array('is_private' => 0), + 'DocumentCategory' => array('id' => 1) + ); + $this->Document->setFilterValues($filterValues); + + $this->setExpectedException('PHPUnit_Framework_Error_Notice'); + $this->Document->find('first'); + } + + /** + * Test filtering with conditions from current model and belongsTo model. + */ + public function testFilteringBelongsTo() + { + $testOptions = array( + 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), + 'DocumentCategory.id' => array('type' => 'select') + ); + $this->_reattachBehavior($testOptions); + + $filterValues = array( + 'Document' => array('title' => 'in'), + 'DocumentCategory' => array('id' => 1) + ); + $this->Document->setFilterValues($filterValues); + + $expected = array( + array('Document' => array('id' => 1, 'title' => 'Testing Doc', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-06-28 10:39:23', 'updated' => '2010-06-29 11:22:48')), + array('Document' => array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-03-28 12:19:13', 'updated' => '2010-04-29 11:23:44')) + ); + + $result = $this->Document->find('all', array('recursive' => -1)); + $this->assertEquals($expected, $result); + } + + public function testFilteringBelongsToTextField() + { + $testOptions = array( + 'DocumentCategory.title' => array('type' => 'text') + ); + $this->_reattachBehavior($testOptions); + + $filterValues = array( + 'DocumentCategory' => array('title' => 'spec') + ); + $this->Document->setFilterValues($filterValues); + + $expected = array( + array('Document' => array('id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0, 'created' => '2009-01-13 05:15:03', 'updated' => '2010-12-05 03:24:15')) + ); + + $result = $this->Document->find('all', array('recursive' => -1)); + $this->assertEquals($expected, $result); + } + + /** + * Test filtering with conditions from current model and belongsTo model, + * same as testFilteringBelongsTo() except for a change in filterField format. + */ + public function testFilteringBelongsToFilterFieldTest() + { + $testOptions = array( + 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), + 'DocumentCategory.id' => array('type' => 'select', 'filterField' => 'Document.document_category_id') + ); + $this->_reattachBehavior($testOptions); + + $filterValues = array( + 'Document' => array('title' => 'in'), + 'DocumentCategory' => array('id' => 1) + ); + $this->Document->setFilterValues($filterValues); + + $expected = array( + array('Document' => array('id' => 1, 'title' => 'Testing Doc', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-06-28 10:39:23', 'updated' => '2010-06-29 11:22:48')), + array('Document' => array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-03-28 12:19:13', 'updated' => '2010-04-29 11:23:44')) + ); + + $result = $this->Document->find('all', array('recursive' => -1)); + $this->assertEquals($expected, $result); + } + + /** + * Test various conditions for the type 'text' in filtering (less than, equal, like, etc..) + */ + public function testFilteringBelongsToDifferentConditions() + { + $testOptions = array( + 'title' => array('type' => 'text', 'condition' => '='), + 'DocumentCategory.id' => array('type' => 'select') + ); + $this->_reattachBehavior($testOptions); + + $filterValues = array( + 'Document' => array('title' => 'Illegal explosives DIY'), + 'DocumentCategory' => array('id' => '') + ); + $this->Document->setFilterValues($filterValues); + + $expected = array( + array('Document' => array('id' => 4, 'title' => 'Illegal explosives DIY', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 1, 'created' => '2010-01-08 05:15:03', 'updated' => '2010-05-22 03:15:24')), + ); + + $result = $this->Document->find('all', array('recursive' => -1)); + $this->assertEquals($expected, $result); + + $testOptions = array( + 'id' => array('type' => 'text', 'condition' => '>='), + 'created' => array('type' => 'text', 'condition' => '<=') + ); + $this->_reattachBehavior($testOptions); + + $filterValues = array( + 'Document' => array('id' => 3, 'created' => '2010-03-01') + ); + $this->Document->setFilterValues($filterValues); + + $expected = array( + array('Document' => array('id' => 4, 'title' => 'Illegal explosives DIY', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 1, 'created' => '2010-01-08 05:15:03', 'updated' => '2010-05-22 03:15:24')), + array('Document' => array('id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0, 'created' => '2009-01-13 05:15:03', 'updated' => '2010-12-05 03:24:15')), + array('Document' => array('id' => 6, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0, 'created' => '2009-01-13 05:15:03', 'updated' => '2010-12-05 03:24:15')), + array('Document' => array('id' => 7, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0, 'created' => '2009-01-13 05:15:03', 'updated' => '2010-12-05 03:24:15')), + ); + + $result = $this->Document->find('all', array('recursive' => -1)); + $this->assertEquals($expected, $result); + } + + /** + * Test filtering with conditions on current model, the belongsTo model + * and hasMany model (behavior adds an INNER JOIN in query). + */ + public function testFilteringBelongsToAndHasMany() + { + $testOptions = array( + 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), + 'DocumentCategory.id' => array('type' => 'select'), + 'Document.is_private' => array('type' => 'checkbox', 'label' => 'Private?'), + 'Item.code' => array('type' => 'text'), + ); + $this->_reattachBehavior($testOptions); + + $filterValues = array( + 'Document' => array('title' => 'in', 'is_private' => 0), + 'DocumentCategory' => array('id' => 1), + 'Item' => array('code' => '04') + ); + $this->Document->setFilterValues($filterValues); + + $expected = array( + array( + 'Document' => array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-03-28 12:19:13', 'updated' => '2010-04-29 11:23:44'), + 'DocumentCategory' => array('id' => 1, 'title' => 'Testing Doc', 'description' => 'It\'s a bleeding test doc!'), + 'Metadata' => array('id' => 2, 'document_id' => 2, 'weight' => 0, 'size' => 45, 'permissions' => 'rw-------'), + 'Item' => array( + array('id' => 4, 'document_id' => 2, 'code' => 'The item #01'), + array('id' => 5, 'document_id' => 2, 'code' => 'The item #02'), + array('id' => 6, 'document_id' => 2, 'code' => 'The item #03'), + array('id' => 7, 'document_id' => 2, 'code' => 'The item #04') + ) + ) + ); + + $result = $this->Document->find('all'); + $this->assertEquals($expected, $result); + + $expected = array( + array( + 'Document' => array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-03-28 12:19:13', 'updated' => '2010-04-29 11:23:44'), + 'DocumentCategory' => array('id' => 1, 'title' => 'Testing Doc', 'description' => 'It\'s a bleeding test doc!'), + 'Metadata' => array('id' => 2, 'document_id' => 2, 'weight' => 0, 'size' => 45, 'permissions' => 'rw-------'), + ) + ); + + $result = $this->Document->find('all', array('recursive' => 0)); + $this->assertEquals($expected, $result); + + $this->Document->unbindModel(array('hasMany' => array('Item')), false); + $this->Document->bindModel(array('hasMany' => array('Item')), false); + + $result = $this->Document->find('all', array('recursive' => 0)); + $this->assertEquals($expected, $result); + + $expected = array( + array( + 'Document' => array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-03-28 12:19:13', 'updated' => '2010-04-29 11:23:44') + ) + ); + + $result = $this->Document->find('all', array('recursive' => -1)); + $this->assertEquals($expected, $result); + } + + /** + * Test filtering with join which has some custom + * condition in the relation (both string and array). + */ + public function testCustomJoinConditions() + { + $testOptions = array( + 'Metadata.weight' => array('type' => 'text', 'condition' => '>'), + ); + $this->_reattachBehavior($testOptions); + + $filterValues = array( + 'Metadata' => array('weight' => 3), + ); + $this->Document->setFilterValues($filterValues); + + $expected = array( + array( + 'Document' => array('id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0, 'created' => '2009-01-13 05:15:03', 'updated' => '2010-12-05 03:24:15'), + 'Metadata' => array('id' => 5, 'document_id' => 5, 'weight' => 4, 'size' => 790, 'permissions' => 'rw-rw-r--'), + ) + ); + + $this->Document->recursive = -1; + $oldConditions = $this->Document->hasOne['Metadata']['conditions']; + $this->Document->hasOne['Metadata']['conditions'] = array('Metadata.size > 500'); + $this->Document->Behaviors->attach('Containable'); + + $result = $this->Document->find('all', array('contain' => array('Metadata'))); + $this->assertEquals($expected, $result); + + $this->Document->hasOne['Metadata']['conditions'] = 'Metadata.size > 500'; + $result = $this->Document->find('all', array('contain' => array('Metadata'))); + $this->assertEquals($expected, $result); + + $this->Document->hasOne['Metadata']['conditions'] = $oldConditions; + $this->Document->Behaviors->detach('Containable'); + } + + /** + * Test for any possible conflicts with Containable behavior. + */ + public function testFilteringBelongsToAndHasManyWithContainable() + { + $testOptions = array( + 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), + 'DocumentCategory.id' => array('type' => 'select'), + 'Document.is_private' => array('type' => 'checkbox', 'label' => 'Private?'), + 'Item.code' => array('type' => 'text'), + ); + + $this->_reattachBehavior($testOptions); + $this->Document->Behaviors->attach('Containable'); + + $filterValues = array( + 'Document' => array('title' => 'in', 'is_private' => 0), + 'DocumentCategory' => array('id' => 1), + 'Item' => array('code' => '04') + ); + $this->Document->setFilterValues($filterValues); + + $expected = array( + array( + 'Document' => array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-03-28 12:19:13', 'updated' => '2010-04-29 11:23:44'), + 'DocumentCategory' => array('id' => 1, 'title' => 'Testing Doc', 'description' => 'It\'s a bleeding test doc!'), + 'Item' => array( + array('id' => 4, 'document_id' => 2, 'code' => 'The item #01'), + array('id' => 5, 'document_id' => 2, 'code' => 'The item #02'), + array('id' => 6, 'document_id' => 2, 'code' => 'The item #03'), + array('id' => 7, 'document_id' => 2, 'code' => 'The item #04') + ) + ) + ); + + $result = $this->Document->find('all', array('contain' => array('DocumentCategory', 'Item'))); + $this->assertEquals($expected, $result); + + $expected = array( + array( + 'Document' => array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-03-28 12:19:13', 'updated' => '2010-04-29 11:23:44'), + 'DocumentCategory' => array('id' => 1, 'title' => 'Testing Doc', 'description' => 'It\'s a bleeding test doc!'), + ) + ); + + $result = $this->Document->find('all', array('contain' => array('DocumentCategory'))); + $this->assertEquals($expected, $result); + + $expected = array( + array( + 'Document' => array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-03-28 12:19:13', 'updated' => '2010-04-29 11:23:44'), + ) + ); + + $result = $this->Document->find('all', array('contain' => array())); + $this->assertEquals($expected, $result); + + $this->Document->Behaviors->detach('Containable'); + } + + /** + * Test filtering by text input with hasOne relation. + */ + public function testHasOneAndHasManyWithTextSearch() + { + $testOptions = array( + 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), + 'Item.code' => array('type' => 'text'), + 'Metadata.size' => array('type' => 'text', 'condition' => '='), + ); + + $filterValues = array( + 'Document' => array('title' => 'in'), + 'Item' => array('code' => '04'), + 'Metadata' => array('size' => 45), + ); + + $expected = array( + array( + 'Document' => array('id' => 2, 'title' => 'Imaginary Spec'), + ) + ); + + $this->_reattachBehavior($testOptions); + $this->Document->setFilterValues($filterValues); + + $this->Document->recursive = -1; + $result = $this->Document->find('all', array('fields' => array('Document.id', 'Document.title'))); + $this->assertEquals($expected, $result); + } + + /** + * Test filtering with Containable and hasOne Model.field. + */ + public function testHasOneWithContainable() + { + $testOptions = array( + 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), + 'Item.code' => array('type' => 'text'), + 'Metadata.size' => array('type' => 'text', 'condition' => '='), + ); + + $filterValues = array( + 'Document' => array('title' => 'in'), + 'Item' => array('code' => '04'), + 'Metadata' => array('size' => 45), + ); + + $expected = array( + array( + 'Document' => array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-03-28 12:19:13', 'updated' => '2010-04-29 11:23:44'), + 'Metadata' => array('id' => 2, 'document_id' => 2, 'weight' => 0, 'size' => 45, 'permissions' => 'rw-------'), + 'Item' => array( + array('id' => 4, 'document_id' => 2, 'code' => 'The item #01'), + array('id' => 5, 'document_id' => 2, 'code' => 'The item #02'), + array('id' => 6, 'document_id' => 2, 'code' => 'The item #03'), + array('id' => 7, 'document_id' => 2, 'code' => 'The item #04') + ) + ) + ); + + // containable first, filtered second + $this->Document->Behaviors->attach('Containable'); + $this->_reattachBehavior($testOptions); + $this->Document->setFilterValues($filterValues); + $result = $this->Document->find('all', array('contain' => array('Metadata', 'Item'))); + $this->assertEquals($expected, $result); + $this->Document->Behaviors->detach('Containable'); + + // filtered first, containable second + $this->_reattachBehavior($testOptions); + $this->Document->setFilterValues($filterValues); + $this->Document->Behaviors->attach('Containable'); + $result = $this->Document->find('all', array('contain' => array('Metadata', 'Item'))); + $this->assertEquals($expected, $result); + $this->Document->Behaviors->detach('Containable'); + } + + /** + * Test filtering when a join is already present in the query, + * this should prevent duplicate joins and query errors. + */ + public function testJoinAlreadyPresent() + { + $testOptions = array( + 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), + 'Item.code' => array('type' => 'text'), + 'Metadata.size' => array('type' => 'text', 'condition' => '='), + ); + + $filterValues = array( + 'Document' => array('title' => 'in'), + 'Item' => array('code' => '04'), + 'Metadata' => array('size' => 45), + ); + + $expected = array( + array( + 'Document' => array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-03-28 12:19:13', 'updated' => '2010-04-29 11:23:44'), + 'DocumentCategory' => array('id' => 1, 'title' => 'Testing Doc', 'description' => 'It\'s a bleeding test doc!'), + 'Metadata' => array('id' => 2, 'document_id' => 2, 'weight' => 0, 'size' => 45, 'permissions' => 'rw-------'), + 'Item' => array( + array('id' => 4, 'document_id' => 2, 'code' => 'The item #01'), + array('id' => 5, 'document_id' => 2, 'code' => 'The item #02'), + array('id' => 6, 'document_id' => 2, 'code' => 'The item #03'), + array('id' => 7, 'document_id' => 2, 'code' => 'The item #04') + ) + ) + ); + + $customJoin = array(); + $customJoin[] = array( + 'table' => 'items', + 'alias' => 'FilterItem', + 'type' => 'INNER', + 'conditions' => 'Document.id = FilterItem.document_id', + ); + + $this->_reattachBehavior($testOptions); + $this->Document->setFilterValues($filterValues); + $result = $this->Document->find('all', array('joins' => $customJoin, 'recursive' => 1)); + $this->assertEquals($expected, $result); + } + + /** + * Test the 'nofilter' query param. + */ + public function testNofilterFindParam() + { + $testOptions = array( + 'Document.title' => array('type' => 'text', 'condition' => 'like'), + 'DocumentCategory.id' => array('type' => 'select'), + 'Document.is_private' => array('type' => 'checkbox', 'label' => 'Private?', 'default' => 0) + ); + $this->_reattachBehavior($testOptions); + + $filterValues = array( + 'DocumentCategory' => array('id' => 2), + 'Document' => array('title' => '') + ); + $this->Document->setFilterValues($filterValues); + + $expected = array( + array('Document' => array('id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0, 'created' => '2009-01-13 05:15:03', 'updated' => '2010-12-05 03:24:15')) + ); + + $result = $this->Document->find('all', array('recursive' => -1, 'nofilter' => true)); + $this->assertNotEquals($expected, $result); + + $result = $this->Document->find('all', array('recursive' => -1, 'nofilter' => 'true')); + $this->assertEquals($expected, $result); + } + + /** + * Test bailing out if no settings exist for the current model. + */ + public function testExitWhenNoSettings() + { + $this->Document->DocumentCategory->Behaviors->attach('Filter.Filtered'); + + $this->assertFalse(isset($this->Document->DocumentCategory->Behaviors->Filtered->settings[$this->Document->DocumentCategory->alias])); + + $filterValues = array( + 'DocumentCategory' => array('id' => 2) + ); + $this->Document->DocumentCategory->setFilterValues($filterValues); + + $expected = array( + array('DocumentCategory' => array('id' => 1, 'title' => 'Testing Doc', 'description' => 'It\'s a bleeding test doc!')), + array('DocumentCategory' => array('id' => 2, 'title' => 'Imaginary Spec', 'description' => 'This doc does not exist')), + array('DocumentCategory' => array('id' => 3, 'title' => 'Nonexistant data', 'description' => 'This doc is probably empty')), + array('DocumentCategory' => array('id' => 4, 'title' => 'Illegal explosives DIY', 'description' => 'Viva la revolucion!')), + array('DocumentCategory' => array('id' => 5, 'title' => 'Father Ted', 'description' => 'Feck! Drink! Arse! Girls!')) + ); + + $result = $this->Document->DocumentCategory->find('all', array('recursive' => -1)); + $this->assertEquals($expected, $result); + + $this->Document->DocumentCategory->Behaviors->detach('Filtered'); + } + + /** + * Test beforeDataFilter() callback, used to cancel filtering if necessary. + */ + public function testBeforeDataFilterCallbackCancel() + { + $this->Document = ClassRegistry::init('Document2'); + + $testOptions = array( + 'Document.title' => array('type' => 'text', 'condition' => 'like'), + 'DocumentCategory.id' => array('type' => 'select'), + 'Document.is_private' => array('type' => 'checkbox', 'label' => 'Private?') + ); + $this->_reattachBehavior($testOptions); + + $filterValues = array( + 'DocumentCategory' => array('id' => 2) + ); + $this->Document->setFilterValues($filterValues); + + $expected = array( + array('Document' => array('id' => 1, 'title' => 'Testing Doc', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-06-28 10:39:23', 'updated' => '2010-06-29 11:22:48')), + array('Document' => array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-03-28 12:19:13', 'updated' => '2010-04-29 11:23:44')), + array('Document' => array('id' => 3, 'title' => 'Nonexistant data', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-04-28 11:12:33', 'updated' => '2010-05-05 15:03:24')), + array('Document' => array('id' => 4, 'title' => 'Illegal explosives DIY', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 1, 'created' => '2010-01-08 05:15:03', 'updated' => '2010-05-22 03:15:24')), + array('Document' => array('id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0, 'created' => '2009-01-13 05:15:03', 'updated' => '2010-12-05 03:24:15')), + array('Document' => array('id' => 6, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0, 'created' => '2009-01-13 05:15:03', 'updated' => '2010-12-05 03:24:15')), + array('Document' => array('id' => 7, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0, 'created' => '2009-01-13 05:15:03', 'updated' => '2010-12-05 03:24:15')), + ); + + $result = $this->Document->find('all', array('recursive' => -1)); + $this->assertEquals($expected, $result); + } + + /** + * Test afterDataFilter() callback, used to modify the conditions after + * filter conditions have been applied. + */ + public function testAfterDataFilterCallbackQueryChange() + { + $this->Document = ClassRegistry::init('Document3'); + $this->Document->itemToUnset = 'FilterDocumentCategory.id'; + + $testOptions = array( + 'Document.title' => array('type' => 'text', 'condition' => 'like'), + 'DocumentCategory.id' => array('type' => 'select'), + 'Document.is_private' => array('type' => 'checkbox', 'label' => 'Private?') + ); + $this->_reattachBehavior($testOptions); + + $filterValues = array( + 'DocumentCategory' => array('id' => 2) + ); + $this->Document->setFilterValues($filterValues); + + $expected = array( + array('Document' => array('id' => 1, 'title' => 'Testing Doc', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-06-28 10:39:23', 'updated' => '2010-06-29 11:22:48')), + array('Document' => array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-03-28 12:19:13', 'updated' => '2010-04-29 11:23:44')), + array('Document' => array('id' => 3, 'title' => 'Nonexistant data', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-04-28 11:12:33', 'updated' => '2010-05-05 15:03:24')), + array('Document' => array('id' => 4, 'title' => 'Illegal explosives DIY', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 1, 'created' => '2010-01-08 05:15:03', 'updated' => '2010-05-22 03:15:24')), + array('Document' => array('id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0, 'created' => '2009-01-13 05:15:03', 'updated' => '2010-12-05 03:24:15')), + array('Document' => array('id' => 6, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0, 'created' => '2009-01-13 05:15:03', 'updated' => '2010-12-05 03:24:15')), + array('Document' => array('id' => 7, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0, 'created' => '2009-01-13 05:15:03', 'updated' => '2010-12-05 03:24:15')), + ); + + $result = $this->Document->find('all', array('recursive' => -1)); + $this->assertEquals($expected, $result); + } } diff --git a/Test/Fixture/DocumentCategoryFixture.php b/Test/Fixture/DocumentCategoryFixture.php index 4b5fd5b..32fbe31 100644 --- a/Test/Fixture/DocumentCategoryFixture.php +++ b/Test/Fixture/DocumentCategoryFixture.php @@ -1,33 +1,31 @@ - - Multi-licensed under: - MPL - LGPL - GPL -*/ + * CakePHP Filter Plugin + * + * Copyright (C) 2009-3827 dr. Hannibal Lecter / lecterror + * + * + * Multi-licensed under: + * MPL + * LGPL + * GPL + */ class DocumentCategoryFixture extends CakeTestFixture { - public $name = 'DocumentCategory'; + public $name = 'DocumentCategory'; - public $fields = array - ( - 'id' => array('type' => 'integer', 'key' => 'primary'), - 'title' => array('type' => 'string', 'length' => 100, 'null' => false), - 'description' => array('type' => 'string', 'length' => 255) - ); + public $fields = array( + 'id' => array('type' => 'integer', 'key' => 'primary'), + 'title' => array('type' => 'string', 'length' => 100, 'null' => false), + 'description' => array('type' => 'string', 'length' => 255) + ); - public $records = array - ( - array('id' => 1, 'title' => 'Testing Doc', 'description' => 'It\'s a bleeding test doc!'), - array('id' => 2, 'title' => 'Imaginary Spec', 'description' => 'This doc does not exist'), - array('id' => 3, 'title' => 'Nonexistant data', 'description' => 'This doc is probably empty'), - array('id' => 4, 'title' => 'Illegal explosives DIY', 'description' => 'Viva la revolucion!'), - array('id' => 5, 'title' => 'Father Ted', 'description' => 'Feck! Drink! Arse! Girls!') - ); + public $records = array( + array('id' => 1, 'title' => 'Testing Doc', 'description' => 'It\'s a bleeding test doc!'), + array('id' => 2, 'title' => 'Imaginary Spec', 'description' => 'This doc does not exist'), + array('id' => 3, 'title' => 'Nonexistant data', 'description' => 'This doc is probably empty'), + array('id' => 4, 'title' => 'Illegal explosives DIY', 'description' => 'Viva la revolucion!'), + array('id' => 5, 'title' => 'Father Ted', 'description' => 'Feck! Drink! Arse! Girls!') + ); } diff --git a/Test/Fixture/DocumentFixture.php b/Test/Fixture/DocumentFixture.php index ce158db..8d9a685 100644 --- a/Test/Fixture/DocumentFixture.php +++ b/Test/Fixture/DocumentFixture.php @@ -1,39 +1,37 @@ - - Multi-licensed under: - MPL - LGPL - GPL -*/ + * CakePHP Filter Plugin + * + * Copyright (C) 2009-3827 dr. Hannibal Lecter / lecterror + * + * + * Multi-licensed under: + * MPL + * LGPL + * GPL + */ class DocumentFixture extends CakeTestFixture { - public $name = 'Document'; + public $name = 'Document'; - public $fields = array - ( - 'id' => array('type' => 'integer', 'key' => 'primary'), - 'title' => array('type' => 'string', 'length' => '255', 'null' => false), - 'document_category_id' => array('type' => 'integer', 'null' => false), - 'owner_id' => array('type' => 'integer', 'null' => false), - 'is_private' => array('type' => 'integer', 'length' => 1, 'null' => false), - 'created' => array('type' => 'datetime', 'null' => false), - 'updated' => array('type' => 'datetime', 'null' => true) - ); + public $fields = array( + 'id' => array('type' => 'integer', 'key' => 'primary'), + 'title' => array('type' => 'string', 'length' => '255', 'null' => false), + 'document_category_id' => array('type' => 'integer', 'null' => false), + 'owner_id' => array('type' => 'integer', 'null' => false), + 'is_private' => array('type' => 'integer', 'length' => 1, 'null' => false), + 'created' => array('type' => 'datetime', 'null' => false), + 'updated' => array('type' => 'datetime', 'null' => true) + ); - public $records = array - ( - array('id' => 1, 'title' => 'Testing Doc', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-06-28 10:39:23', 'updated' => '2010-06-29 11:22:48'), - array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-03-28 12:19:13', 'updated' => '2010-04-29 11:23:44'), - array('id' => 3, 'title' => 'Nonexistant data', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-04-28 11:12:33', 'updated' => '2010-05-05 15:03:24'), - array('id' => 4, 'title' => 'Illegal explosives DIY', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 1, 'created' => '2010-01-08 05:15:03', 'updated' => '2010-05-22 03:15:24'), - array('id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0, 'created' => '2009-01-13 05:15:03', 'updated' => '2010-12-05 03:24:15'), - array('id' => 6, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0, 'created' => '2009-01-13 05:15:03', 'updated' => '2010-12-05 03:24:15'), - array('id' => 7, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0, 'created' => '2009-01-13 05:15:03', 'updated' => '2010-12-05 03:24:15'), - ); + public $records = array( + array('id' => 1, 'title' => 'Testing Doc', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-06-28 10:39:23', 'updated' => '2010-06-29 11:22:48'), + array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-03-28 12:19:13', 'updated' => '2010-04-29 11:23:44'), + array('id' => 3, 'title' => 'Nonexistant data', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-04-28 11:12:33', 'updated' => '2010-05-05 15:03:24'), + array('id' => 4, 'title' => 'Illegal explosives DIY', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 1, 'created' => '2010-01-08 05:15:03', 'updated' => '2010-05-22 03:15:24'), + array('id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0, 'created' => '2009-01-13 05:15:03', 'updated' => '2010-12-05 03:24:15'), + array('id' => 6, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0, 'created' => '2009-01-13 05:15:03', 'updated' => '2010-12-05 03:24:15'), + array('id' => 7, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0, 'created' => '2009-01-13 05:15:03', 'updated' => '2010-12-05 03:24:15'), + ); } diff --git a/Test/Fixture/ItemFixture.php b/Test/Fixture/ItemFixture.php index 9cbe58a..ad510f5 100644 --- a/Test/Fixture/ItemFixture.php +++ b/Test/Fixture/ItemFixture.php @@ -1,38 +1,36 @@ - - Multi-licensed under: - MPL - LGPL - GPL -*/ + * CakePHP Filter Plugin + * + * Copyright (C) 2009-3827 dr. Hannibal Lecter / lecterror + * + * + * Multi-licensed under: + * MPL + * LGPL + * GPL + */ class ItemFixture extends CakeTestFixture { - public $name = 'Item'; + public $name = 'Item'; - public $fields = array - ( - 'id' => array('type' => 'integer', 'key' => 'primary'), - 'document_id' => array('type' => 'integer', 'null' => false), - 'code' => array('type' => 'string', 'length' => '20', 'null' => false) - ); + public $fields = array( + 'id' => array('type' => 'integer', 'key' => 'primary'), + 'document_id' => array('type' => 'integer', 'null' => false), + 'code' => array('type' => 'string', 'length' => '20', 'null' => false) + ); - public $records = array - ( - array('id' => 1, 'document_id' => 1, 'code' => 'The item #01'), - array('id' => 2, 'document_id' => 1, 'code' => 'The item #02'), - array('id' => 3, 'document_id' => 1, 'code' => 'The item #03'), - array('id' => 4, 'document_id' => 2, 'code' => 'The item #01'), - array('id' => 5, 'document_id' => 2, 'code' => 'The item #02'), - array('id' => 6, 'document_id' => 2, 'code' => 'The item #03'), - array('id' => 7, 'document_id' => 2, 'code' => 'The item #04'), - array('id' => 8, 'document_id' => 3, 'code' => 'The item #01'), - array('id' => 9, 'document_id' => 4, 'code' => 'The item #01'), - array('id' => 10, 'document_id' => 5, 'code' => 'The item #01') - ); + public $records = array( + array('id' => 1, 'document_id' => 1, 'code' => 'The item #01'), + array('id' => 2, 'document_id' => 1, 'code' => 'The item #02'), + array('id' => 3, 'document_id' => 1, 'code' => 'The item #03'), + array('id' => 4, 'document_id' => 2, 'code' => 'The item #01'), + array('id' => 5, 'document_id' => 2, 'code' => 'The item #02'), + array('id' => 6, 'document_id' => 2, 'code' => 'The item #03'), + array('id' => 7, 'document_id' => 2, 'code' => 'The item #04'), + array('id' => 8, 'document_id' => 3, 'code' => 'The item #01'), + array('id' => 9, 'document_id' => 4, 'code' => 'The item #01'), + array('id' => 10, 'document_id' => 5, 'code' => 'The item #01') + ); } diff --git a/Test/Fixture/MetadataFixture.php b/Test/Fixture/MetadataFixture.php index 872a551..c942775 100644 --- a/Test/Fixture/MetadataFixture.php +++ b/Test/Fixture/MetadataFixture.php @@ -1,35 +1,33 @@ - - Multi-licensed under: - MPL - LGPL - GPL -*/ + * CakePHP Filter Plugin + * + * Copyright (C) 2009-3827 dr. Hannibal Lecter / lecterror + * + * + * Multi-licensed under: + * MPL + * LGPL + * GPL + */ class MetadataFixture extends CakeTestFixture { - public $name = 'Metadata'; + public $name = 'Metadata'; - public $fields = array - ( - 'id' => array('type' => 'integer', 'key' => 'primary'), - 'document_id' => array('type' => 'integer', 'null' => false), - 'weight' => array('type' => 'integer', 'null' => false), - 'size' => array('type' => 'integer', 'null' => false), - 'permissions' => array('type' => 'string', 'length' => 10, 'null' => false), - ); + public $fields = array( + 'id' => array('type' => 'integer', 'key' => 'primary'), + 'document_id' => array('type' => 'integer', 'null' => false), + 'weight' => array('type' => 'integer', 'null' => false), + 'size' => array('type' => 'integer', 'null' => false), + 'permissions' => array('type' => 'string', 'length' => 10, 'null' => false), + ); - public $records = array - ( - array('id' => 1, 'document_id' => 1, 'weight' => 5, 'size' => 256, 'permissions' => 'rw-r--r--'), - array('id' => 2, 'document_id' => 2, 'weight' => 0, 'size' => 45, 'permissions' => 'rw-------'), - array('id' => 3, 'document_id' => 3, 'weight' => 2, 'size' => 78, 'permissions' => 'rw-rw-r--'), - array('id' => 4, 'document_id' => 4, 'weight' => 1, 'size' => 412, 'permissions' => 'rw-r--r--'), - array('id' => 5, 'document_id' => 5, 'weight' => 4, 'size' => 790, 'permissions' => 'rw-rw-r--'), - ); + public $records = array( + array('id' => 1, 'document_id' => 1, 'weight' => 5, 'size' => 256, 'permissions' => 'rw-r--r--'), + array('id' => 2, 'document_id' => 2, 'weight' => 0, 'size' => 45, 'permissions' => 'rw-------'), + array('id' => 3, 'document_id' => 3, 'weight' => 2, 'size' => 78, 'permissions' => 'rw-rw-r--'), + array('id' => 4, 'document_id' => 4, 'weight' => 1, 'size' => 412, 'permissions' => 'rw-r--r--'), + array('id' => 5, 'document_id' => 5, 'weight' => 4, 'size' => 790, 'permissions' => 'rw-rw-r--'), + ); } diff --git a/View/Elements/filter_form_begin.ctp b/View/Elements/filter_form_begin.ctp index 7bba9dc..17345cf 100644 --- a/View/Elements/filter_form_begin.ctp +++ b/View/Elements/filter_form_begin.ctp @@ -1,34 +1,31 @@ - - Multi-licensed under: - MPL - LGPL - GPL -*/ + * CakePHP Filter Plugin + * + * Copyright (C) 2009-3827 dr. Hannibal Lecter / lecterror + * + * + * Multi-licensed under: + * MPL + * LGPL + * GPL + */ ?>
- Form->create( - false, - array( - 'url' => array( - 'plugin' => $this->request->params['plugin'], - 'controller' => $this->request->params['controller'], - 'action' => $this->request->params['action'], - ), - 'id' => $modelName.'Filter', - ) + $options - ); ?> - Form->inputDefaults(array('required' => false)); ?> -
- - Form->input('Filter.filterFormId', array('type' => 'hidden', 'value' => $modelName)); ?> + Form->create( + false, + array( + 'url' => array( + 'plugin' => $this->request->params['plugin'], + 'controller' => $this->request->params['controller'], + 'action' => $this->request->params['action'], + ), + 'id' => $modelName . 'Filter', + ) + $options + ); ?> + Form->inputDefaults(array('required' => false)); ?> +
+ + + + Form->input('Filter.filterFormId', array('type' => 'hidden', 'value' => $modelName)); ?> diff --git a/View/Elements/filter_form_end.ctp b/View/Elements/filter_form_end.ctp index 4985522..da8215c 100644 --- a/View/Elements/filter_form_end.ctp +++ b/View/Elements/filter_form_end.ctp @@ -1,17 +1,17 @@ - - Multi-licensed under: - MPL - LGPL - GPL -*/ + * CakePHP Filter Plugin + * + * Copyright (C) 2009-3827 dr. Hannibal Lecter / lecterror + * + * + * Multi-licensed under: + * MPL + * LGPL + * GPL + */ ?> -
- Form->submit(__('Submit')); ?> - Form->end(); ?> +
+Form->submit(__('Submit')); ?> +Form->end(); ?>
diff --git a/View/Elements/filter_form_fields.ctp b/View/Elements/filter_form_fields.ctp index 0d1ba57..8e142fd 100644 --- a/View/Elements/filter_form_fields.ctp +++ b/View/Elements/filter_form_fields.ctp @@ -1,27 +1,24 @@ + * + * Multi-licensed under: + * MPL + * LGPL + * GPL + */ - Copyright (C) 2009-3827 dr. Hannibal Lecter / lecterror - - - Multi-licensed under: - MPL - LGPL - GPL -*/ - -if (isset($viewFilterParams)) -{ - foreach ($viewFilterParams as $field) - { - if(empty($includeFields) || in_array($field['name'], $includeFields)) - { - $fieldName = explode('.', $field['name']); - if (count($fieldName) === 2) { - $field['options']['name'] = sprintf('data[%s][%s]', $fieldName[0], $fieldName[1]); - } - echo $this->Form->input($field['name'], $field['options']); - } - } +if (isset($viewFilterParams)) { + foreach ($viewFilterParams as $field) { + if (empty($includeFields) || in_array($field['name'], $includeFields)) { + $fieldName = explode('.', $field['name']); + if (count($fieldName) === 2) { + $field['options']['name'] = sprintf('data[%s][%s]', $fieldName[0], $fieldName[1]); + } + echo $this->Form->input($field['name'], $field['options']); + } + } } diff --git a/View/Helper/FilterHelper.php b/View/Helper/FilterHelper.php index 682ffd0..b3e276c 100644 --- a/View/Helper/FilterHelper.php +++ b/View/Helper/FilterHelper.php @@ -1,104 +1,95 @@ - - Multi-licensed under: - MPL - LGPL - GPL -*/ + * CakePHP Filter Plugin + * + * Copyright (C) 2009-3827 dr. Hannibal Lecter / lecterror + * + * + * Multi-licensed under: + * MPL + * LGPL + * GPL + */ App::uses('AppHelper', 'View/Helper'); class FilterHelper extends AppHelper { - protected $_view = null; + protected $_view = null; - public function __construct(View $view, $settings = array()) - { - $this->_view = $view; - } + public function __construct(View $view, $settings = array()) + { + $this->_view = $view; + } - public function filterForm($modelName, $options) - { - $view =& $this->_view; + public function filterForm($modelName, $options) + { + $view =& $this->_view; - $output = $view->element - ( - 'filter_form_begin', - array - ( - 'plugin' => 'Filter', - 'modelName' => $modelName, - 'options' => $options - ), - array('plugin' => 'Filter') - ); + $output = $view->element( + 'filter_form_begin', + array( + 'plugin' => 'Filter', + 'modelName' => $modelName, + 'options' => $options + ), + array('plugin' => 'Filter') + ); - $output .= $view->element - ( - 'filter_form_fields', - array('plugin' => 'Filter'), - array('plugin' => 'Filter') - ); + $output .= $view->element( + 'filter_form_fields', + array('plugin' => 'Filter'), + array('plugin' => 'Filter') + ); - $output .= $view->element - ( - 'filter_form_end', - array('plugin' => 'Filter'), - array('plugin' => 'Filter') - ); + $output .= $view->element( + 'filter_form_end', + array('plugin' => 'Filter'), + array('plugin' => 'Filter') + ); - return $output; - } + return $output; + } - public function beginForm($modelName, $options) - { - $view =& $this->_view; - $output = $view->element - ( - 'filter_form_begin', - array - ( - 'plugin' => 'Filter', - 'modelName' => $modelName, - 'options' => $options - ), - array('plugin' => 'Filter') - ); + public function beginForm($modelName, $options) + { + $view =& $this->_view; + $output = $view->element( + 'filter_form_begin', + array( + 'plugin' => 'Filter', + 'modelName' => $modelName, + 'options' => $options + ), + array('plugin' => 'Filter') + ); - return $output; - } + return $output; + } - public function inputFields($fields = array()) - { - $view =& $this->_view; - $output = $view->element - ( - 'filter_form_fields', - array - ( - 'plugin' => 'Filter', - 'includeFields' => $fields - ), - array('plugin' => 'Filter') - ); + public function inputFields($fields = array()) + { + $view =& $this->_view; + $output = $view->element( + 'filter_form_fields', + array( + 'plugin' => 'Filter', + 'includeFields' => $fields + ), + array('plugin' => 'Filter') + ); - return $output; - } + return $output; + } - public function endForm() - { - $view = $this->_view; - $output = $view->element - ( - 'filter_form_end', - array(), - array('plugin' => 'Filter') - ); + public function endForm() + { + $view = $this->_view; + $output = $view->element( + 'filter_form_end', + array(), + array('plugin' => 'Filter') + ); - return $output; - } + return $output; + } } From 36d32a9dede9da7f66f94b2999dc99b5d5863691 Mon Sep 17 00:00:00 2001 From: Josef Stich Date: Wed, 3 Apr 2024 08:32:38 +0200 Subject: [PATCH 05/38] various fixes for phpstan level9 code analysis #3 --- Controller/Component/FilterComponent.php | 85 +++++++++++++++---- Model/Behavior/FilteredBehavior.php | 77 ++++++++++++----- Test/Case/AllTest.php | 2 +- .../Component/FilterComponentTest.php | 60 ++++++++----- Test/Case/MockObjects/Document.php | 19 +++++ Test/Case/MockObjects/Document2.php | 25 +++++- Test/Case/MockObjects/Document3.php | 25 +++++- Test/Case/MockObjects/DocumentCategory.php | 16 +++- .../MockObjects/DocumentTestsController.php | 16 +++- Test/Case/MockObjects/Item.php | 7 ++ Test/Case/MockObjects/Metadata.php | 7 ++ .../Model/Behaviors/FilteredBehaviorTest.php | 79 +++++++++++------ Test/Fixture/DocumentCategoryFixture.php | 6 ++ Test/Fixture/DocumentFixture.php | 6 ++ Test/Fixture/ItemFixture.php | 6 ++ Test/Fixture/MetadataFixture.php | 9 ++ View/Helper/FilterHelper.php | 32 +++++-- phpstan.neon | 5 ++ phpstan/stubs/CakePHP.stub | 16 ++++ 19 files changed, 401 insertions(+), 97 deletions(-) create mode 100644 phpstan/stubs/CakePHP.stub diff --git a/Controller/Component/FilterComponent.php b/Controller/Component/FilterComponent.php index 32c380f..81413b7 100644 --- a/Controller/Component/FilterComponent.php +++ b/Controller/Component/FilterComponent.php @@ -20,20 +20,46 @@ */ class FilterComponent extends Component { + /** + * @var string[] + */ public $components = array('Session'); + /** + * @var array> + */ public $settings = array(); + + /** + * @var array + */ public $nopersist = array(); + + /** + * @var array> + */ public $formData = array(); + + /** + * @var array + */ protected $_request_settings = array(); + /** + * @param ComponentCollection $collection + * @param array $settings + */ public function __construct(ComponentCollection $collection, $settings = array()) { parent::__construct($collection, $settings); $this->_request_settings = $settings; } - public function initialize(Controller $controller) + /** + * @param Controller $controller + * @return void + */ + public function initialize(Controller $controller): void { if (!isset($controller->filters)) { return; @@ -50,7 +76,10 @@ public function initialize(Controller $controller) foreach ($settings as $model => $filter) { if (!isset($controller->{$model})) { - trigger_error(__('Filter model not found: %s', $model)); + $errMsg = __('Filter model not found: %s', $model); + if (is_string($errMsg)) { + trigger_error($errMsg); + } continue; } @@ -64,6 +93,7 @@ public function startup(Controller $controller) return; } + /** @var array> $settings */ $settings = $this->settings[$controller->name][$controller->request->action]; if (!in_array('Filter.Filter', $controller->helpers)) { @@ -73,7 +103,10 @@ public function startup(Controller $controller) $sessionKey = sprintf('FilterPlugin.Filters.%s.%s', $controller->name, $controller->request->action); $filterFormId = $controller->request->query('filterFormId'); if ($controller->request->is('get') && !empty($filterFormId)) { - $this->formData = $controller->request->query('data'); + $dataFromQuery = $controller->request->query('data'); + if (is_array($dataFromQuery)) { + $this->formData = $dataFromQuery; + } } elseif (!$controller->request->is('post') || !isset($controller->request->data['Filter']['filterFormId'])) { $persistedData = array(); @@ -81,7 +114,7 @@ public function startup(Controller $controller) $persistedData = $this->Session->read($sessionKey); } - if (empty($persistedData)) { + if (empty($persistedData) || !is_array($persistedData)) { return; } @@ -95,7 +128,10 @@ public function startup(Controller $controller) foreach ($settings as $model => $options) { if (!isset($controller->{$model})) { - trigger_error(__('Filter model not found: %s', $model)); + $errMsg = __('Filter model not found: %s', $model); + if (is_string($errMsg)) { + trigger_error($errMsg); + } continue; } @@ -109,12 +145,16 @@ public function beforeRender(Controller $controller) return; } + /** @var array>> $models */ $models = $this->settings[$controller->name][$controller->request->action]; $viewFilterParams = array(); foreach ($models as $model => $fields) { if (!isset($controller->$model)) { - trigger_error(__('Filter model not found: %s', $model)); + $errMsg = __('Filter model not found: %s', $model); + if (is_string($errMsg)) { + trigger_error($errMsg); + } continue; } @@ -172,6 +212,7 @@ public function beforeRender(Controller $controller) $options['type'] = 'select'; $selectOptions = array(); + /** @var Model $workingModel */ $workingModel = ClassRegistry::init($fieldModel); if (isset($settings['selectOptions'])) { @@ -180,15 +221,15 @@ public function beforeRender(Controller $controller) if (isset($settings['selector'])) { if (!method_exists($workingModel, $settings['selector'])) { - trigger_error - ( - __( - 'Selector method "%s" not found in model "%s" for field "%s"!', - $settings['selector'], - $fieldModel, - $fieldName - ) + $errMsg = __( + 'Selector method "%s" not found in model "%s" for field "%s"!', + $settings['selector'], + $fieldModel, + $fieldName ); + if (is_string($errMsg)) { + trigger_error($errMsg); + } return; } @@ -246,8 +287,7 @@ public function beforeRender(Controller $controller) $options['value'] = $settings['default']; } - $viewFilterParams[] = array - ( + $viewFilterParams[] = array( 'name' => sprintf('%s.%s', $fieldModel, $fieldName), 'options' => $options ); @@ -273,10 +313,18 @@ public function beforeRender(Controller $controller) $controller->set('viewFilterParams', $viewFilterParams); } - private function __updatePersistence($controller, $settings) + /** + * @param Controller $controller + * @param array $settings + * @return void + */ + private function __updatePersistence(Controller $controller, array $settings): void { if ($this->Session->check('FilterPlugin.NoPersist')) { - $this->nopersist = $this->Session->read('FilterPlugin.NoPersist'); + $nopersistFromSession = $this->Session->read('FilterPlugin.NoPersist'); + if (is_array($nopersistFromSession)) { + $this->nopersist = $nopersistFromSession; + } } if (isset($settings['nopersist'])) { @@ -299,6 +347,7 @@ private function __updatePersistence($controller, $settings) $actions = array(); } + /** @var string[] $actions */ if (empty($actions) && $controller->name != $nopersistController) { if ($this->Session->check(sprintf('FilterPlugin.Filters.%s', $nopersistController))) { $this->Session->delete(sprintf('FilterPlugin.Filters.%s', $nopersistController)); diff --git a/Model/Behavior/FilteredBehavior.php b/Model/Behavior/FilteredBehavior.php index 805d9c0..aa166bc 100644 --- a/Model/Behavior/FilteredBehavior.php +++ b/Model/Behavior/FilteredBehavior.php @@ -18,10 +18,15 @@ class FilteredBehavior extends ModelBehavior /** * Keeps current values after filter form post. * - * @var array + * @var array> */ protected $_filterValues = array(); + /** + * @param Model $Model + * @param array $settings + * @return void + */ public function setup(Model $Model, $settings = array()) { foreach ($settings as $key => $value) { @@ -44,7 +49,12 @@ public function setup(Model $Model, $settings = array()) $this->_filterValues[$Model->alias] = array(); } - public function beforeFind(Model $Model, $query) + /** + * @param Model $Model + * @param array $query + * @return array + */ + public function beforeFind(Model $Model, $query): array { if (isset($query['nofilter']) && $query['nofilter'] === true) { return $query; @@ -64,12 +74,14 @@ public function beforeFind(Model $Model, $query) } $settings = $this->settings[$Model->alias]; + /** @var array> $values */ $values = $this->_filterValues[$Model->alias]; foreach ($settings as $field => $options) { $this->addFieldToFilter($Model, $query, $settings, $values, $field, $options); } + /** @var Model $Model */ if (method_exists($Model, 'afterDataFilter')) { $callbackOptions['values'] = $this->_filterValues[$Model->alias]; $callbackOptions['settings'] = $this->settings[$Model->alias]; @@ -84,7 +96,16 @@ public function beforeFind(Model $Model, $query) return $query; } - protected function addFieldToFilter(&$Model, &$query, $settings, $values, $field, $field_options) + /** + * @param Model $Model + * @param array $query + * @param array $settings + * @param array> $values + * @param string $field + * @param array $field_options + * @return void + */ + protected function addFieldToFilter(Model &$Model, array &$query, array $settings, array $values, string $field, array $field_options): void { $configurationModelName = $Model->alias; $configurationFieldName = $field; @@ -99,7 +120,10 @@ protected function addFieldToFilter(&$Model, &$query, $settings, $values, $field if ($field_options['required'] && !isset($values[$configurationModelName][$configurationFieldName])) { // TODO: implement a bit of a user friendly handling of this scenario.. - trigger_error(__('No value present for required field %s and default value not present', $field)); + $errMsg = __('No value present for required field %s and default value not present', $field); + if (is_string($errMsg)) { + trigger_error($errMsg); + } return; } @@ -175,9 +199,10 @@ protected function addFieldToFilter(&$Model, &$query, $settings, $values, $field * * @param Model $Model * @param Model $relatedModel - * @return array Cake join array + * @param string $linkModelName + * @return array Cake join array */ - protected function buildFilterJoin(Model &$Model, Model &$relatedModel, $linkModelName) + protected function buildFilterJoin(Model &$Model, Model &$relatedModel, string $linkModelName): array { $conditions = array(); $relationTypes = array('hasMany', 'hasOne', 'belongsTo', 'hasAndBelongsToMany'); @@ -240,7 +265,14 @@ protected function buildFilterJoin(Model &$Model, Model &$relatedModel, $linkMod $customConditions = array($customConditions); } - $filterConditions = preg_replace(sprintf('#(?alias), $relatedModelAlias, $customConditions); + $filterConditions = preg_replace( + sprintf( + '#(?alias + ), + (is_string($relatedModelAlias) ? $relatedModelAlias : ''), + $customConditions + ); $conditions = array_merge($conditions, $filterConditions); } @@ -280,12 +312,12 @@ protected function buildFilterJoin(Model &$Model, Model &$relatedModel, $linkMod /** * Build query conditions and add them to $query. * - * @param array $query Cake query array. + * @param array> $query Cake query array. * @param string $field Filter field. - * @param array $options Configuration options for this field. + * @param array $options Configuration options for this field. * @param mixed $value Field value. */ - protected function buildFilterConditions(array &$query, $field, $options, $value) + protected function buildFilterConditions(array &$query, string $field, array $options, $value): void { $conditionFieldFormats = array ( 'like' => '%s like', @@ -310,6 +342,7 @@ protected function buildFilterConditions(array &$query, $field, $options, $value switch ($options['type']) { case 'text': + /** @var bool|float|int|string|null $value */ if (strlen(trim(strval($value))) == 0) { break; } @@ -354,15 +387,16 @@ protected function buildFilterConditions(array &$query, $field, $options, $value /** * Makes a string SQL-safe. * - * @param string $string String to sanitize. + * @param mixed $string String to sanitize. * @param string $connection Database connection being used. - * @return string SQL safe string. + * @return mixed SQL safe string. */ - private function __escape($string, $connection = 'default') + private function __escape($string, string $connection = 'default') { - if (is_numeric($string) || $string === null || is_bool($string)) { + if (!is_string($string)) { return $string; } + /** @var DboSource $db */ $db = ConnectionManager::getDataSource($connection); $string = $db->value($string, 'string'); $start = 1; @@ -375,11 +409,11 @@ private function __escape($string, $connection = 'default') /** * Makes an array SQL-safe. * - * @param string|array $data Data to sanitize. - * @param string $options DB connection being used. + * @param mixed $data Data to sanitize. + * @param string $connection DB connection being used. * @return mixed Sanitized data. */ - private function __clean($data, $connection = 'default') + private function __clean($data, string $connection = 'default') { if (empty($data)) { return $data; @@ -397,9 +431,9 @@ private function __clean($data, $connection = 'default') * Sets filter values. * * @param Model $Model Current model. - * @param array $values Filter values. + * @param array $values Filter values. */ - public function setFilterValues(&$Model, $values = array()) + public function setFilterValues(Model &$Model, array $values = array()): void { $values = $this->__clean($values, $Model->useDbConfig); $this->_filterValues[$Model->alias] = array_merge($this->_filterValues[$Model->alias], (array)$values); @@ -408,10 +442,9 @@ public function setFilterValues(&$Model, $values = array()) /** * Gets filter values. * - * @param Model $Model Current model. - * @return array + * @return array */ - public function getFilterValues($Model) + public function getFilterValues(): array { return $this->_filterValues; } diff --git a/Test/Case/AllTest.php b/Test/Case/AllTest.php index a3d0209..909a24d 100644 --- a/Test/Case/AllTest.php +++ b/Test/Case/AllTest.php @@ -14,7 +14,7 @@ class AllFilterTests extends CakeTestSuite { - public static function suite() + public static function suite(): CakeTestSuite { $suite = new CakeTestSuite('All FilterPlugin tests'); diff --git a/Test/Case/Controller/Component/FilterComponentTest.php b/Test/Case/Controller/Component/FilterComponentTest.php index b5c53c6..0513343 100644 --- a/Test/Case/Controller/Component/FilterComponentTest.php +++ b/Test/Case/Controller/Component/FilterComponentTest.php @@ -23,6 +23,9 @@ class FilterComponentTest extends CakeTestCase { + /** + * @var string[] + */ public $fixtures = array ( 'plugin.filter.document_category', @@ -31,7 +34,10 @@ class FilterComponentTest extends CakeTestCase 'plugin.filter.metadata', ); - public $Controller = null; + /** + * @var \DocumentTestsController + */ + public $Controller; public function startTest($method) { @@ -61,17 +67,21 @@ public function startTest($method) public function endTest($method) { $this->Controller->Session->destroy(); - $this->Controller = null; + unset($this->Controller); } /** * Test bailing out when no filters are present. */ - public function testNoFilters() + public function testNoFilters(): void { $this->Controller->Components->trigger('initialize', array($this->Controller)); $this->assertEmpty($this->Controller->Filter->settings); - $this->assertFalse($this->Controller->Document->Behaviors->enabled('Filtered')); + $isBehaviorEnabled = $this->Controller->Document->Behaviors->enabled('Filtered'); + $this->assertInternalType('bool', $isBehaviorEnabled); + if (is_bool($isBehaviorEnabled)) { + $this->assertFalse($isBehaviorEnabled); + } $this->Controller->Components->trigger('startup', array($this->Controller)); $this->assertFalse(in_array('Filter.Filter', $this->Controller->helpers)); @@ -81,7 +91,7 @@ public function testNoFilters() * Test bailing out when a filter model can't be found * or when the current action has no filters. */ - public function testNoModelPresentOrNoActionFilters() + public function testNoModelPresentOrNoActionFilters(): void { $testSettings = array( 'index' => array( @@ -106,7 +116,11 @@ public function testNoModelPresentOrNoActionFilters() $this->Controller->filters = $testSettings; $this->Controller->Components->trigger('initialize', array($this->Controller)); - $this->assertFalse($this->Controller->Document->Behaviors->enabled('Filtered')); + $isBehaviorEnabled = $this->Controller->Document->Behaviors->enabled('Filtered'); + $this->assertInternalType('bool', $isBehaviorEnabled); + if (is_bool($isBehaviorEnabled)) { + $this->assertFalse($isBehaviorEnabled); + } $testSettings = array( 'index' => array( @@ -118,13 +132,17 @@ public function testNoModelPresentOrNoActionFilters() $this->Controller->filters = $testSettings; $this->Controller->Components->trigger('initialize', array($this->Controller)); - $this->assertTrue($this->Controller->Document->Behaviors->enabled('Filtered')); + $isBehaviorEnabled = $this->Controller->Document->Behaviors->enabled('Filtered'); + $this->assertInternalType('bool', $isBehaviorEnabled); + if (is_bool($isBehaviorEnabled)) { + $this->assertTrue($isBehaviorEnabled); + } } /** * Test basic filter settings. */ - public function testBasicFilters() + public function testBasicFilters(): void { $testSettings = array( 'index' => array( @@ -146,7 +164,7 @@ public function testBasicFilters() /** * Test running a component with no filter data. */ - public function testEmptyStartup() + public function testEmptyStartup(): void { $testSettings = array( 'index' => array( @@ -165,7 +183,7 @@ public function testEmptyStartup() /** * Test loading filter data from session (both full and empty). */ - public function testSessionStartupData() + public function testSessionStartupData(): void { $testSettings = array( 'index' => array( @@ -210,7 +228,7 @@ public function testSessionStartupData() /** * Test loading filter data from a post request. */ - public function testPostStartupData() + public function testPostStartupData(): void { $_SERVER['REQUEST_METHOD'] = 'POST'; @@ -244,7 +262,7 @@ public function testPostStartupData() /** * Test exiting beforeRender when in an action with no settings. */ - public function testBeforeRenderAbort() + public function testBeforeRenderAbort(): void { $testSettings = array( 'veryMuchNotIndex' => array( @@ -266,7 +284,7 @@ public function testBeforeRenderAbort() * Test triggering an error when the plugin runs into a setting * for filtering a model which cannot be found. */ - public function testNoModelFound() + public function testNoModelFound(): void { $testSettings = array( 'index' => array( @@ -291,7 +309,7 @@ public function testNoModelFound() * Test the view variable generation for very basic filtering. * Also tests model name detection and custom label. */ - public function testBasicViewInfo() + public function testBasicViewInfo(): void { $testSettings = array( 'index' => array( @@ -333,7 +351,7 @@ public function testBasicViewInfo() * Test passing additional inputOptions to the form * helper, used to customize search form. */ - public function testAdditionalInputOptions() + public function testAdditionalInputOptions(): void { $testSettings = array( 'index' => array( @@ -386,7 +404,7 @@ public function testAdditionalInputOptions() * Test data fetching for select input when custom selector * and custom options are provided. */ - public function testCustomSelector() + public function testCustomSelector(): void { $testSettings = array( 'index' => array( @@ -427,7 +445,7 @@ public function testCustomSelector() /** * Test checkbox input filtering. */ - public function testCheckboxOptions() + public function testCheckboxOptions(): void { $testSettings = array( 'index' => array( @@ -463,7 +481,7 @@ public function testCheckboxOptions() /** * Test basic filter settings. */ - public function testSelectMultiple() + public function testSelectMultiple(): void { $testSettings = array( 'index' => array( @@ -488,7 +506,7 @@ public function testSelectMultiple() /** * Test select input for the model filtered. */ - public function testSelectInputFromSameModel() + public function testSelectInputFromSameModel(): void { $testSettings = array( 'index' => array( @@ -530,7 +548,7 @@ public function testSelectInputFromSameModel() * Test disabling persistence for single action * and for the entire controller. */ - public function testPersistence() + public function testPersistence(): void { $testSettings = array( 'index' => array( @@ -564,7 +582,7 @@ public function testPersistence() * Test whether filtering by belongsTo model text field * works correctly. */ - public function testBelongsToFilteringByText() + public function testBelongsToFilteringByText(): void { $testSettings = array( 'index' => array( diff --git a/Test/Case/MockObjects/Document.php b/Test/Case/MockObjects/Document.php index c8856ed..3627bf2 100644 --- a/Test/Case/MockObjects/Document.php +++ b/Test/Case/MockObjects/Document.php @@ -1,9 +1,28 @@ > + */ public $hasOne = array('Metadata'); } diff --git a/Test/Case/MockObjects/Document2.php b/Test/Case/MockObjects/Document2.php index f2d0c92..9c90952 100644 --- a/Test/Case/MockObjects/Document2.php +++ b/Test/Case/MockObjects/Document2.php @@ -2,14 +2,37 @@ class Document2 extends CakeTestModel { + /** + * @var string + */ public $name = 'Document'; + + /** + * @var string + */ public $alias = 'Document'; + + /** + * @var string[] + */ public $belongsTo = array('DocumentCategory'); + + /** + * @var string[] + */ public $hasMany = array('Item'); + /** + * @var bool + */ public $returnValue = false; - public function beforeDataFilter($query, $options) + /** + * @param array $query + * @param array $options + * @return bool + */ + public function beforeDataFilter(array $query, array $options): bool { return $this->returnValue; } diff --git a/Test/Case/MockObjects/Document3.php b/Test/Case/MockObjects/Document3.php index 220dd63..9921e9d 100644 --- a/Test/Case/MockObjects/Document3.php +++ b/Test/Case/MockObjects/Document3.php @@ -2,14 +2,37 @@ class Document3 extends CakeTestModel { + /** + * @var string + */ public $name = 'Document'; + + /** + * @var string + */ public $alias = 'Document'; + + /** + * @var string[] + */ public $belongsTo = array('DocumentCategory'); + + /** + * @var string[] + */ public $hasMany = array('Item'); + /** + * @var null|string + */ public $itemToUnset = null; - public function afterDataFilter($query, $options) + /** + * @param array> $query + * @param array $options + * @return array + */ + public function afterDataFilter(array $query, array $options): array { if (!is_string($this->itemToUnset)) { return $query; diff --git a/Test/Case/MockObjects/DocumentCategory.php b/Test/Case/MockObjects/DocumentCategory.php index 14d5e2c..403c03c 100644 --- a/Test/Case/MockObjects/DocumentCategory.php +++ b/Test/Case/MockObjects/DocumentCategory.php @@ -1,11 +1,25 @@ > $options + * @return array|int|null + */ + public function customSelector(array $options = array()) { $options['conditions']['DocumentCategory.title LIKE'] = '%T%'; $options['nofilter'] = true; diff --git a/Test/Case/MockObjects/DocumentTestsController.php b/Test/Case/MockObjects/DocumentTestsController.php index 620e8f6..ee78ce1 100644 --- a/Test/Case/MockObjects/DocumentTestsController.php +++ b/Test/Case/MockObjects/DocumentTestsController.php @@ -2,16 +2,30 @@ App::uses('Controller', 'Controller'); +/** + * @property array $filters + * @property FilterComponent $Filter + * @property Document $Document + */ class DocumentTestsController extends Controller { + /** + * @var string + */ public $name = 'DocumentTests'; - public function index() + public function index(): void { } // must override this or the tests never complete.. // @TODO: mock partial? + /** + * @param array|string $url + * @param array $status + * @param bool $exit + * @return CakeResponse|null + */ public function redirect($url, $status = null, $exit = true) { } diff --git a/Test/Case/MockObjects/Item.php b/Test/Case/MockObjects/Item.php index ffb28af..1f50608 100644 --- a/Test/Case/MockObjects/Item.php +++ b/Test/Case/MockObjects/Item.php @@ -2,6 +2,13 @@ class Item extends CakeTestModel { + /** + * @var string + */ public $name = 'Item'; + + /** + * @var string[] + */ public $belongsTo = array('Document'); } diff --git a/Test/Case/MockObjects/Metadata.php b/Test/Case/MockObjects/Metadata.php index c9bf609..d7262ce 100644 --- a/Test/Case/MockObjects/Metadata.php +++ b/Test/Case/MockObjects/Metadata.php @@ -2,6 +2,13 @@ class Metadata extends CakeTestModel { + /** + * @var string + */ public $name = 'Metadata'; + + /** + * @var string[] + */ public $hasOne = array('Document'); } diff --git a/Test/Case/Model/Behaviors/FilteredBehaviorTest.php b/Test/Case/Model/Behaviors/FilteredBehaviorTest.php index 6409821..5a0d5d3 100644 --- a/Test/Case/Model/Behaviors/FilteredBehaviorTest.php +++ b/Test/Case/Model/Behaviors/FilteredBehaviorTest.php @@ -22,6 +22,9 @@ class FilteredBehaviorTest extends CakeTestCase { + /** + * @var string[] + */ public $fixtures = array ( 'plugin.filter.document_category', @@ -30,24 +33,38 @@ class FilteredBehaviorTest extends CakeTestCase 'plugin.filter.metadata', ); - public $Document = null; + /** + * @var Document + */ + public $Document; + /** + * @param $model + * @return void + * @throws Exception + */ public function startTest($model) { - $this->Document = ClassRegistry::init('Document'); + $Document = ClassRegistry::init('Document'); + + if ($Document instanceof Document) { + $this->Document = $Document; + } else { + throw new Exception('Can not create Document model'); + } } public function endTest($model) { - $this->Document = null; + unset($this->Document); } /** * Detach and re-attach the behavior to reset the options. * - * @param array $options Behavior options. + * @param array $options Behavior options. */ - protected function _reattachBehavior($options = array()) + protected function _reattachBehavior(array $options = array()): void { $this->Document->Behaviors->detach('Filtered'); $this->Document->Behaviors->attach('Filter.Filtered', $options); @@ -56,16 +73,20 @@ protected function _reattachBehavior($options = array()) /** * Test attaching without options. */ - public function testBlankAttaching() + public function testBlankAttaching(): void { $this->Document->Behaviors->attach('Filter.Filtered'); - $this->assertTrue($this->Document->Behaviors->enabled('Filtered')); + $isBehaviorAttached = $this->Document->Behaviors->enabled('Filtered'); + $this->assertInternalType('bool', $isBehaviorAttached); + if (is_bool($isBehaviorAttached)) { + $this->assertTrue($isBehaviorAttached); + } } /** * Test attaching with options. */ - public function testInitSettings() + public function testInitSettings(): void { $testOptions = array( 'Document.title' => array('type' => 'text', 'condition' => 'like'), @@ -85,7 +106,7 @@ public function testInitSettings() /** * Test init settings when only a single field is given, with no extra options. */ - public function testInitSettingsSingle() + public function testInitSettingsSingle(): void { $testOptions = array('Document.title'); $this->_reattachBehavior($testOptions); @@ -99,7 +120,7 @@ public function testInitSettingsSingle() /** * Test setting the filter values for future queries. */ - public function testSetFilterValues() + public function testSetFilterValues(): void { $testOptions = array( 'Document.title' => array('type' => 'text', 'condition' => 'like', 'required' => true), @@ -122,7 +143,7 @@ public function testSetFilterValues() /** * Test detecting an error in options - when a field is 'required' but no value is given for it. */ - public function testLoadingRequiredFieldValueMissing() + public function testLoadingRequiredFieldValueMissing(): void { $testOptions = array( 'Document.title' => array('type' => 'text', 'condition' => 'like', 'required' => true), @@ -144,7 +165,7 @@ public function testLoadingRequiredFieldValueMissing() /** * Test filtering with conditions from current model and belongsTo model. */ - public function testFilteringBelongsTo() + public function testFilteringBelongsTo(): void { $testOptions = array( 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), @@ -167,7 +188,7 @@ public function testFilteringBelongsTo() $this->assertEquals($expected, $result); } - public function testFilteringBelongsToTextField() + public function testFilteringBelongsToTextField(): void { $testOptions = array( 'DocumentCategory.title' => array('type' => 'text') @@ -191,7 +212,7 @@ public function testFilteringBelongsToTextField() * Test filtering with conditions from current model and belongsTo model, * same as testFilteringBelongsTo() except for a change in filterField format. */ - public function testFilteringBelongsToFilterFieldTest() + public function testFilteringBelongsToFilterFieldTest(): void { $testOptions = array( 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), @@ -217,7 +238,7 @@ public function testFilteringBelongsToFilterFieldTest() /** * Test various conditions for the type 'text' in filtering (less than, equal, like, etc..) */ - public function testFilteringBelongsToDifferentConditions() + public function testFilteringBelongsToDifferentConditions(): void { $testOptions = array( 'title' => array('type' => 'text', 'condition' => '='), @@ -264,7 +285,7 @@ public function testFilteringBelongsToDifferentConditions() * Test filtering with conditions on current model, the belongsTo model * and hasMany model (behavior adds an INNER JOIN in query). */ - public function testFilteringBelongsToAndHasMany() + public function testFilteringBelongsToAndHasMany(): void { $testOptions = array( 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), @@ -329,7 +350,7 @@ public function testFilteringBelongsToAndHasMany() * Test filtering with join which has some custom * condition in the relation (both string and array). */ - public function testCustomJoinConditions() + public function testCustomJoinConditions(): void { $testOptions = array( 'Metadata.weight' => array('type' => 'text', 'condition' => '>'), @@ -367,7 +388,7 @@ public function testCustomJoinConditions() /** * Test for any possible conflicts with Containable behavior. */ - public function testFilteringBelongsToAndHasManyWithContainable() + public function testFilteringBelongsToAndHasManyWithContainable(): void { $testOptions = array( 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), @@ -427,7 +448,7 @@ public function testFilteringBelongsToAndHasManyWithContainable() /** * Test filtering by text input with hasOne relation. */ - public function testHasOneAndHasManyWithTextSearch() + public function testHasOneAndHasManyWithTextSearch(): void { $testOptions = array( 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), @@ -458,7 +479,7 @@ public function testHasOneAndHasManyWithTextSearch() /** * Test filtering with Containable and hasOne Model.field. */ - public function testHasOneWithContainable() + public function testHasOneWithContainable(): void { $testOptions = array( 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), @@ -506,7 +527,7 @@ public function testHasOneWithContainable() * Test filtering when a join is already present in the query, * this should prevent duplicate joins and query errors. */ - public function testJoinAlreadyPresent() + public function testJoinAlreadyPresent(): void { $testOptions = array( 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), @@ -551,7 +572,7 @@ public function testJoinAlreadyPresent() /** * Test the 'nofilter' query param. */ - public function testNofilterFindParam() + public function testNofilterFindParam(): void { $testOptions = array( 'Document.title' => array('type' => 'text', 'condition' => 'like'), @@ -580,7 +601,7 @@ public function testNofilterFindParam() /** * Test bailing out if no settings exist for the current model. */ - public function testExitWhenNoSettings() + public function testExitWhenNoSettings(): void { $this->Document->DocumentCategory->Behaviors->attach('Filter.Filtered'); @@ -608,7 +629,7 @@ public function testExitWhenNoSettings() /** * Test beforeDataFilter() callback, used to cancel filtering if necessary. */ - public function testBeforeDataFilterCallbackCancel() + public function testBeforeDataFilterCallbackCancel(): void { $this->Document = ClassRegistry::init('Document2'); @@ -641,10 +662,16 @@ public function testBeforeDataFilterCallbackCancel() /** * Test afterDataFilter() callback, used to modify the conditions after * filter conditions have been applied. + * @throws Exception */ - public function testAfterDataFilterCallbackQueryChange() + public function testAfterDataFilterCallbackQueryChange(): void { - $this->Document = ClassRegistry::init('Document3'); + $document = ClassRegistry::init('Document3'); + if ($document instanceof Document) { + $this->Document = $document; + } else { + throw new Exception('Can not create Document model'); + } $this->Document->itemToUnset = 'FilterDocumentCategory.id'; $testOptions = array( diff --git a/Test/Fixture/DocumentCategoryFixture.php b/Test/Fixture/DocumentCategoryFixture.php index 32fbe31..83d46e4 100644 --- a/Test/Fixture/DocumentCategoryFixture.php +++ b/Test/Fixture/DocumentCategoryFixture.php @@ -15,12 +15,18 @@ class DocumentCategoryFixture extends CakeTestFixture { public $name = 'DocumentCategory'; + /** + * @var array> + */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'title' => array('type' => 'string', 'length' => 100, 'null' => false), 'description' => array('type' => 'string', 'length' => 255) ); + /** + * @var array> + */ public $records = array( array('id' => 1, 'title' => 'Testing Doc', 'description' => 'It\'s a bleeding test doc!'), array('id' => 2, 'title' => 'Imaginary Spec', 'description' => 'This doc does not exist'), diff --git a/Test/Fixture/DocumentFixture.php b/Test/Fixture/DocumentFixture.php index 8d9a685..90b4adb 100644 --- a/Test/Fixture/DocumentFixture.php +++ b/Test/Fixture/DocumentFixture.php @@ -15,6 +15,9 @@ class DocumentFixture extends CakeTestFixture { public $name = 'Document'; + /** + * @var array> + */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'title' => array('type' => 'string', 'length' => '255', 'null' => false), @@ -25,6 +28,9 @@ class DocumentFixture extends CakeTestFixture 'updated' => array('type' => 'datetime', 'null' => true) ); + /** + * @var array> + */ public $records = array( array('id' => 1, 'title' => 'Testing Doc', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-06-28 10:39:23', 'updated' => '2010-06-29 11:22:48'), array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-03-28 12:19:13', 'updated' => '2010-04-29 11:23:44'), diff --git a/Test/Fixture/ItemFixture.php b/Test/Fixture/ItemFixture.php index ad510f5..9a95fb0 100644 --- a/Test/Fixture/ItemFixture.php +++ b/Test/Fixture/ItemFixture.php @@ -15,12 +15,18 @@ class ItemFixture extends CakeTestFixture { public $name = 'Item'; + /** + * @var array> + */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'document_id' => array('type' => 'integer', 'null' => false), 'code' => array('type' => 'string', 'length' => '20', 'null' => false) ); + /** + * @var array> + */ public $records = array( array('id' => 1, 'document_id' => 1, 'code' => 'The item #01'), array('id' => 2, 'document_id' => 1, 'code' => 'The item #02'), diff --git a/Test/Fixture/MetadataFixture.php b/Test/Fixture/MetadataFixture.php index c942775..e538ccc 100644 --- a/Test/Fixture/MetadataFixture.php +++ b/Test/Fixture/MetadataFixture.php @@ -13,8 +13,14 @@ class MetadataFixture extends CakeTestFixture { + /** + * @var string + */ public $name = 'Metadata'; + /** + * @var array> + */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'document_id' => array('type' => 'integer', 'null' => false), @@ -23,6 +29,9 @@ class MetadataFixture extends CakeTestFixture 'permissions' => array('type' => 'string', 'length' => 10, 'null' => false), ); + /** + * @var array> + */ public $records = array( array('id' => 1, 'document_id' => 1, 'weight' => 5, 'size' => 256, 'permissions' => 'rw-r--r--'), array('id' => 2, 'document_id' => 2, 'weight' => 0, 'size' => 45, 'permissions' => 'rw-------'), diff --git a/View/Helper/FilterHelper.php b/View/Helper/FilterHelper.php index b3e276c..b68edd7 100644 --- a/View/Helper/FilterHelper.php +++ b/View/Helper/FilterHelper.php @@ -14,14 +14,27 @@ class FilterHelper extends AppHelper { + /** + * @var View + */ protected $_view = null; - public function __construct(View $view, $settings = array()) + /** + * @param View $view + * @param array $settings + */ + public function __construct(View $view, array $settings = array()) { + parent::__construct($view, $settings); $this->_view = $view; } - public function filterForm($modelName, $options) + /** + * @param string $modelName + * @param array $options + * @return string + */ + public function filterForm(string $modelName, array $options): string { $view =& $this->_view; @@ -50,7 +63,12 @@ public function filterForm($modelName, $options) return $output; } - public function beginForm($modelName, $options) + /** + * @param string $modelName + * @param array $options + * @return string + */ + public function beginForm(string $modelName, array $options): string { $view =& $this->_view; $output = $view->element( @@ -66,7 +84,11 @@ public function beginForm($modelName, $options) return $output; } - public function inputFields($fields = array()) + /** + * @param array $fields + * @return string + */ + public function inputFields(array $fields = array()): string { $view =& $this->_view; $output = $view->element( @@ -81,7 +103,7 @@ public function inputFields($fields = array()) return $output; } - public function endForm() + public function endForm(): string { $view = $this->_view; $output = $view->element( diff --git a/phpstan.neon b/phpstan.neon index 04e3e63..b870430 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -2,3 +2,8 @@ parameters: level: 9 scanDirectories: - vendor + stubFiles: + - phpstan/stubs/CakePHP.stub + ignoreErrors: + # False positive as __() function can accept more than 2 parameters. + - '/Function __ invoked with [0-9_]+ parameters, 1-2 required./' diff --git a/phpstan/stubs/CakePHP.stub b/phpstan/stubs/CakePHP.stub new file mode 100644 index 0000000..35cff21 --- /dev/null +++ b/phpstan/stubs/CakePHP.stub @@ -0,0 +1,16 @@ + Date: Wed, 3 Apr 2024 09:04:50 +0200 Subject: [PATCH 06/38] fix wrong type for $linkModelName in FilteredBehavior #3 --- Model/Behavior/FilteredBehavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Model/Behavior/FilteredBehavior.php b/Model/Behavior/FilteredBehavior.php index aa166bc..8b522c4 100644 --- a/Model/Behavior/FilteredBehavior.php +++ b/Model/Behavior/FilteredBehavior.php @@ -136,7 +136,7 @@ protected function addFieldToFilter(Model &$Model, array &$query, array $setting // model and field we're using to filter the data $filterFieldName = $configurationFieldName; $filterModelName = $configurationModelName; - $linkModelName = null; + $linkModelName = ''; $relationType = null; if ($configurationModelName != $Model->alias) { From a276b32924b79b5b06ed851c1764eba0d5b970aa Mon Sep 17 00:00:00 2001 From: Josef Stich Date: Wed, 3 Apr 2024 09:05:18 +0200 Subject: [PATCH 07/38] fix testAfterDataFilterCallbackQueryChange unit test #3 --- Test/Case/Model/Behaviors/FilteredBehaviorTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Test/Case/Model/Behaviors/FilteredBehaviorTest.php b/Test/Case/Model/Behaviors/FilteredBehaviorTest.php index 5a0d5d3..e65b32a 100644 --- a/Test/Case/Model/Behaviors/FilteredBehaviorTest.php +++ b/Test/Case/Model/Behaviors/FilteredBehaviorTest.php @@ -34,7 +34,7 @@ class FilteredBehaviorTest extends CakeTestCase ); /** - * @var Document + * @var Document|Document2|Document3 */ public $Document; @@ -667,10 +667,10 @@ public function testBeforeDataFilterCallbackCancel(): void public function testAfterDataFilterCallbackQueryChange(): void { $document = ClassRegistry::init('Document3'); - if ($document instanceof Document) { + if ($document instanceof Document3) { $this->Document = $document; } else { - throw new Exception('Can not create Document model'); + throw new Exception('Can not create Document3 model'); } $this->Document->itemToUnset = 'FilterDocumentCategory.id'; From 0195e1e73ea07755a4d09950a7b9d38445ece18e Mon Sep 17 00:00:00 2001 From: Josef Stich Date: Wed, 3 Apr 2024 09:07:56 +0200 Subject: [PATCH 08/38] Document2 and Document3 docblock for mixin and property #3 --- Test/Case/MockObjects/Document2.php | 4 ++++ Test/Case/MockObjects/Document3.php | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/Test/Case/MockObjects/Document2.php b/Test/Case/MockObjects/Document2.php index 9c90952..fe5f99e 100644 --- a/Test/Case/MockObjects/Document2.php +++ b/Test/Case/MockObjects/Document2.php @@ -1,5 +1,9 @@ Date: Wed, 3 Apr 2024 09:10:46 +0200 Subject: [PATCH 09/38] fix "Property FilteredBehaviorTest::$Document (Document|Document2|Document3) does not accept bool|object" #2 --- Test/Case/Model/Behaviors/FilteredBehaviorTest.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Test/Case/Model/Behaviors/FilteredBehaviorTest.php b/Test/Case/Model/Behaviors/FilteredBehaviorTest.php index e65b32a..0d93f58 100644 --- a/Test/Case/Model/Behaviors/FilteredBehaviorTest.php +++ b/Test/Case/Model/Behaviors/FilteredBehaviorTest.php @@ -628,10 +628,17 @@ public function testExitWhenNoSettings(): void /** * Test beforeDataFilter() callback, used to cancel filtering if necessary. + * + * @throws Exception */ public function testBeforeDataFilterCallbackCancel(): void { - $this->Document = ClassRegistry::init('Document2'); + $document2 = ClassRegistry::init('Document2'); + if ($document2 instanceof Document2) { + $this->Document = $document2; + } else { + throw new Exception('Can not create Document2 model'); + } $testOptions = array( 'Document.title' => array('type' => 'text', 'condition' => 'like'), From 6aab09e5f9943d29bf8f4a052aea56a9cc06a1dd Mon Sep 17 00:00:00 2001 From: Josef Stich Date: Wed, 3 Apr 2024 09:19:56 +0200 Subject: [PATCH 10/38] ignore phpstan error "Access to an undefined property BehaviorCollection::$Filtered." #3 --- phpstan.neon | 1 + 1 file changed, 1 insertion(+) diff --git a/phpstan.neon b/phpstan.neon index b870430..a776730 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -7,3 +7,4 @@ parameters: ignoreErrors: # False positive as __() function can accept more than 2 parameters. - '/Function __ invoked with [0-9_]+ parameters, 1-2 required./' + - '/Access to an undefined property BehaviorCollection::\$Filtered./' From 4bb36ce8674c1c6e86c76c7bf3609737f1b0a5d3 Mon Sep 17 00:00:00 2001 From: Josef Stich Date: Wed, 3 Apr 2024 09:34:36 +0200 Subject: [PATCH 11/38] fix "Method DocumentTestsController::redirect() should return CakeResponse|null but return statement is missing" #3 --- Test/Case/MockObjects/DocumentTestsController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Test/Case/MockObjects/DocumentTestsController.php b/Test/Case/MockObjects/DocumentTestsController.php index ee78ce1..95f6b66 100644 --- a/Test/Case/MockObjects/DocumentTestsController.php +++ b/Test/Case/MockObjects/DocumentTestsController.php @@ -28,5 +28,6 @@ public function index(): void */ public function redirect($url, $status = null, $exit = true) { + return $this->response; } } From 6369abbce026d86f9aad3dcb5a9309a1b6d7f153 Mon Sep 17 00:00:00 2001 From: Josef Stich Date: Wed, 3 Apr 2024 09:44:29 +0200 Subject: [PATCH 12/38] fix "Property Document::$hasOne (array>) does not accept default value of type array" #3 --- Test/Case/MockObjects/Document.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Test/Case/MockObjects/Document.php b/Test/Case/MockObjects/Document.php index 3627bf2..aa44dd2 100644 --- a/Test/Case/MockObjects/Document.php +++ b/Test/Case/MockObjects/Document.php @@ -22,7 +22,7 @@ class Document extends CakeTestModel public $hasMany = array('Item'); /** - * @var array> + * @var array */ public $hasOne = array('Metadata'); } From 4bc83a4f775821645065260a5c246a893262d420 Mon Sep 17 00:00:00 2001 From: Josef Stich Date: Wed, 3 Apr 2024 09:54:47 +0200 Subject: [PATCH 13/38] add github automated testing workflow #3 --- .github/workflows/main.yml | 51 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..1534a42 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,51 @@ +# This is a basic workflow to help you get started with Actions + +name: CI + +# Controls when the action will run. +on: + # Triggers the workflow on push or pull request events but only for the main branch + push: + pull_request: + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # Composer config validation + composer: + name: Composer config validation + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Validate composer.json + run: composer validate --strict + + # PHP syntax validation + php: + name: PHP syntax validation + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Check PHP syntax of config/phinx.php + run: php -l src/ tests/ + + # phpstan for several php versions + phpstan: + runs-on: ubuntu-latest + strategy: + matrix: + php_version: [7.4, 8.0, 8.1, 8.2, 8.3] + steps: + - uses: actions/checkout@v3 + - uses: php-actions/composer@v6 + with: + php_version: ${{ matrix.php_version }} + + - name: PHPStan Static Analysis + uses: php-actions/phpstan@v3 + with: + php_version: ${{ matrix.php_version }} + configuration: phpstan.neon + path: Controller/ Model/ Test/ View/ From 63228689d08fdbd83a701b07a2b04df431481fa2 Mon Sep 17 00:00:00 2001 From: Josef Stich Date: Wed, 3 Apr 2024 09:56:12 +0200 Subject: [PATCH 14/38] fix automated php syntax check #3 --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1534a42..ce94bc7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -28,8 +28,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - name: Check PHP syntax of config/phinx.php - run: php -l src/ tests/ + - name: Check PHP syntax + run: php -l Controller/ Model/ Test/ View/ # phpstan for several php versions phpstan: From 0d126743ef0355fbcc1adb6182db999fb0587e00 Mon Sep 17 00:00:00 2001 From: Josef Stich Date: Mon, 8 Apr 2024 15:24:17 +0200 Subject: [PATCH 15/38] require "kba-team/cakephp" and "phpunit/phpunit" #3 --- composer.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 0550096..eb5ed5b 100644 --- a/composer.json +++ b/composer.json @@ -6,9 +6,12 @@ "minimum-stability": "stable", "require": { "php": ">=7.4, <8.4", - "cakephp/cakephp": "^2.4", + "kba-team/cakephp": "^2.11", "composer/installers": "^1.9" }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, "extra": { "installer-name": "Filter" }, From baf61af70c6c0a68789a563d2ce7a3dc415917b5 Mon Sep 17 00:00:00 2001 From: Josef Stich Date: Mon, 8 Apr 2024 15:25:57 +0200 Subject: [PATCH 16/38] remove php7.4 compatibility #3 --- .github/workflows/main.yml | 2 +- composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ce94bc7..27263e2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -36,7 +36,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php_version: [7.4, 8.0, 8.1, 8.2, 8.3] + php_version: [8.0, 8.1, 8.2, 8.3] steps: - uses: actions/checkout@v3 - uses: php-actions/composer@v6 diff --git a/composer.json b/composer.json index eb5ed5b..0d311fc 100644 --- a/composer.json +++ b/composer.json @@ -5,7 +5,7 @@ "license": "GPL-3.0-or-later", "minimum-stability": "stable", "require": { - "php": ">=7.4, <8.4", + "php": ">=8.0, <8.4", "kba-team/cakephp": "^2.11", "composer/installers": "^1.9" }, From ac1bcccbc62cbe3f33f9deea9ec714da6203c633 Mon Sep 17 00:00:00 2001 From: Josef Stich Date: Mon, 27 May 2024 15:51:44 +0200 Subject: [PATCH 17/38] set parent class to TestCase for FilterComponentTest #3 --- Test/Case/Controller/Component/FilterComponentTest.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Test/Case/Controller/Component/FilterComponentTest.php b/Test/Case/Controller/Component/FilterComponentTest.php index 0513343..981f2a7 100644 --- a/Test/Case/Controller/Component/FilterComponentTest.php +++ b/Test/Case/Controller/Component/FilterComponentTest.php @@ -11,6 +11,8 @@ * GPL */ +use PHPUnit\Framework\TestCase; + App::uses('Router', 'Routing'); App::uses('Component', 'Filter.Filter'); App::uses('Document', 'Filter.Test/Case/MockObjects'); @@ -21,7 +23,7 @@ App::uses('Item', 'Filter.Test/Case/MockObjects'); App::uses('Metadata', 'Filter.Test/Case/MockObjects'); -class FilterComponentTest extends CakeTestCase +class FilterComponentTest extends TestCase { /** * @var string[] @@ -39,8 +41,10 @@ class FilterComponentTest extends CakeTestCase */ public $Controller; - public function startTest($method) + public function setUp(): void { + parent::setUp(); + Router::connect('/', array('controller' => 'document_tests', 'action' => 'index')); $request = new CakeRequest('/'); $request->addParams(Router::parse('/')); From dbdfb5c30446a360129c5085e2836493c0cf8762 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 May 2024 16:34:33 +0200 Subject: [PATCH 18/38] ignore .uuid files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 97728e5..dce9690 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ /.project /vendor/ /composer.lock +.uuid From 4598bba1be9684dc18b8b7e23a75e058042ba078 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 May 2024 16:35:01 +0200 Subject: [PATCH 19/38] add script to run tests inside docker container --- Test/test.sh | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100755 Test/test.sh diff --git a/Test/test.sh b/Test/test.sh new file mode 100755 index 0000000..22a0add --- /dev/null +++ b/Test/test.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env sh + +phpdismod xdebug; + +service mysql start + +mysql -h localhost -u root -e "CREATE DATABASE database_name;CREATE DATABASE test_database_name;CREATE USER 'user'@'localhost' IDENTIFIED BY 'password';GRANT ALL PRIVILEGES ON * . * TO 'user'@'localhost';" + +composer config repositories.cakephp-filter-plugin '{"type": "path", "url": "/cakephp-filter-plugin", "options": {"symlink": true}}' +composer config repo.packagist false +composer require --prefer-source kba-team/cakephp-filter-plugin:@dev +vendor/bin/phpunit Plugin/Filter/Test/Case/Controller/Component/FilterComponentTest.php +E=$? +service mysql stop +exit $E From faab5668c0a4909b6a4212d2b18bb0012f530c3a Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 May 2024 16:36:46 +0200 Subject: [PATCH 20/38] replace array braces with brackets in FilteredBehavior --- Model/Behavior/FilteredBehavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Model/Behavior/FilteredBehavior.php b/Model/Behavior/FilteredBehavior.php index 8b522c4..5e6c0ef 100644 --- a/Model/Behavior/FilteredBehavior.php +++ b/Model/Behavior/FilteredBehavior.php @@ -400,7 +400,7 @@ private function __escape($string, string $connection = 'default') $db = ConnectionManager::getDataSource($connection); $string = $db->value($string, 'string'); $start = 1; - if ($string{0} === 'N') { + if ($string[0] === 'N') { $start = 2; } return substr(substr($string, $start), 0, -1); From da58397576bd61e6f8119d6f03e22ca68d0e701b Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 May 2024 16:40:16 +0200 Subject: [PATCH 21/38] rename readme and add section testing to readme --- README.markdown => README.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) rename README.markdown => README.md (90%) diff --git a/README.markdown b/README.md similarity index 90% rename from README.markdown rename to README.md index ea82238..c7f8b17 100644 --- a/README.markdown +++ b/README.md @@ -1,6 +1,6 @@ -# CakePHP Filter Plugin # +# CakePHP Filter Plugin -## About ## +## About Filter is a [CakePHP][] plugin which enables you to create filtering forms for your data in a very fast and simple way, without getting in the way of paging, sorting and other @@ -14,7 +14,7 @@ the correct path to unload the plugin is `app/plugins/filter/`. More importantly **if you're using CakePHP 1.3.x you should use the 1.3.x version of this plugin**, not the latest version from GitHub. -## Usage ## +## Usage First, obtain the plugin. If you're using Git, run this while in your app folder: @@ -33,6 +33,17 @@ this article: 'GET'` to the `filterForm()` or `beginForm()` options array. +## Testing + +```shell +docker run \ + --rm \ + -it \ + -v "$(pwd)":/cakephp-filter-plugin \ + -e DEBUG=0 \ + devkba/cake2-app-template:staging /cakephp-filter-plugin/Test/test.sh +``` + ## Contributing ## If you'd like to contribute, clone the source on GitHub, make your changes and send me a pull request. From f3f1cac8a881b065b0652b2787b9e359770596ca Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 May 2024 16:42:28 +0200 Subject: [PATCH 22/38] add docker pull command to readme testing section --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c7f8b17..1b75178 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ In order to generate GET forms add `'type' => 'GET'` to the `filterForm()` or `b ## Testing ```shell +docker pull devkba/cake2-app-template:staging; docker run \ --rm \ -it \ From c41144dd5bb420b60ed6e79a0ed340799a38b573 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 May 2024 16:51:50 +0200 Subject: [PATCH 23/38] update usage in readme and code blocks --- README.md | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 1b75178..d3e39b1 100644 --- a/README.md +++ b/README.md @@ -18,12 +18,9 @@ not the latest version from GitHub. First, obtain the plugin. If you're using Git, run this while in your app folder: - git submodule add git://github.com/lecterror/cakephp-filter-plugin.git Plugin/Filter - git submodule init - git submodule update - -Or visit and download the -plugin manually to your `app/Plugin/Filter/` folder. +```shell +composer require kba-team/cakephp-filter-plugin +``` To use the plugin, you need to tell it which model to filter and which fields to use. For a quick tutorial, visit @@ -45,27 +42,31 @@ docker run \ devkba/cake2-app-template:staging /cakephp-filter-plugin/Test/test.sh ``` -## Contributing ## +## Contributing If you'd like to contribute, clone the source on GitHub, make your changes and send me a pull request. -If you don't know how to fix the issue or you're too lazy to do it, create a ticket and we'll see +If you don't know how to fix the issue, or you're too lazy to do it, create a ticket, and we'll see what happens next. **Important**: If you're sending a patch, follow the coding style! If you don't, there is a great chance I won't accept it. For example: - // bad - public function drink() { - return false; - } +```php +// bad +public function drink() { + return false; +} +``` - // good - public function drink() - { - return true; - } +```php +// good +public function drink() +{ + return true; +} +``` -## Licence ## +## Licence Multi-licensed under: From cbf8f30f6eea5ea3cceb9c38f8f9b981aa037be0 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 May 2024 17:19:07 +0200 Subject: [PATCH 24/38] make test.sh configurable and add comments --- Test/test.sh | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/Test/test.sh b/Test/test.sh index 22a0add..87256fc 100755 --- a/Test/test.sh +++ b/Test/test.sh @@ -1,15 +1,23 @@ #!/usr/bin/env sh - -phpdismod xdebug; - +# start mysql database service service mysql start - +# create default database and user from cake2-app-template:/Config/.env.default mysql -h localhost -u root -e "CREATE DATABASE database_name;CREATE DATABASE test_database_name;CREATE USER 'user'@'localhost' IDENTIFIED BY 'password';GRANT ALL PRIVILEGES ON * . * TO 'user'@'localhost';" - +# use this repository symlinked into the app template composer config repositories.cakephp-filter-plugin '{"type": "path", "url": "/cakephp-filter-plugin", "options": {"symlink": true}}' +# prevent composer from looking up packages on packagist.org composer config repo.packagist false +# require the current dev version of the filter plugin composer require --prefer-source kba-team/cakephp-filter-plugin:@dev -vendor/bin/phpunit Plugin/Filter/Test/Case/Controller/Component/FilterComponentTest.php -E=$? +# Either use the test case from the parameter, or run all tests +if [ -n "${1}" ]; then + # call PHPUnit and remember return code + vendor/bin/phpunit "Plugin/Filter/${1}" + E=$? +else + (>&2 echo "ERROR: Missing test case!") +fi +# stop mysql service at the end of the tests service mysql stop +# exit with the return code of phpunit exit $E From a6bbc62951bcc460ddb18f64b5b0f48bfdcf34d6 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 May 2024 17:19:42 +0200 Subject: [PATCH 25/38] remove the AllTest test suite as TestSuits don't exist in PHPUnit 9 anymore --- Test/Case/AllTest.php | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 Test/Case/AllTest.php diff --git a/Test/Case/AllTest.php b/Test/Case/AllTest.php deleted file mode 100644 index 909a24d..0000000 --- a/Test/Case/AllTest.php +++ /dev/null @@ -1,25 +0,0 @@ - - * - * Multi-licensed under: - * MPL - * LGPL - * GPL - */ -App::uses('CakePlugin', 'Core'); - -class AllFilterTests extends CakeTestSuite -{ - public static function suite(): CakeTestSuite - { - $suite = new CakeTestSuite('All FilterPlugin tests'); - - $suite->addTestDirectoryRecursive(CakePlugin::path('Filter') . 'Test' . DS . 'Case'); - - return $suite; - } -} From 706f2200db89cb8a4746f5e3670450e3f22a6c63 Mon Sep 17 00:00:00 2001 From: Josef Stich Date: Mon, 27 May 2024 17:31:09 +0200 Subject: [PATCH 26/38] change case class for FilteredBehaviorTest to \PHPUnit\Framework\TestCase #3 --- Test/Case/Model/Behaviors/FilteredBehaviorTest.php | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/Test/Case/Model/Behaviors/FilteredBehaviorTest.php b/Test/Case/Model/Behaviors/FilteredBehaviorTest.php index 0d93f58..fe3cb77 100644 --- a/Test/Case/Model/Behaviors/FilteredBehaviorTest.php +++ b/Test/Case/Model/Behaviors/FilteredBehaviorTest.php @@ -20,7 +20,7 @@ App::uses('Item', 'Filter.Test/Case/MockObjects'); App::uses('Metadata', 'Filter.Test/Case/MockObjects'); -class FilteredBehaviorTest extends CakeTestCase +class FilteredBehaviorTest extends \PHPUnit\Framework\TestCase { /** * @var string[] @@ -39,12 +39,12 @@ class FilteredBehaviorTest extends CakeTestCase public $Document; /** - * @param $model * @return void * @throws Exception */ - public function startTest($model) + public function setUp(): void { + parent::setUp(); $Document = ClassRegistry::init('Document'); if ($Document instanceof Document) { @@ -54,11 +54,6 @@ public function startTest($model) } } - public function endTest($model) - { - unset($this->Document); - } - /** * Detach and re-attach the behavior to reset the options. * From b2d090472a1715b5797cf8ec4723769ce1cf11aa Mon Sep 17 00:00:00 2001 From: Josef Stich Date: Tue, 28 May 2024 10:07:30 +0200 Subject: [PATCH 27/38] WIP fix unit tests #3 --- Test/Case/Model/Behaviors/FilteredBehaviorTest.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Test/Case/Model/Behaviors/FilteredBehaviorTest.php b/Test/Case/Model/Behaviors/FilteredBehaviorTest.php index fe3cb77..48d80e8 100644 --- a/Test/Case/Model/Behaviors/FilteredBehaviorTest.php +++ b/Test/Case/Model/Behaviors/FilteredBehaviorTest.php @@ -34,7 +34,7 @@ class FilteredBehaviorTest extends \PHPUnit\Framework\TestCase ); /** - * @var Document|Document2|Document3 + * @var AppModel|Document|Document2|Document3 */ public $Document; @@ -47,9 +47,10 @@ public function setUp(): void parent::setUp(); $Document = ClassRegistry::init('Document'); - if ($Document instanceof Document) { + if ($Document instanceof AppModel) { $this->Document = $Document; } else { + var_dump(get_class($Document)); throw new Exception('Can not create Document model'); } } @@ -72,7 +73,7 @@ public function testBlankAttaching(): void { $this->Document->Behaviors->attach('Filter.Filtered'); $isBehaviorAttached = $this->Document->Behaviors->enabled('Filtered'); - $this->assertInternalType('bool', $isBehaviorAttached); + $this->assertIsBool($isBehaviorAttached); if (is_bool($isBehaviorAttached)) { $this->assertTrue($isBehaviorAttached); } @@ -153,7 +154,7 @@ public function testLoadingRequiredFieldValueMissing(): void ); $this->Document->setFilterValues($filterValues); - $this->setExpectedException('PHPUnit_Framework_Error_Notice'); + $this->expectException('PHPUnit_Framework_Error_Notice'); $this->Document->find('first'); } @@ -629,7 +630,7 @@ public function testExitWhenNoSettings(): void public function testBeforeDataFilterCallbackCancel(): void { $document2 = ClassRegistry::init('Document2'); - if ($document2 instanceof Document2) { + if ($document2 instanceof AppModel) { $this->Document = $document2; } else { throw new Exception('Can not create Document2 model'); @@ -669,7 +670,7 @@ public function testBeforeDataFilterCallbackCancel(): void public function testAfterDataFilterCallbackQueryChange(): void { $document = ClassRegistry::init('Document3'); - if ($document instanceof Document3) { + if ($document instanceof AppModel) { $this->Document = $document; } else { throw new Exception('Can not create Document3 model'); From fa9efa06efad8382886a54cdbee81a6c0fa35189 Mon Sep 17 00:00:00 2001 From: Josef Stich Date: Tue, 28 May 2024 15:16:18 +0200 Subject: [PATCH 28/38] raise minimum PHP version to 8.1 #3 --- .github/workflows/main.yml | 2 +- composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 27263e2..975b32a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -36,7 +36,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php_version: [8.0, 8.1, 8.2, 8.3] + php_version: [8.1, 8.2, 8.3] steps: - uses: actions/checkout@v3 - uses: php-actions/composer@v6 diff --git a/composer.json b/composer.json index 0d311fc..3d39552 100644 --- a/composer.json +++ b/composer.json @@ -5,7 +5,7 @@ "license": "GPL-3.0-or-later", "minimum-stability": "stable", "require": { - "php": ">=8.0, <8.4", + "php": ">=8.1, <8.4", "kba-team/cakephp": "^2.11", "composer/installers": "^1.9" }, From 8b2b648b1ae5d6e5f3d3bafd3d1f37c870732dac Mon Sep 17 00:00:00 2001 From: Josef Stich Date: Tue, 28 May 2024 15:21:24 +0200 Subject: [PATCH 29/38] use exceptions instead of trigger_error #3 --- Controller/Component/FilterComponent.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Controller/Component/FilterComponent.php b/Controller/Component/FilterComponent.php index 81413b7..81d9024 100644 --- a/Controller/Component/FilterComponent.php +++ b/Controller/Component/FilterComponent.php @@ -78,7 +78,7 @@ public function initialize(Controller $controller): void if (!isset($controller->{$model})) { $errMsg = __('Filter model not found: %s', $model); if (is_string($errMsg)) { - trigger_error($errMsg); + throw new InvalidArgumentException($errMsg); } continue; } @@ -130,7 +130,7 @@ public function startup(Controller $controller) if (!isset($controller->{$model})) { $errMsg = __('Filter model not found: %s', $model); if (is_string($errMsg)) { - trigger_error($errMsg); + throw new InvalidArgumentException($errMsg); } continue; } From b88412acecb204b19123500bd180fb917984d43e Mon Sep 17 00:00:00 2001 From: Josef Stich Date: Tue, 28 May 2024 15:21:51 +0200 Subject: [PATCH 30/38] WIP fix unit tests #3 --- .../Component/FilterComponentTest.php | 31 ++++++++++++------- Test/test.sh | 2 +- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/Test/Case/Controller/Component/FilterComponentTest.php b/Test/Case/Controller/Component/FilterComponentTest.php index 981f2a7..85620de 100644 --- a/Test/Case/Controller/Component/FilterComponentTest.php +++ b/Test/Case/Controller/Component/FilterComponentTest.php @@ -11,8 +11,6 @@ * GPL */ -use PHPUnit\Framework\TestCase; - App::uses('Router', 'Routing'); App::uses('Component', 'Filter.Filter'); App::uses('Document', 'Filter.Test/Case/MockObjects'); @@ -23,7 +21,7 @@ App::uses('Item', 'Filter.Test/Case/MockObjects'); App::uses('Metadata', 'Filter.Test/Case/MockObjects'); -class FilterComponentTest extends TestCase +class FilterComponentTest extends CakeTestCase { /** * @var string[] @@ -82,7 +80,7 @@ public function testNoFilters(): void $this->Controller->Components->trigger('initialize', array($this->Controller)); $this->assertEmpty($this->Controller->Filter->settings); $isBehaviorEnabled = $this->Controller->Document->Behaviors->enabled('Filtered'); - $this->assertInternalType('bool', $isBehaviorEnabled); + $this->assertIsBool($isBehaviorEnabled); if (is_bool($isBehaviorEnabled)) { $this->assertFalse($isBehaviorEnabled); } @@ -105,7 +103,9 @@ public function testNoModelPresentOrNoActionFilters(): void ) ); - $this->setExpectedException('PHPUnit_Framework_Error_Notice'); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Filter model not found: DocumentArse'); + $this->Controller->filters = $testSettings; $this->Controller->Components->trigger('initialize', array($this->Controller)); @@ -117,11 +117,10 @@ public function testNoModelPresentOrNoActionFilters(): void ) ); - $this->Controller->filters = $testSettings; $this->Controller->Components->trigger('initialize', array($this->Controller)); $isBehaviorEnabled = $this->Controller->Document->Behaviors->enabled('Filtered'); - $this->assertInternalType('bool', $isBehaviorEnabled); + $this->assertIsBool($isBehaviorEnabled); if (is_bool($isBehaviorEnabled)) { $this->assertFalse($isBehaviorEnabled); } @@ -137,7 +136,7 @@ public function testNoModelPresentOrNoActionFilters(): void $this->Controller->filters = $testSettings; $this->Controller->Components->trigger('initialize', array($this->Controller)); $isBehaviorEnabled = $this->Controller->Document->Behaviors->enabled('Filtered'); - $this->assertInternalType('bool', $isBehaviorEnabled); + $this->assertIsBool($isBehaviorEnabled); if (is_bool($isBehaviorEnabled)) { $this->assertTrue($isBehaviorEnabled); } @@ -205,11 +204,19 @@ public function testSessionStartupData(): void $filterValues = array(); $this->Controller->Session->write($sessionKey, $filterValues); - $this->setExpectedException('PHPUnit_Framework_Error_Notice'); - $this->Controller->Components->trigger('initialize', array($this->Controller)); + try { + $this->Controller->Components->trigger('initialize', array($this->Controller)); + $this->fail('InvalidArgumentException was not thrown'); + } catch (InvalidArgumentException $e1) { + $this->assertSame('Filter model not found: FakeNonexistant', $e1->getMessage()); + } - $this->setExpectedException('PHPUnit_Framework_Error_Notice'); - $this->Controller->Components->trigger('startup', array($this->Controller)); + try { + $this->Controller->Components->trigger('startup', array($this->Controller)); + $this->fail('InvalidArgumentException was not thrown'); + } catch (InvalidArgumentException $e2) { + $this->assertSame('xxxFilter model not found: FakeNonexistant', $e2->getMessage()); + } $actualFilterValues = $this->Controller->Document->getFilterValues(); $this->assertEquals( $filterValues, diff --git a/Test/test.sh b/Test/test.sh index 87256fc..00da225 100755 --- a/Test/test.sh +++ b/Test/test.sh @@ -12,7 +12,7 @@ composer require --prefer-source kba-team/cakephp-filter-plugin:@dev # Either use the test case from the parameter, or run all tests if [ -n "${1}" ]; then # call PHPUnit and remember return code - vendor/bin/phpunit "Plugin/Filter/${1}" + vendor/bin/phpunit ${*} E=$? else (>&2 echo "ERROR: Missing test case!") From 34c653fa21949a24daeb2cd9f639cff8baa90d7b Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 29 May 2024 09:59:07 +0200 Subject: [PATCH 31/38] replace test script with phpunit script and before and after scripts --- README.md | 15 +++++++++++++-- Test/after_script.sh | 3 +++ Test/{test.sh => before_script.sh} | 12 ------------ Test/phpunit.sh | 31 ++++++++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 14 deletions(-) create mode 100755 Test/after_script.sh rename Test/{test.sh => before_script.sh} (69%) create mode 100755 Test/phpunit.sh diff --git a/README.md b/README.md index d3e39b1..27074e2 100644 --- a/README.md +++ b/README.md @@ -32,14 +32,25 @@ In order to generate GET forms add `'type' => 'GET'` to the `filterForm()` or `b ## Testing +To run PHPUnit tests: + +```shell +Test/phpunit.sh +``` + +Execute shell for manual testing + ```shell -docker pull devkba/cake2-app-template:staging; +docker pull devkba/cake2-app-template:staging docker run \ --rm \ + --init \ -it \ -v "$(pwd)":/cakephp-filter-plugin \ -e DEBUG=0 \ - devkba/cake2-app-template:staging /cakephp-filter-plugin/Test/test.sh + -e BEFORE_SCRIPT="/cakephp-filter-plugin/Test/before_script.sh" \ + -e AFTER_SCRIPT="/cakephp-filter-plugin/Test/after_script.sh" \ + devkba/cake2-app-template:staging ``` ## Contributing diff --git a/Test/after_script.sh b/Test/after_script.sh new file mode 100755 index 0000000..3c60c71 --- /dev/null +++ b/Test/after_script.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env sh +# stop mysql service at the end of the tests +service mysql stop diff --git a/Test/test.sh b/Test/before_script.sh similarity index 69% rename from Test/test.sh rename to Test/before_script.sh index 00da225..a92cd29 100755 --- a/Test/test.sh +++ b/Test/before_script.sh @@ -9,15 +9,3 @@ composer config repositories.cakephp-filter-plugin '{"type": "path", "url": "/ca composer config repo.packagist false # require the current dev version of the filter plugin composer require --prefer-source kba-team/cakephp-filter-plugin:@dev -# Either use the test case from the parameter, or run all tests -if [ -n "${1}" ]; then - # call PHPUnit and remember return code - vendor/bin/phpunit ${*} - E=$? -else - (>&2 echo "ERROR: Missing test case!") -fi -# stop mysql service at the end of the tests -service mysql stop -# exit with the return code of phpunit -exit $E diff --git a/Test/phpunit.sh b/Test/phpunit.sh new file mode 100755 index 0000000..cc7cb3a --- /dev/null +++ b/Test/phpunit.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# the docker image to use for phpunit testing +DOCKER_IMAGE="devkba/cake2-app-template:staging" +# the path to prepend to the test case +TEST_PREFIX="Plugin/Filter/" +# compile PHPUnit parameters +PARAMS="" +for arg in "$@"; do + # add prefix in case an argument is a path to a test + if [[ "${arg}" == *Test.php ]]; then + arg="${TEST_PREFIX}${arg}" + fi + # in case this is the first argument, don't add $IFS + if [ "${PARAMS}" == "" ]; then + PARAMS="${arg}" + else + PARAMS="${PARAMS}${IFS}${arg}" + fi +done +# pull latest docker image +docker pull "${DOCKER_IMAGE}" || exit $? +# run actual PHPUnit inside docker container +docker run \ + --rm \ + --init \ + -it \ + -v "$(pwd)":/cakephp-filter-plugin \ + -e DEBUG=0 \ + -e BEFORE_SCRIPT="/cakephp-filter-plugin/Test/before_script.sh" \ + -e AFTER_SCRIPT="/cakephp-filter-plugin/Test/after_script.sh" \ + "${DOCKER_IMAGE}" vendor/bin/phpunit ${PARAMS} From 1e27ad65778963786f2b0a3701a83c310095003a Mon Sep 17 00:00:00 2001 From: Josef Stich Date: Wed, 29 May 2024 14:50:25 +0200 Subject: [PATCH 32/38] fixed testSessionStartupData in FilterComponentTest.php #3 --- .../Component/FilterComponentTest.php | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/Test/Case/Controller/Component/FilterComponentTest.php b/Test/Case/Controller/Component/FilterComponentTest.php index 85620de..a0b8dbd 100644 --- a/Test/Case/Controller/Component/FilterComponentTest.php +++ b/Test/Case/Controller/Component/FilterComponentTest.php @@ -66,8 +66,10 @@ public function setUp(): void $this->Controller->Components->trigger('initialize', array($this->Controller)); } - public function endTest($method) + public function tearDown(): void { + parent::tearDown(); + $this->Controller->Session->destroy(); unset($this->Controller); } @@ -211,11 +213,14 @@ public function testSessionStartupData(): void $this->assertSame('Filter model not found: FakeNonexistant', $e1->getMessage()); } + $filterValues = array('Document' => array('title' => 'in')); + $this->Controller->Session->write($sessionKey, $filterValues); + try { $this->Controller->Components->trigger('startup', array($this->Controller)); $this->fail('InvalidArgumentException was not thrown'); } catch (InvalidArgumentException $e2) { - $this->assertSame('xxxFilter model not found: FakeNonexistant', $e2->getMessage()); + $this->assertSame('Filter model not found: FakeNonexistant', $e2->getMessage()); } $actualFilterValues = $this->Controller->Document->getFilterValues(); $this->assertEquals( @@ -223,16 +228,6 @@ public function testSessionStartupData(): void $actualFilterValues[$this->Controller->Document->alias] ); - $filterValues = array('Document' => array('title' => 'in')); - $this->Controller->Session->write($sessionKey, $filterValues); - - $this->Controller->Components->trigger('startup', array($this->Controller)); - $actualFilterValues = $this->Controller->Document->getFilterValues(); - $this->assertEquals( - $filterValues, - $actualFilterValues[$this->Controller->Document->alias] - ); - $this->Controller->Session->delete($sessionKey); } From ce74bd0571f1b6ce9ddb09307165bc676296133e Mon Sep 17 00:00:00 2001 From: Josef Stich Date: Wed, 29 May 2024 15:04:35 +0200 Subject: [PATCH 33/38] fixed testNoModelFound in FilterComponentTest.php #3 --- Controller/Component/FilterComponent.php | 2 +- .../Component/FilterComponentTest.php | 28 +++++++++++++++---- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/Controller/Component/FilterComponent.php b/Controller/Component/FilterComponent.php index 81d9024..9db026a 100644 --- a/Controller/Component/FilterComponent.php +++ b/Controller/Component/FilterComponent.php @@ -153,7 +153,7 @@ public function beforeRender(Controller $controller) if (!isset($controller->$model)) { $errMsg = __('Filter model not found: %s', $model); if (is_string($errMsg)) { - trigger_error($errMsg); + throw new InvalidArgumentException($errMsg); } continue; } diff --git a/Test/Case/Controller/Component/FilterComponentTest.php b/Test/Case/Controller/Component/FilterComponentTest.php index a0b8dbd..510c79a 100644 --- a/Test/Case/Controller/Component/FilterComponentTest.php +++ b/Test/Case/Controller/Component/FilterComponentTest.php @@ -301,14 +301,30 @@ public function testNoModelFound(): void ); $this->Controller->filters = $testSettings; - $this->setExpectedException('PHPUnit_Framework_Error_Notice'); - $this->Controller->Components->trigger('initialize', array($this->Controller)); + try { + $this->Controller->Components->trigger('initialize', array($this->Controller)); + $this->fail('InvalidArgumentException was not thrown'); + } catch (InvalidArgumentException $e) { + $this->assertSame('Filter model not found: ThisModelDoesNotExist', $e->getMessage()); + } - //$this->expectError(); - $this->Controller->Components->trigger('startup', array($this->Controller)); + $sessionKey = sprintf('FilterPlugin.Filters.%s.%s', $this->Controller->name, $this->Controller->action); + $filterValues = array('ThisModelDoesNotExist' => array('title' => 'in')); + $this->Controller->Session->write($sessionKey, $filterValues); + try { + $this->Controller->Components->trigger('startup', array($this->Controller)); + $this->fail('InvalidArgumentException was not thrown'); + } catch (InvalidArgumentException $e) { + $this->assertSame('Filter model not found: ThisModelDoesNotExist', $e->getMessage()); + } + $this->Controller->Session->delete($sessionKey); - $this->setExpectedException('PHPUnit_Framework_Error_Notice'); - $this->Controller->Components->trigger('beforeRender', array($this->Controller)); + try { + $this->Controller->Components->trigger('beforeRender', array($this->Controller)); + $this->fail('InvalidArgumentException was not thrown'); + } catch (InvalidArgumentException $e) { + $this->assertSame('Filter model not found: ThisModelDoesNotExist', $e->getMessage()); + } } /** From 2047aaba47b25f6ea9492cf53a655d5e342e82b0 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 2 Jul 2024 12:10:47 +0200 Subject: [PATCH 34/38] fixed failing testCustomSelector test #3 --- .../Component/FilterComponentTest.php | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/Test/Case/Controller/Component/FilterComponentTest.php b/Test/Case/Controller/Component/FilterComponentTest.php index 510c79a..8b9f2b3 100644 --- a/Test/Case/Controller/Component/FilterComponentTest.php +++ b/Test/Case/Controller/Component/FilterComponentTest.php @@ -49,17 +49,10 @@ public function setUp(): void $this->Controller = new DocumentTestsController($request); $this->Controller->uses = array('Document'); - if (array_search($method, array('testPersistence')) !== false) { - $this->Controller->components = array( - 'Session', - 'Filter.Filter' => array('nopersist' => true) - ); - } else { - $this->Controller->components = array( - 'Session', - 'Filter.Filter' - ); - } + $this->Controller->components = array( + 'Session', + 'Filter.Filter' + ); $this->Controller->constructClasses(); $this->Controller->Session->destroy(); @@ -440,6 +433,16 @@ public function testCustomSelector(): void ) ) ); + + /** + * For some reason the mock object DocumentCategory is not found by + * ClassRegistry::init('DocumentCategory'); + * in + * FilterComponent.php:216 + * This line adds the mock object as model class for DocumentCategory. + */ + ClassRegistry::addObject('document_category', new DocumentCategory()); + $this->Controller->filters = $testSettings; $this->Controller->Components->trigger('initialize', array($this->Controller)); From 84942b03614cbb1a0335f0c5dfc1a0bef90ef499 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 2 Jul 2024 12:15:47 +0200 Subject: [PATCH 35/38] ensure the mock object is used in testCustomSelector #3 --- Test/Case/Controller/Component/FilterComponentTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Test/Case/Controller/Component/FilterComponentTest.php b/Test/Case/Controller/Component/FilterComponentTest.php index 8b9f2b3..c4c88c5 100644 --- a/Test/Case/Controller/Component/FilterComponentTest.php +++ b/Test/Case/Controller/Component/FilterComponentTest.php @@ -439,8 +439,9 @@ public function testCustomSelector(): void * ClassRegistry::init('DocumentCategory'); * in * FilterComponent.php:216 - * This line adds the mock object as model class for DocumentCategory. + * The following two lines adds the mock object as model class for DocumentCategory. */ + App::uses('DocumentCategory', 'Filter.Test/Case/MockObjects'); ClassRegistry::addObject('document_category', new DocumentCategory()); $this->Controller->filters = $testSettings; From 0acc82c2170789f691bce5f775d38a4a1e685d6b Mon Sep 17 00:00:00 2001 From: Josef Stich Date: Wed, 3 Jul 2024 11:30:05 +0200 Subject: [PATCH 36/38] fix FilteredBehavior unit test #3 --- Model/Behavior/FilteredBehavior.php | 2 +- .../Model/Behaviors/FilteredBehaviorTest.php | 32 +++++++++++-------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/Model/Behavior/FilteredBehavior.php b/Model/Behavior/FilteredBehavior.php index 5e6c0ef..7de16eb 100644 --- a/Model/Behavior/FilteredBehavior.php +++ b/Model/Behavior/FilteredBehavior.php @@ -122,7 +122,7 @@ protected function addFieldToFilter(Model &$Model, array &$query, array $setting // TODO: implement a bit of a user friendly handling of this scenario.. $errMsg = __('No value present for required field %s and default value not present', $field); if (is_string($errMsg)) { - trigger_error($errMsg); + throw new InvalidArgumentException($errMsg); } return; } diff --git a/Test/Case/Model/Behaviors/FilteredBehaviorTest.php b/Test/Case/Model/Behaviors/FilteredBehaviorTest.php index 48d80e8..d508973 100644 --- a/Test/Case/Model/Behaviors/FilteredBehaviorTest.php +++ b/Test/Case/Model/Behaviors/FilteredBehaviorTest.php @@ -12,15 +12,16 @@ */ App::import('Core', array('AppModel', 'Model')); -App::uses('Document', 'Filter.Test/Case/MockObjects'); -App::uses('Document2', 'Filter.Test/Case/MockObjects'); -App::uses('Document3', 'Filter.Test/Case/MockObjects'); -App::uses('DocumentCategory', 'Filter.Test/Case/MockObjects'); App::uses('DocumentTestsController', 'Filter.Test/Case/MockObjects'); -App::uses('Item', 'Filter.Test/Case/MockObjects'); -App::uses('Metadata', 'Filter.Test/Case/MockObjects'); -class FilteredBehaviorTest extends \PHPUnit\Framework\TestCase +require_once(dirname(__FILE__, 3) . DS . 'MockObjects/Document.php'); +require_once(dirname(__FILE__, 3) . DS . 'MockObjects/Document2.php'); +require_once(dirname(__FILE__, 3) . DS . 'MockObjects/Document3.php'); +require_once(dirname(__FILE__, 3) . DS . 'MockObjects/DocumentCategory.php'); +require_once(dirname(__FILE__, 3) . DS . 'MockObjects/Item.php'); +require_once(dirname(__FILE__, 3) . DS . 'MockObjects/Metadata.php'); + +class FilteredBehaviorTest extends CakeTestCase { /** * @var string[] @@ -34,7 +35,7 @@ class FilteredBehaviorTest extends \PHPUnit\Framework\TestCase ); /** - * @var AppModel|Document|Document2|Document3 + * @var Model|Document|Document2|Document3 */ public $Document; @@ -47,10 +48,9 @@ public function setUp(): void parent::setUp(); $Document = ClassRegistry::init('Document'); - if ($Document instanceof AppModel) { + if ($Document instanceof Model) { $this->Document = $Document; } else { - var_dump(get_class($Document)); throw new Exception('Can not create Document model'); } } @@ -154,8 +154,12 @@ public function testLoadingRequiredFieldValueMissing(): void ); $this->Document->setFilterValues($filterValues); - $this->expectException('PHPUnit_Framework_Error_Notice'); - $this->Document->find('first'); + try { + $this->Document->find('first'); + $this->fail('InvalidArgumentException was not thrown'); + } catch (InvalidArgumentException $e) { + $this->assertSame('No value present for required field Document.title and default value not present', $e->getMessage()); + } } /** @@ -630,7 +634,7 @@ public function testExitWhenNoSettings(): void public function testBeforeDataFilterCallbackCancel(): void { $document2 = ClassRegistry::init('Document2'); - if ($document2 instanceof AppModel) { + if ($document2 instanceof Model) { $this->Document = $document2; } else { throw new Exception('Can not create Document2 model'); @@ -670,7 +674,7 @@ public function testBeforeDataFilterCallbackCancel(): void public function testAfterDataFilterCallbackQueryChange(): void { $document = ClassRegistry::init('Document3'); - if ($document instanceof AppModel) { + if ($document instanceof Model) { $this->Document = $document; } else { throw new Exception('Can not create Document3 model'); From 8a575059dc444a693b5c248eefc68be24267e936 Mon Sep 17 00:00:00 2001 From: Josef Stich Date: Wed, 3 Jul 2024 11:30:36 +0200 Subject: [PATCH 37/38] use model mock objects in FilterComponentTest #3 --- .../Component/FilterComponentTest.php | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/Test/Case/Controller/Component/FilterComponentTest.php b/Test/Case/Controller/Component/FilterComponentTest.php index c4c88c5..fc7083b 100644 --- a/Test/Case/Controller/Component/FilterComponentTest.php +++ b/Test/Case/Controller/Component/FilterComponentTest.php @@ -13,13 +13,14 @@ App::uses('Router', 'Routing'); App::uses('Component', 'Filter.Filter'); -App::uses('Document', 'Filter.Test/Case/MockObjects'); -App::uses('Document2', 'Filter.Test/Case/MockObjects'); -App::uses('Document3', 'Filter.Test/Case/MockObjects'); -App::uses('DocumentCategory', 'Filter.Test/Case/MockObjects'); App::uses('DocumentTestsController', 'Filter.Test/Case/MockObjects'); -App::uses('Item', 'Filter.Test/Case/MockObjects'); -App::uses('Metadata', 'Filter.Test/Case/MockObjects'); + +require_once(dirname(__FILE__, 3) . DS . 'MockObjects/Document.php'); +require_once(dirname(__FILE__, 3) . DS . 'MockObjects/Document2.php'); +require_once(dirname(__FILE__, 3) . DS . 'MockObjects/Document3.php'); +require_once(dirname(__FILE__, 3) . DS . 'MockObjects/DocumentCategory.php'); +require_once(dirname(__FILE__, 3) . DS . 'MockObjects/Item.php'); +require_once(dirname(__FILE__, 3) . DS . 'MockObjects/Metadata.php'); class FilterComponentTest extends CakeTestCase { @@ -434,16 +435,6 @@ public function testCustomSelector(): void ) ); - /** - * For some reason the mock object DocumentCategory is not found by - * ClassRegistry::init('DocumentCategory'); - * in - * FilterComponent.php:216 - * The following two lines adds the mock object as model class for DocumentCategory. - */ - App::uses('DocumentCategory', 'Filter.Test/Case/MockObjects'); - ClassRegistry::addObject('document_category', new DocumentCategory()); - $this->Controller->filters = $testSettings; $this->Controller->Components->trigger('initialize', array($this->Controller)); From 41b78d5a372b97d174881abaefac9315ba41206b Mon Sep 17 00:00:00 2001 From: Josef Stich Date: Wed, 3 Jul 2024 11:36:42 +0200 Subject: [PATCH 38/38] ignore some phpstan findings #3 Fixing them would not improve code quality --- phpstan.neon | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/phpstan.neon b/phpstan.neon index a776730..290e230 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -5,6 +5,11 @@ parameters: stubFiles: - phpstan/stubs/CakePHP.stub ignoreErrors: - # False positive as __() function can accept more than 2 parameters. - '/Function __ invoked with [0-9_]+ parameters, 1-2 required./' - '/Access to an undefined property BehaviorCollection::\$Filtered./' + - '/Dead catch \- InvalidArgumentException is never thrown in the try block./' + - '/Unreachable statement \- code above always terminates./' + - '/Call to an undefined method Model::setFilterValues\(\)./' + - '/Call to an undefined method Model::getFilterValues\(\)./' + - '/Access to an undefined property Model::\$DocumentCategory./' + - '/Access to an undefined property Model::\$itemToUnset./'