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/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..975b32a --- /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 + run: php -l Controller/ Model/ Test/ View/ + + # phpstan for several php versions + phpstan: + runs-on: ubuntu-latest + strategy: + matrix: + php_version: [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/ diff --git a/.gitignore b/.gitignore index 82dcd5b..dce9690 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ +/.idea/ /.settings/ /.buildpath /.project -/Vendor/ +/vendor/ +/composer.lock +.uuid diff --git a/Controller/Component/FilterComponent.php b/Controller/Component/FilterComponent.php index 0e6a8fb..9db026a 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,355 @@ */ 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) - { - } + /** + * @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; + } + + /** + * @param Controller $controller + * @return void + */ + public function initialize(Controller $controller): void + { + 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})) { + $errMsg = __('Filter model not found: %s', $model); + if (is_string($errMsg)) { + throw new InvalidArgumentException($errMsg); + } + continue; + } + + $controller->$model->Behaviors->attach('Filter.Filtered', $filter); + } + } + + public function startup(Controller $controller) + { + if (!isset($this->settings[$controller->name][$controller->request->action])) { + return; + } + + /** @var array> $settings */ + $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)) { + $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(); + + if ($this->Session->check($sessionKey)) { + $persistedData = $this->Session->read($sessionKey); + } + + if (empty($persistedData) || !is_array($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})) { + $errMsg = __('Filter model not found: %s', $model); + if (is_string($errMsg)) { + throw new InvalidArgumentException($errMsg); + } + continue; + } + + $controller->$model->setFilterValues($this->formData); + } + } + + public function beforeRender(Controller $controller) + { + if (!isset($this->settings[$controller->name][$controller->request->action])) { + return; + } + + /** @var array>> $models */ + $models = $this->settings[$controller->name][$controller->request->action]; + $viewFilterParams = array(); + + foreach ($models as $model => $fields) { + if (!isset($controller->$model)) { + $errMsg = __('Filter model not found: %s', $model); + if (is_string($errMsg)) { + throw new InvalidArgumentException($errMsg); + } + 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(); + /** @var Model $workingModel */ + $workingModel = ClassRegistry::init($fieldModel); + + if (isset($settings['selectOptions'])) { + $selectOptions = $settings['selectOptions']; + } + + if (isset($settings['selector'])) { + if (!method_exists($workingModel, $settings['selector'])) { + $errMsg = __( + 'Selector method "%s" not found in model "%s" for field "%s"!', + $settings['selector'], + $fieldModel, + $fieldName + ); + if (is_string($errMsg)) { + trigger_error($errMsg); + } + 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); + } + + /** + * @param Controller $controller + * @param array $settings + * @return void + */ + private function __updatePersistence(Controller $controller, array $settings): void + { + if ($this->Session->check('FilterPlugin.NoPersist')) { + $nopersistFromSession = $this->Session->read('FilterPlugin.NoPersist'); + if (is_array($nopersistFromSession)) { + $this->nopersist = $nopersistFromSession; + } + } + + 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(); + } + + /** @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)); + 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..7de16eb 100644 --- a/Model/Behavior/FilteredBehavior.php +++ b/Model/Behavior/FilteredBehavior.php @@ -1,464 +1,452 @@ - - 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(); + + /** + * @param Model $Model + * @param array $settings + * @return void + */ + 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(); + } + + /** + * @param Model $Model + * @param array $query + * @return array + */ + public function beforeFind(Model $Model, $query): array + { + 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]; + /** @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]; + + $result = $Model->afterDataFilter($query, $callbackOptions); + + if (is_array($result)) { + $query = $result; + } + } + + return $query; + } + + /** + * @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; + + 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.. + $errMsg = __('No value present for required field %s and default value not present', $field); + if (is_string($errMsg)) { + throw new InvalidArgumentException($errMsg); + } + 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 = ''; + $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 + * @param string $linkModelName + * @return array Cake join array + */ + protected function buildFilterJoin(Model &$Model, Model &$relatedModel, string $linkModelName): array + { + $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 + ), + (is_string($relatedModelAlias) ? $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, string $field, array $options, $value): void + { + $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': + /** @var bool|float|int|string|null $value */ + 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 mixed $string String to sanitize. + * @param string $connection Database connection being used. + * @return mixed SQL safe string. + */ + private function __escape($string, string $connection = 'default') + { + if (!is_string($string)) { + return $string; + } + /** @var DboSource $db */ + $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 mixed $data Data to sanitize. + * @param string $connection DB connection being used. + * @return mixed Sanitized data. + */ + private function __clean($data, string $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 &$Model, array $values = array()): void + { + $values = $this->__clean($values, $Model->useDbConfig); + $this->_filterValues[$Model->alias] = array_merge($this->_filterValues[$Model->alias], (array)$values); + } + + /** + * Gets filter values. + * + * @return array + */ + public function getFilterValues(): array + { + 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/README.markdown b/README.md similarity index 67% rename from README.markdown rename to README.md index ea82238..27074e2 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,16 +14,13 @@ 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: - 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 @@ -33,27 +30,54 @@ this article: 'GET'` to the `filterForm()` or `beginForm()` options array. -## Contributing ## +## Testing + +To run PHPUnit tests: + +```shell +Test/phpunit.sh +``` + +Execute shell for manual testing + +```shell +docker pull devkba/cake2-app-template:staging +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" \ + devkba/cake2-app-template:staging +``` + +## 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; - } - - // good - public function drink() - { - return true; - } - -## Licence ## +```php +// bad +public function drink() { + return false; +} +``` + +```php +// good +public function drink() +{ + return true; +} +``` + +## Licence Multi-licensed under: diff --git a/Test/Case/AllTest.php b/Test/Case/AllTest.php deleted file mode 100644 index 09702ea..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() - { - $suite = new CakeTestSuite('All FilterPlugin tests'); - - $suite->addTestDirectoryRecursive(CakePlugin::path('Filter').'Test'.DS.'Case'); - - return $suite; - } -} diff --git a/Test/Case/Controller/Component/FilterComponentTest.php b/Test/Case/Controller/Component/FilterComponentTest.php index 3e88749..fc7083b 100644 --- a/Test/Case/Controller/Component/FilterComponentTest.php +++ b/Test/Case/Controller/Component/FilterComponentTest.php @@ -1,684 +1,628 @@ - - 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'); -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 { - 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']); - } + /** + * @var string[] + */ + public $fixtures = array + ( + 'plugin.filter.document_category', + 'plugin.filter.document', + 'plugin.filter.item', + 'plugin.filter.metadata', + ); + + /** + * @var \DocumentTestsController + */ + public $Controller; + + public function setUp(): void + { + parent::setUp(); + + 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'); + + $this->Controller->components = array( + 'Session', + 'Filter.Filter' + ); + + $this->Controller->constructClasses(); + $this->Controller->Session->destroy(); + $this->Controller->Components->trigger('initialize', array($this->Controller)); + } + + public function tearDown(): void + { + parent::tearDown(); + + $this->Controller->Session->destroy(); + unset($this->Controller); + } + + /** + * Test bailing out when no filters are present. + */ + 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->assertIsBool($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)); + } + + /** + * Test bailing out when a filter model can't be found + * or when the current action has no filters. + */ + public function testNoModelPresentOrNoActionFilters(): void + { + $testSettings = array( + 'index' => array( + 'DocumentArse' => array( + 'DocumentFeck.drink' => array('type' => 'irrelevant') + ) + ) + ); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Filter model not found: DocumentArse'); + + $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)); + $isBehaviorEnabled = $this->Controller->Document->Behaviors->enabled('Filtered'); + $this->assertIsBool($isBehaviorEnabled); + if (is_bool($isBehaviorEnabled)) { + $this->assertFalse($isBehaviorEnabled); + } + + $testSettings = array( + 'index' => array( + 'Document' => array( + 'Document.title' => array('type' => 'text') + ) + ), + ); + + $this->Controller->filters = $testSettings; + $this->Controller->Components->trigger('initialize', array($this->Controller)); + $isBehaviorEnabled = $this->Controller->Document->Behaviors->enabled('Filtered'); + $this->assertIsBool($isBehaviorEnabled); + if (is_bool($isBehaviorEnabled)) { + $this->assertTrue($isBehaviorEnabled); + } + } + + /** + * Test basic filter settings. + */ + public function testBasicFilters(): void + { + $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(): void + { + $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(): void + { + $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); + 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()); + } + + $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('Filter model not found: FakeNonexistant', $e2->getMessage()); + } + $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(): void + { + $_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(): void + { + $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(): void + { + $testSettings = array( + 'index' => array( + 'ThisModelDoesNotExist' => array( + 'ThisModelDoesNotExist.title' => array('type' => 'text') + ) + ) + ); + $this->Controller->filters = $testSettings; + + 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()); + } + + $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); + + 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()); + } + } + + /** + * Test the view variable generation for very basic filtering. + * Also tests model name detection and custom label. + */ + public function testBasicViewInfo(): void + { + $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(): void + { + $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(): void + { + $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(): void + { + $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(): void + { + $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(): void + { + $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(): void + { + $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(): void + { + $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..aa44dd2 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 28af3d5..fe5f99e 100644 --- a/Test/Case/MockObjects/Document2.php +++ b/Test/Case/MockObjects/Document2.php @@ -1,16 +1,43 @@ returnValue; - } + /** + * @var string[] + */ + public $belongsTo = array('DocumentCategory'); + + /** + * @var string[] + */ + public $hasMany = array('Item'); + + /** + * @var bool + */ + public $returnValue = false; + + /** + * @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 a74ea53..32f2dcd 100644 --- a/Test/Case/MockObjects/Document3.php +++ b/Test/Case/MockObjects/Document3.php @@ -1,26 +1,51 @@ itemToUnset)) - { - return $query; - } - - if (isset($query['conditions'][$this->itemToUnset])) - { - unset($query['conditions'][$this->itemToUnset]); - } - - return $query; - } + /** + * @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; + + /** + * @param array> $query + * @param array $options + * @return array + */ + public function afterDataFilter(array $query, array $options): array + { + if (!is_string($this->itemToUnset)) { + return $query; + } + + if (isset($query['conditions'][$this->itemToUnset])) { + unset($query['conditions'][$this->itemToUnset]); + } + + return $query; + } } diff --git a/Test/Case/MockObjects/DocumentCategory.php b/Test/Case/MockObjects/DocumentCategory.php index b53814d..403c03c 100644 --- a/Test/Case/MockObjects/DocumentCategory.php +++ b/Test/Case/MockObjects/DocumentCategory.php @@ -1,15 +1,29 @@ find('list', $options); - } + /** + * @param array> $options + * @return array|int|null + */ + public function customSelector(array $options = array()) + { + $options['conditions']['DocumentCategory.title LIKE'] = '%T%'; + $options['nofilter'] = true; + + return $this->find('list', $options); + } } diff --git a/Test/Case/MockObjects/DocumentTestsController.php b/Test/Case/MockObjects/DocumentTestsController.php index 68842ce..95f6b66 100644 --- a/Test/Case/MockObjects/DocumentTestsController.php +++ b/Test/Case/MockObjects/DocumentTestsController.php @@ -2,17 +2,32 @@ App::uses('Controller', 'Controller'); +/** + * @property array $filters + * @property FilterComponent $Filter + * @property Document $Document + */ class DocumentTestsController extends Controller { - public $name = 'DocumentTests'; + /** + * @var string + */ + public $name = 'DocumentTests'; - public function index() - { - } + public function index(): void + { + } - // 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? + /** + * @param array|string $url + * @param array $status + * @param bool $exit + * @return CakeResponse|null + */ + public function redirect($url, $status = null, $exit = true) + { + return $this->response; + } } diff --git a/Test/Case/MockObjects/Item.php b/Test/Case/MockObjects/Item.php index 64f109f..1f50608 100644 --- a/Test/Case/MockObjects/Item.php +++ b/Test/Case/MockObjects/Item.php @@ -2,6 +2,13 @@ class Item extends CakeTestModel { - public $name = 'Item'; - public $belongsTo = array('Document'); + /** + * @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 cfdcd59..d7262ce 100644 --- a/Test/Case/MockObjects/Metadata.php +++ b/Test/Case/MockObjects/Metadata.php @@ -2,6 +2,13 @@ class Metadata extends CakeTestModel { - public $name = 'Metadata'; - public $hasOne = array('Document'); + /** + * @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 086d691..d508973 100644 --- a/Test/Case/Model/Behaviors/FilteredBehaviorTest.php +++ b/Test/Case/Model/Behaviors/FilteredBehaviorTest.php @@ -1,748 +1,709 @@ - - 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'); -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 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); - } + /** + * @var string[] + */ + public $fixtures = array + ( + 'plugin.filter.document_category', + 'plugin.filter.document', + 'plugin.filter.item', + 'plugin.filter.metadata', + ); + + /** + * @var Model|Document|Document2|Document3 + */ + public $Document; + + /** + * @return void + * @throws Exception + */ + public function setUp(): void + { + parent::setUp(); + $Document = ClassRegistry::init('Document'); + + if ($Document instanceof Model) { + $this->Document = $Document; + } else { + throw new Exception('Can not create Document model'); + } + } + + /** + * Detach and re-attach the behavior to reset the options. + * + * @param array $options Behavior options. + */ + protected function _reattachBehavior(array $options = array()): void + { + $this->Document->Behaviors->detach('Filtered'); + $this->Document->Behaviors->attach('Filter.Filtered', $options); + } + + /** + * Test attaching without options. + */ + public function testBlankAttaching(): void + { + $this->Document->Behaviors->attach('Filter.Filtered'); + $isBehaviorAttached = $this->Document->Behaviors->enabled('Filtered'); + $this->assertIsBool($isBehaviorAttached); + if (is_bool($isBehaviorAttached)) { + $this->assertTrue($isBehaviorAttached); + } + } + + /** + * Test attaching with options. + */ + public function testInitSettings(): void + { + $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(): void + { + $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(): void + { + $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(): void + { + $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); + + 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()); + } + } + + /** + * Test filtering with conditions from current model and belongsTo model. + */ + public function testFilteringBelongsTo(): void + { + $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(): void + { + $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(): void + { + $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(): void + { + $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(): void + { + $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(): void + { + $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(): void + { + $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(): void + { + $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(): void + { + $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(): void + { + $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(): void + { + $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(): void + { + $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. + * + * @throws Exception + */ + public function testBeforeDataFilterCallbackCancel(): void + { + $document2 = ClassRegistry::init('Document2'); + if ($document2 instanceof Model) { + $this->Document = $document2; + } else { + throw new Exception('Can not create Document2 model'); + } + + $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. + * @throws Exception + */ + public function testAfterDataFilterCallbackQueryChange(): void + { + $document = ClassRegistry::init('Document3'); + if ($document instanceof Model) { + $this->Document = $document; + } else { + throw new Exception('Can not create Document3 model'); + } + $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..83d46e4 100644 --- a/Test/Fixture/DocumentCategoryFixture.php +++ b/Test/Fixture/DocumentCategoryFixture.php @@ -1,33 +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 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) - ); + /** + * @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) + ); - 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!') - ); + /** + * @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'), + 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..90b4adb 100644 --- a/Test/Fixture/DocumentFixture.php +++ b/Test/Fixture/DocumentFixture.php @@ -1,39 +1,43 @@ - - 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) - ); + /** + * @var array> + */ + 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'), - ); + /** + * @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'), + 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..9a95fb0 100644 --- a/Test/Fixture/ItemFixture.php +++ b/Test/Fixture/ItemFixture.php @@ -1,38 +1,42 @@ - - 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) - ); + /** + * @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) + ); - 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') - ); + /** + * @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'), + 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..e538ccc 100644 --- a/Test/Fixture/MetadataFixture.php +++ b/Test/Fixture/MetadataFixture.php @@ -1,35 +1,42 @@ - - 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'; + /** + * @var string + */ + 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), - ); + /** + * @var array> + */ + 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--'), - ); + /** + * @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-------'), + 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/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/before_script.sh b/Test/before_script.sh new file mode 100755 index 0000000..a92cd29 --- /dev/null +++ b/Test/before_script.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env sh +# 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 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} 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..b68edd7 100644 --- a/View/Helper/FilterHelper.php +++ b/View/Helper/FilterHelper.php @@ -1,104 +1,117 @@ - - 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; + /** + * @var View + */ + protected $_view = null; - public function __construct(View $view, $settings = array()) - { - $this->_view = $view; - } + /** + * @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) - { - $view =& $this->_view; + /** + * @param string $modelName + * @param array $options + * @return string + */ + public function filterForm(string $modelName, array $options): string + { + $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') - ); + /** + * @param string $modelName + * @param array $options + * @return string + */ + public function beginForm(string $modelName, array $options): string + { + $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') - ); + /** + * @param array $fields + * @return string + */ + public function inputFields(array $fields = array()): string + { + $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(): string + { + $view = $this->_view; + $output = $view->element( + 'filter_form_end', + array(), + array('plugin' => 'Filter') + ); - return $output; - } + return $output; + } } diff --git a/composer.json b/composer.json index 9890f5a..3d39552 100644 --- a/composer.json +++ b/composer.json @@ -5,14 +5,19 @@ "license": "GPL-3.0-or-later", "minimum-stability": "stable", "require": { - "php": "^7.2", - "cakephp/cakephp": "^2.4", + "php": ">=8.1, <8.4", + "kba-team/cakephp": "^2.11", "composer/installers": "^1.9" }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, "extra": { "installer-name": "Filter" }, "config": { - "vendor-dir": "Vendor/" + "allow-plugins": { + "composer/installers": true + } } } 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" -} diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..290e230 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,15 @@ +parameters: + level: 9 + scanDirectories: + - vendor + stubFiles: + - phpstan/stubs/CakePHP.stub + ignoreErrors: + - '/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./' 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 @@ +