From e2bd2e436df0955eb85fc905e303127d83eb532a Mon Sep 17 00:00:00 2001 From: bancer Date: Thu, 24 Aug 2023 14:46:07 +0200 Subject: [PATCH 01/37] Bump CakePHP version to 3.x --- .github/actions/setup-cakephp/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup-cakephp/action.yml b/.github/actions/setup-cakephp/action.yml index 741a7e9..ecb45fa 100644 --- a/.github/actions/setup-cakephp/action.yml +++ b/.github/actions/setup-cakephp/action.yml @@ -10,7 +10,7 @@ runs: with: repository: cakephp/cakephp path: ./cakephp - ref: 2.10.14 + ref: 3.10.5 - name: Make tmp folder writable run: chmod -R 777 ./cakephp/app/tmp From aec0c3d4e1367c7c60b2ba7500646780b2e95ea7 Mon Sep 17 00:00:00 2001 From: Val Bancer Date: Thu, 24 Aug 2023 14:53:46 +0200 Subject: [PATCH 02/37] Add branch 3.x to CI --- .github/workflows/pull-request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index ef77152..954a2ed 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -3,7 +3,7 @@ run-name: Pull to ${{ github.base_ref }} by ${{ github.actor }} on: pull_request: - branches: [master, develop] + branches: [master, develop, 3.x] jobs: Unit-Tests: From 3e522b95357c644c5326d00352225561c1ce0ace Mon Sep 17 00:00:00 2001 From: bancer Date: Mon, 28 Aug 2023 13:24:15 +0200 Subject: [PATCH 03/37] Migrate to 3.x --- .github/actions/setup-cakephp/action.yml | 42 +- .github/workflows/pull-request.yml | 27 +- .gitignore | 2 + Controller/FilterAppController.php | 17 - Model/Behavior/FilteredBehavior.php | 493 --------- Model/FilterAppModel.php | 17 - Test/Case/AllTest.php | 28 - Test/Case/MockObjects/Document.php | 28 - Test/Case/MockObjects/Document2.php | 38 - Test/Case/MockObjects/Document3.php | 48 - Test/Case/MockObjects/DocumentCategory.php | 28 - Test/Case/MockObjects/Item.php | 14 - Test/Case/MockObjects/Metadata.php | 11 - .../Model/Behaviors/FilteredBehaviorTest.php | 803 --------------- composer.json | 21 + phpstan.neon | 3 - phpunit.xml | 26 + .../Controller}/Component/FilterComponent.php | 201 ++-- src/Model/Behavior/FilteredBehavior.php | 498 +++++++++ .../Fixture/DocumentCategoriesFixture.php | 14 +- .../Fixture/DocumentsFixture.php | 14 +- .../Fixture/ItemsFixture.php | 14 +- {Test => tests}/Fixture/MetadataFixture.php | 12 +- .../Component/FilterComponentTest.php | 305 +++--- .../MockObjects/DocumentCategoriesTable.php | 37 + .../MockObjects/DocumentTestsController.php | 24 +- .../TestCase/MockObjects/Documents2Table.php | 42 + .../TestCase/MockObjects/Documents3Table.php | 54 + tests/TestCase/MockObjects/DocumentsTable.php | 27 + tests/TestCase/MockObjects/ItemsTable.php | 21 + tests/TestCase/MockObjects/MetadataTable.php | 18 + .../Model/Behavior/FilteredBehaviorTest.php | 975 ++++++++++++++++++ tests/bootstrap.php | 15 + 33 files changed, 2112 insertions(+), 1805 deletions(-) delete mode 100644 Controller/FilterAppController.php delete mode 100644 Model/Behavior/FilteredBehavior.php delete mode 100644 Model/FilterAppModel.php delete mode 100644 Test/Case/AllTest.php delete mode 100644 Test/Case/MockObjects/Document.php delete mode 100644 Test/Case/MockObjects/Document2.php delete mode 100644 Test/Case/MockObjects/Document3.php delete mode 100644 Test/Case/MockObjects/DocumentCategory.php delete mode 100644 Test/Case/MockObjects/Item.php delete mode 100644 Test/Case/MockObjects/Metadata.php delete mode 100644 Test/Case/Model/Behaviors/FilteredBehaviorTest.php create mode 100644 composer.json create mode 100644 phpunit.xml rename {Controller => src/Controller}/Component/FilterComponent.php (51%) create mode 100644 src/Model/Behavior/FilteredBehavior.php rename Test/Fixture/DocumentCategoryFixture.php => tests/Fixture/DocumentCategoriesFixture.php (76%) rename Test/Fixture/DocumentFixture.php => tests/Fixture/DocumentsFixture.php (87%) rename Test/Fixture/ItemFixture.php => tests/Fixture/ItemsFixture.php (82%) rename {Test => tests}/Fixture/MetadataFixture.php (83%) rename {Test/Case => tests/TestCase}/Controller/Component/FilterComponentTest.php (58%) create mode 100644 tests/TestCase/MockObjects/DocumentCategoriesTable.php rename {Test/Case => tests/TestCase}/MockObjects/DocumentTestsController.php (50%) create mode 100644 tests/TestCase/MockObjects/Documents2Table.php create mode 100644 tests/TestCase/MockObjects/Documents3Table.php create mode 100644 tests/TestCase/MockObjects/DocumentsTable.php create mode 100644 tests/TestCase/MockObjects/ItemsTable.php create mode 100644 tests/TestCase/MockObjects/MetadataTable.php create mode 100644 tests/TestCase/Model/Behavior/FilteredBehaviorTest.php create mode 100644 tests/bootstrap.php diff --git a/.github/actions/setup-cakephp/action.yml b/.github/actions/setup-cakephp/action.yml index ecb45fa..399fda1 100644 --- a/.github/actions/setup-cakephp/action.yml +++ b/.github/actions/setup-cakephp/action.yml @@ -12,26 +12,26 @@ runs: path: ./cakephp ref: 3.10.5 - - name: Make tmp folder writable - run: chmod -R 777 ./cakephp/app/tmp - shell: bash +# - name: Make tmp folder writable +# run: chmod -R 777 ./cakephp/app/tmp +# shell: bash - - name: Fix composer autoload - run: | - echo " /tmp/core.php - echo "require ROOT . '/vendors/autoload.php'; " >> /tmp/core.php - echo "spl_autoload_unregister(array('App', 'load')); " >> /tmp/core.php - echo "spl_autoload_register(array('App', 'load'), true, true); " >> /tmp/core.php - echo "?>" >> /tmp/core.php - cat ./cakephp/app/Config/core.php >> /tmp/core.php - cp /tmp/core.php ./cakephp/app/Config/core.php - shell: bash +# - name: Fix composer autoload +# run: | +# echo " /tmp/core.php +# echo "require ROOT . '/vendors/autoload.php'; " >> /tmp/core.php +# echo "spl_autoload_unregister(array('App', 'load')); " >> /tmp/core.php +# echo "spl_autoload_register(array('App', 'load'), true, true); " >> /tmp/core.php +# echo "?>" >> /tmp/core.php +# cat ./cakephp/app/Config/core.php >> /tmp/core.php +# cp /tmp/core.php ./cakephp/app/Config/core.php +# shell: bash - - name: Copy plugin files to plugins folder - run: | - mkdir -p ./cakephp/plugins/Filter - cp -R ./Controller ./cakephp/plugins/Filter/Controller - cp -R ./Model ./cakephp/plugins/Filter/Model - cp -R ./Test ./cakephp/plugins/Filter/Test - cp -R ./View ./cakephp/plugins/Filter/View - shell: bash +# - name: Copy plugin files to plugins folder +# run: | +# mkdir -p ./cakephp/plugins/Filter +# cp -R ./Controller ./cakephp/plugins/Filter/Controller +# cp -R ./Model ./cakephp/plugins/Filter/Model +# cp -R ./Test ./cakephp/plugins/Filter/Test +# cp -R ./View ./cakephp/plugins/Filter/View +# shell: bash diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 954a2ed..8f7a8a0 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -22,18 +22,21 @@ jobs: mysql-version: '5.6' - name: Check out repository code uses: actions/checkout@v3 - - name: Setup CakePHP - uses: ./.github/actions/setup-cakephp - - name: Setup database - uses: ./.github/actions/setup-database - - name: Install PHPUnit - run: | - cd ./cakephp - composer require 'phpunit/phpunit=5.7' - cd ../ + #- name: Setup CakePHP + # uses: ./.github/actions/setup-cakephp + #- name: Setup database + # uses: ./.github/actions/setup-database + - name: Create test database + run: mysql -u root -e "CREATE DATABASE cakephp_test" + shell: bash + #- name: Install PHPUnit + # run: | + # cd ./cakephp + # composer require 'phpunit/phpunit=5.7' + # cd ../ - name: Unit Tests - run: ./cakephp/lib/Cake/Console/cake test Filter All --stderr -app ./cakephp/app + run: ./vendor/bin/phpunit ./tests PHP-Lint: runs-on: ubuntu-latest @@ -44,8 +47,6 @@ jobs: php-version: 7.4 - name: Check out repository code uses: actions/checkout@v3 - - name: Install PHPLint - run: composer require --dev overtrue/phplint - name: Run PHPLint run: vendor/bin/phplint @@ -67,4 +68,4 @@ jobs: composer require --dev phpstan/phpstan composer require --dev phpstan/phpstan-phpunit - name: PHPStan - run: vendor/bin/phpstan analyse --level=8 ./cakephp/plugins/Filter + run: vendor/bin/phpstan analyse --level=8 ./src diff --git a/.gitignore b/.gitignore index ca21e90..004611e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ /.settings/ /.buildpath /.project +/composer.lock +/vendor/ diff --git a/Controller/FilterAppController.php b/Controller/FilterAppController.php deleted file mode 100644 index 50f98c9..0000000 --- a/Controller/FilterAppController.php +++ /dev/null @@ -1,17 +0,0 @@ - - - Multi-licensed under: - MPL - LGPL - GPL -*/ - -class FilterAppController extends AppController -{ - -} diff --git a/Model/Behavior/FilteredBehavior.php b/Model/Behavior/FilteredBehavior.php deleted file mode 100644 index 8aa0984..0000000 --- a/Model/Behavior/FilteredBehavior.php +++ /dev/null @@ -1,493 +0,0 @@ - - - Multi-licensed under: - MPL - LGPL - GPL -*/ - -App::uses('Sanitize', 'Utility'); - -class FilteredBehavior extends ModelBehavior -{ - /** - * Keeps current values after filter form post. - * - * @var mixed[] - */ - protected $_filterValues = array(); - - /** - * {@inheritDoc} - * - * @param \Model $Model Model using this behavior - * @param mixed[] $settings Configuration settings for $model - * @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(); - } - - /** - * {@inheritDoc} - * - * @param \Model $Model Model using this behavior - * @param mixed[] $query Data used to execute this query, i.e. conditions, order, etc. - * @return bool|mixed[] - */ - 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; - } - - /** - * @param \Model $Model - * @param mixed[] $query - * @param mixed[] $settings - * @param mixed[] $values - * @param string $field - * @param mixed[] $field_options - * @return void - */ - 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 - * @param string $linkModelName - * @return mixed[] 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); - } - if ($relatedModelAlias !== null) { - $aliasPattern = sprintf('#(?alias); - $filterConditions = preg_replace($aliasPattern, $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 mixed[] $query Cake query array. - * @param string $field Filter field. - * @param mixed[] $options Configuration options for this field. - * @param mixed $value Field value. - * @return void - */ - 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 'select': - if (is_string($value) && strlen(trim(strval($value))) == 0) - { - break; - } - - $query['conditions'][$field] = $value; - break; - case 'checkbox': - $query['conditions'][$field] = $value; - break; - default: - 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; - } - } - - /** - * Makes a string SQL-safe. - * - * @param bool|string|null $string String to sanitize. - * @param string $connection Database connection being used. - * @return bool|string|null SQL safe string. - */ - private function __escape($string, $connection = 'default') - { - if (is_numeric($string) || $string === null || is_bool($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 string|mixed[] $data Data to sanitize. - * @param string $connection DB connection being used. - * @return (bool|string|null)|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 mixed[] $values Filter values. - * @return void - */ - 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 mixed[] - */ - public function getFilterValues($Model) - { - return $this->_filterValues; - } - -} diff --git a/Model/FilterAppModel.php b/Model/FilterAppModel.php deleted file mode 100644 index 0c66581..0000000 --- a/Model/FilterAppModel.php +++ /dev/null @@ -1,17 +0,0 @@ - - - Multi-licensed under: - MPL - LGPL - GPL -*/ - -class FilterAppModel extends AppModel -{ - -} diff --git a/Test/Case/AllTest.php b/Test/Case/AllTest.php deleted file mode 100644 index fd2c232..0000000 --- a/Test/Case/AllTest.php +++ /dev/null @@ -1,28 +0,0 @@ - - - Multi-licensed under: - MPL - LGPL - GPL -*/ -App::uses('CakePlugin', 'Core'); - -class AllFilterTests extends CakeTestSuite -{ - /** - * @return \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/MockObjects/Document.php b/Test/Case/MockObjects/Document.php deleted file mode 100644 index f51ecfc..0000000 --- a/Test/Case/MockObjects/Document.php +++ /dev/null @@ -1,28 +0,0 @@ -returnValue; - } -} diff --git a/Test/Case/MockObjects/Document3.php b/Test/Case/MockObjects/Document3.php deleted file mode 100644 index d0d7eda..0000000 --- a/Test/Case/MockObjects/Document3.php +++ /dev/null @@ -1,48 +0,0 @@ -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 deleted file mode 100644 index c3b1033..0000000 --- a/Test/Case/MockObjects/DocumentCategory.php +++ /dev/null @@ -1,28 +0,0 @@ -find('list', $options); - } -} diff --git a/Test/Case/MockObjects/Item.php b/Test/Case/MockObjects/Item.php deleted file mode 100644 index 4ae19c3..0000000 --- a/Test/Case/MockObjects/Item.php +++ /dev/null @@ -1,14 +0,0 @@ - - - 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'); - -class FilteredBehaviorTest extends CakeTestCase -{ - /** - * @var string[] - */ - public $fixtures = array - ( - 'plugin.filter.document_category', - 'plugin.filter.document', - 'plugin.filter.item', - 'plugin.filter.metadata', - ); - - /** - * @var \Document|\Document2|\Document3 - */ - public $Document = null; - - public function startTest($model) - { - $Document = ClassRegistry::init('Document'); - $this->assertInstanceOf('Document', $Document); - $this->Document = $Document; - } - - public function endTest($model) - { - unset($this->Document); - } - - /** - * Detach and re-attach the behavior to reset the options. - * - * @param mixed[] $options Behavior options. - * @return void - */ - protected function _reattachBehavior($options = array()) - { - $this->Document->Behaviors->detach('Filtered'); - $this->Document->Behaviors->attach('Filter.Filtered', $options); - } - - /** - * Test attaching without options. - * - * @return void - */ - public function testBlankAttaching() - { - $this->Document->Behaviors->attach('Filter.Filtered'); - $this->assertTrue($this->Document->Behaviors->enabled('Filtered')); - } - - /** - * Test attaching with options. - * - * @return void - */ - 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()) - ); - $Filtered = $this->Document->Behaviors->__get('Filtered'); - $this->assertInstanceOf('FilteredBehavior', $Filtered); - $this->assertEquals($expected, $Filtered->settings[$this->Document->alias]); - } - - /** - * Test init settings when only a single field is given, with no extra options. - * - * @return void - */ - public function testInitSettingsSingle() - { - $testOptions = array('Document.title'); - $this->_reattachBehavior($testOptions); - - $expected = array - ( - 'Document.title' => array('type' => 'text', 'condition' => 'like', 'required' => false, 'selectOptions' => array()), - ); - $Filtered = $this->Document->Behaviors->__get('Filtered'); - $this->assertInstanceOf('FilteredBehavior', $Filtered); - $this->assertEquals($expected, $Filtered->settings[$this->Document->alias]); - } - - /** - * Test setting the filter values for future queries. - * - * @return void - */ - 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. - * - * @return void - */ - 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->expectException('PHPUnit_Framework_Error_Notice'); - $this->Document->find('first'); - } - - /** - * Test filtering with conditions from current model and belongsTo model. - * - * @return void - */ - 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); - } - - /** - * @return void - */ - 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. - * - * @return void - */ - 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..) - * - * @return void - */ - 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). - * - * @return void - */ - 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). - * - * @return void - */ - 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. - * - * @return void - */ - 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. - * - * @return void - */ - 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. - * - * @return void - */ - 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. - * - * @return void - */ - 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. - * - * @return void - */ - 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. - * - * @return void - */ - 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. - * - * @return void - */ - public function testBeforeDataFilterCallbackCancel() - { - $Document = ClassRegistry::init('Document2'); - $this->assertInstanceOf('Document2', $Document); - $this->Document = $Document; - $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. - * - * @return void - */ - public function testAfterDataFilterCallbackQueryChange() - { - $Document = ClassRegistry::init('Document3'); - $this->assertInstanceOf('Document3', $Document); - $this->Document = $Document; - $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/composer.json b/composer.json new file mode 100644 index 0000000..9690c93 --- /dev/null +++ b/composer.json @@ -0,0 +1,21 @@ +{ + "name": "lecterror/cakephp-filter-plugin", + "description": "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 \\\"standard\\\" things when displaying data. It also remembers the filter conditions in a session, but this can be turned off if undesirable.", + "type": "cakephp-plugin", + "license": "GPL-3.0-or-later", + "minimum-stability": "stable", + "require": { + "cakephp/cakephp": "^3.10" + }, + "require-dev": { + "overtrue/phplint": "^3.4", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^5.7.14|^6.0" + }, + "autoload": { + "psr-4": { + "Filter\\": "src/", + "Filter\\Test\\": "tests/" + } + } +} diff --git a/phpstan.neon b/phpstan.neon index f391fb0..d67b3f3 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -6,6 +6,3 @@ parameters: - phpstan-bootstrap.php scanDirectories: - ./ - ignoreErrors: - # False positive as __() function can accept more than 2 parameters. - - '/Function __ invoked with [0-9_]+ parameters, 1-2 required./' diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..c352744 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,26 @@ + + + + + tests/ + + + + + + + + + + + + + + + src/ + + + diff --git a/Controller/Component/FilterComponent.php b/src/Controller/Component/FilterComponent.php similarity index 51% rename from Controller/Component/FilterComponent.php rename to src/Controller/Component/FilterComponent.php index 5b96c44..d5c67fc 100644 --- a/Controller/Component/FilterComponent.php +++ b/src/Controller/Component/FilterComponent.php @@ -1,4 +1,11 @@ */ -App::import('Component', 'Session'); -App::import('Behavior', 'Filter.Filtered'); - /** * @property RequestHandlerComponent $RequestHandler * @property SessionComponent $Session @@ -46,71 +50,94 @@ class FilterComponent extends Component protected $_request_settings = array(); /** - * @param \ComponentCollection $collection - * @param mixed[] $settings + * {@inheritDoc} + * + * @param \Cake\Controller\ComponentRegistry $registry A ComponentRegistry this component can use to lazy load its components + * @param mixed[] $config Array of configuration settings. */ - public function __construct(ComponentCollection $collection, $settings = array()) + public function __construct(ComponentRegistry $registry, array $config = []) { - parent::__construct($collection, $settings); - $this->_request_settings = $settings; + parent::__construct($registry, $config); + $this->_request_settings = $config; } - public function initialize(Controller $controller) + /** + * Is called before the controller’s beforeFilter method, but after the controller’s initialize() method. + * + * @param \Cake\Event\Event $event Event object. + * @return void + */ + public function beforeFilter(Event $event) { + $controller = $this->getController(); if (!isset($controller->filters)) { return; } - $this->__updatePersistence($controller, $this->_request_settings); - $this->settings[$controller->name] = $controller->filters; + $this->__updatePersistence($this->_request_settings); + $controllerName = $controller->getName(); + $this->settings[$controllerName] = $controller->filters; - if (!isset($this->settings[$controller->name][$controller->request->action])) + $action = $controller->getRequest()->getParam('action'); + if (!isset($this->settings[$controllerName][$action])) { return; } - $settings = $this->settings[$controller->name][$controller->request->action]; + $settings = $this->settings[$controllerName][$action]; foreach ($settings as $model => $filter) { if (!isset($controller->{$model})) { - trigger_error(__('Filter model not found: %s', $model)); + trigger_error(sprintf('Filter model not found: %s', $model)); continue; } - $controller->$model->Behaviors->attach('Filter.Filtered', $filter); + $controller->$model->addBehavior('Filter.Filtered', $filter); } } - public function startup(Controller $controller) + /** + * Is called after the controller’s beforeFilter method but before the controller executes the current action handler. + * + * @param \Cake\Event\Event $event Event object. + * @return void + */ + public function startup(Event $event) { - if (!isset($this->settings[$controller->name][$controller->request->action])) + $controller = $this->getController(); + $controllerName = $controller->getName(); + $action = $controller->getRequest()->getParam('action'); + if (!isset($this->settings[$controllerName][$action])) { return; } - $settings = $this->settings[$controller->name][$controller->request->action]; + $settings = $this->settings[$controllerName][$action]; - if (!in_array('Filter.Filter', $controller->helpers)) + if (!in_array('Filter.Filter', $controller->viewBuilder()->getHelpers())) { - $controller->helpers[] = 'Filter.Filter'; + $controller->viewBuilder()->setHelpers(['Filter.Filter']); } - $sessionKey = sprintf('FilterPlugin.Filters.%s.%s', $controller->name, $controller->request->action); - $filterFormId = $controller->request->query('filterFormId'); + $sessionKey = sprintf('FilterPlugin.Filters.%s.%s', $controllerName, $action); + $Session = $controller->getRequest()->getSession(); + $filterFormId = $controller->request->getQuery('filterFormId'); if ($controller->request->is('get') && !empty($filterFormId)) { - $this->formData = $controller->request->query('data'); + /** @var mixed[] $requestData */ + $requestData = $controller->request->getQuery('data', []); + $this->formData = $requestData; } - elseif (!$controller->request->is('post') || !isset($controller->request->data['Filter']['filterFormId'])) + elseif (!$controller->request->is('post') || $controller->request->getData('Filter.filterFormId') === null) { $persistedData = array(); - if ($this->Session->check($sessionKey)) + if ($Session->check($sessionKey)) { - $persistedData = $this->Session->read($sessionKey); + $persistedData = $Session->read($sessionKey); } if (empty($persistedData)) @@ -122,13 +149,14 @@ public function startup(Controller $controller) } else { - $this->formData = $controller->request->data; - if ($this->Session->started()) + /** @var mixed[] $requestData */ + $requestData = $controller->request->getData(); + $this->formData = $requestData; + if ($Session->started()) { - $this->Session->write($sessionKey, $this->formData); + $Session->write($sessionKey, $this->formData); } } - foreach ($settings as $model => $options) { if (!isset($controller->{$model})) @@ -141,14 +169,23 @@ public function startup(Controller $controller) } } - public function beforeRender(Controller $controller) + /** + * Is called after the controller executes the requested action’s logic, but before the controller renders views and layout. + * + * @param \Cake\Event\Event $event Event object. + * @return void + */ + public function beforeRender(Event $event) { - if (!isset($this->settings[$controller->name][$controller->request->action])) + $controller = $this->getController(); + $controllerName = $controller->getName(); + $action = $controller->getRequest()->getParam('action'); + if (!isset($this->settings[$controllerName][$action])) { return; } - $models = $this->settings[$controller->name][$controller->request->action]; + $models = $this->settings[$controllerName][$action]; $viewFilterParams = array(); foreach ($models as $model => $fields) @@ -181,7 +218,10 @@ public function beforeRender(Controller $controller) $fieldName = $field; $fieldModel = $model; - + $className = null; + if (isset($settings['className'])) { + $className = $settings['className']; + } if (strpos($field, '.') !== false) { list($fieldModel, $fieldName) = explode('.', $field); @@ -221,10 +261,17 @@ public function beforeRender(Controller $controller) $options['type'] = 'select'; $selectOptions = array(); - /** @var \Model|bool $workingModel */ - $workingModel = ClassRegistry::init($fieldModel); - if (is_bool($workingModel)) { - throw new MissingModelException(array($fieldModel)); + $TableLocator = $this->getController()->getTableLocator(); + if ($TableLocator->exists($fieldModel)) { + $workingModel = $TableLocator->get($fieldModel); + } else { + if ($className !== null) { + $workingModel = $TableLocator->get($fieldModel, [ + 'className' => $className, + ]); + } else { + $workingModel = $TableLocator->get($fieldModel); + } } if (isset($settings['selectOptions'])) @@ -238,7 +285,7 @@ public function beforeRender(Controller $controller) { trigger_error ( - __( + sprintf( 'Selector method "%s" not found in model "%s" for field "%s"!', $settings['selector'], $fieldModel, @@ -255,24 +302,22 @@ public function beforeRender(Controller $controller) { if ($fieldModel == $model) { - $options['options'] = $workingModel->find - ( - 'list', - array_merge - ( - $selectOptions, - array - ( - 'nofilter' => true, - 'fields' => array($fieldName, $fieldName), - ) - ) - ); + $listOptions = array_merge( + $selectOptions, + [ + 'nofilter' => true, + 'keyField' => $fieldName, + 'valueField' => $fieldName, + 'fields' => array($fieldName, $fieldName), + ] + ); } else { - $options['options'] = $workingModel->find('list', array_merge($selectOptions, array('nofilter' => true))); + $listOptions = array_merge($selectOptions, array('nofilter' => true)); } + $options['options'] = $workingModel->find('list', $listOptions) + ->toArray(); } if (!$settings['required']) @@ -322,9 +367,10 @@ public function beforeRender(Controller $controller) } } - if (!empty($this->settings['add_filter_value_to_title']) && - array_search($controller->action, $this->settings['add_filter_value_to_title']) !== false) - { + if ( + !empty($this->settings['add_filter_value_to_title']) && + array_search($action, $this->settings['add_filter_value_to_title']) !== false + ) { $title = $controller->viewVars['title_for_layout']; foreach ($viewFilterParams as $viewFilterParam) { @@ -345,31 +391,33 @@ public function beforeRender(Controller $controller) } /** - * @param \Controller $controller * @param mixed[] $settings * @return void */ - private function __updatePersistence($controller, $settings) + private function __updatePersistence($settings) { - if ($this->Session->check('FilterPlugin.NoPersist')) + $controller = $this->getController(); + $controllerName = $controller->getName(); + $Session = $controller->getRequest()->getSession(); + if ($Session->check('FilterPlugin.NoPersist')) { - $this->nopersist = $this->Session->read('FilterPlugin.NoPersist'); + $this->nopersist = $Session->read('FilterPlugin.NoPersist'); } if (isset($settings['nopersist'])) { - $this->nopersist[$controller->name] = $settings['nopersist']; - if ($this->Session->started()) + $this->nopersist[$controllerName] = $settings['nopersist']; + if ($Session->started()) { - $this->Session->write('FilterPlugin.NoPersist', $this->nopersist); + $Session->write('FilterPlugin.NoPersist', $this->nopersist); } } - else if (isset($this->nopersist[$controller->name])) + else if (isset($this->nopersist[$controllerName])) { - unset($this->nopersist[$controller->name]); - if ($this->Session->started()) + unset($this->nopersist[$controllerName]); + if ($Session->started()) { - $this->Session->write('FilterPlugin.NoPersist', $this->nopersist); + $Session->write('FilterPlugin.NoPersist', $this->nopersist); } } @@ -386,32 +434,29 @@ private function __updatePersistence($controller, $settings) $actions = array(); } - if (empty($actions) && $controller->name != $nopersistController) + if (empty($actions) && $controllerName != $nopersistController) { - if ($this->Session->check(sprintf('FilterPlugin.Filters.%s', $nopersistController))) + if ($Session->check(sprintf('FilterPlugin.Filters.%s', $nopersistController))) { - $this->Session->delete(sprintf('FilterPlugin.Filters.%s', $nopersistController)); + $Session->delete(sprintf('FilterPlugin.Filters.%s', $nopersistController)); continue; } } - foreach ($actions as $action) + $action = $controller->getRequest()->getParam('action'); + foreach ($actions as $noPersistAction) { - if ($controller->name == $nopersistController && $action == $controller->request->action) + if ($controllerName == $nopersistController && $noPersistAction == $action) { continue; } - if ($this->Session->check(sprintf('FilterPlugin.Filters.%s.%s', $nopersistController, $action))) + if ($Session->check(sprintf('FilterPlugin.Filters.%s.%s', $nopersistController, $noPersistAction))) { - $this->Session->delete(sprintf('FilterPlugin.Filters.%s.%s', $nopersistController, $action)); + $Session->delete(sprintf('FilterPlugin.Filters.%s.%s', $nopersistController, $noPersistAction)); } } } } } - - public function shutdown(Controller $controller) - { - } } diff --git a/src/Model/Behavior/FilteredBehavior.php b/src/Model/Behavior/FilteredBehavior.php new file mode 100644 index 0000000..1135910 --- /dev/null +++ b/src/Model/Behavior/FilteredBehavior.php @@ -0,0 +1,498 @@ + + + Multi-licensed under: + MPL + LGPL + GPL +*/ + +class FilteredBehavior extends Behavior +{ + /** + * Keeps current values after filter form post. + * + * @var mixed[] + */ + protected $_filterValues = array(); + + /** + * 2.x compartible settings (supports having dots in the keys, f.ex. 'Model.id'). + * + * @var mixed[] + */ + public $settings = []; + + /** + * {@inheritDoc} + * + * @param mixed[] $settings The configuration settings provided to this behavior. + * @return void + */ + public function initialize(array $settings) + { + foreach ($settings as $key => $value) + { + if (!is_array($value)) + { + $key = $value; + $value = array(); + } + + $this->settings[$this->getTable()->getAlias()][$key] = array_merge + ( + array + ( + 'type' => 'text', + 'condition' => 'like', + 'required' => false, + 'selectOptions' => array() + ), + $value + ); + } + + $this->_filterValues[$this->getTable()->getAlias()] = array(); + } + + /** + * {@inheritDoc} + * + * Callback method that listens to the `beforeFind` event in the bound + * table. It modifies the passed query by applying search filters. + * + * @param \Cake\Event\Event $event The beforeFind event that was fired. + * @param \Cake\ORM\Query $Query Query. + * @param \ArrayObject $options The options for the query. + * @return void + */ + public function beforeFind(Event $event, Query $Query, ArrayObject $options) + { + if (isset($Query->getOptions()['nofilter']) && $Query->getOptions()['nofilter'] === true) + { + return; + } + $Table = $this->getTable(); + $alias = $Table->getAlias(); + if (method_exists($Table, 'beforeDataFilter')) + { + $callbackOptions['values'] = $this->_filterValues[$alias]; + $callbackOptions['settings'] = $this->settings[$alias]; + + if (!$Table->beforeDataFilter($Query, $callbackOptions)) + { + return; + } + } + + if (!isset($this->settings[$alias])) + { + return; + } + + $settings = $this->settings[$alias]; + $values = $this->_filterValues[$alias]; + + foreach ($settings as $field => $options) + { + $this->addFieldToFilter($Table, $Query, $values, $field, $options); + } + + if (method_exists($Table, 'afterDataFilter')) + { + $callbackOptions['values'] = $this->_filterValues[$alias]; + $callbackOptions['settings'] = $this->settings[$alias]; + + $Table->afterDataFilter($Query, $callbackOptions); + } + } + + /** + * Adds field filters. + * + * @param \Cake\ORM\Table $Table Model table object. + * @param \Cake\ORM\Query $Query Query object. + * @param mixed[] $values Filter values. + * @param string $field Field name. + * @param mixed[] $fieldOptions Field options. + * @return void + */ + protected function addFieldToFilter(Table $Table, Query $Query, $values, $field, $fieldOptions) + { + $configurationModelName = $Table->getAlias(); + $configurationFieldName = $field; + + if (strpos($field, '.') !== false) + { + list($configurationModelName, $configurationFieldName) = explode('.', $field); + } + + if (!isset($values[$configurationModelName][$configurationFieldName]) && isset($fieldOptions['default'])) + { + $values[$configurationModelName][$configurationFieldName] = $fieldOptions['default']; + } + + if ($fieldOptions['required'] && !isset($values[$configurationModelName][$configurationFieldName])) + { + // TODO: implement a bit of a user friendly handling of this scenario.. + trigger_error(sprintf('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 != $Table->getAlias()) + { + if ($Table->hasAssociation($configurationModelName)) { + $relationType = $Table->getAssociation($configurationModelName)->type(); + if ($relationType == Association::MANY_TO_MANY) { + $linkModelName = $Table->{$configurationModelName}->junction()->getAlias(); + } + $filterModelName = 'Filter'.$configurationModelName; + } + } + + if (isset($fieldOptions['filterField'])) + { + if (strpos($fieldOptions['filterField'], '.') !== false) + { + list($filterModelName, $filterFieldName) = explode('.', $fieldOptions['filterField']); + + if ($filterModelName != $Table->getAlias()) + { + $filterModelName = 'Filter'.$filterModelName; + } + } + else + { + $filterModelName = $Table->getAlias(); + $filterFieldName = $fieldOptions['filterField']; + } + } + + $realFilterField = sprintf('%s.%s', $filterModelName, $filterFieldName); + if ($Table->hasAssociation($configurationModelName)) + { + $relatedModel = $Table->{$configurationModelName}->getTarget(); + if (!$this->__isAlreadyJoined($Query, $relatedModel)) + { + $joinStatements = $this->buildFilterJoin($Table, $relatedModel, $linkModelName); + foreach ($joinStatements as $joinStatement) + { + $Query->join($joinStatement); + } + } + } + + $this->buildFilterConditions + ( + $Query, + $realFilterField, + $fieldOptions, + $values[$configurationModelName][$configurationFieldName] + ); + } + + /** + * Checks whether the given query object already contains a given table join. + * + * @param \Cake\ORM\Query $Query Query object. + * @param \Cake\ORM\Table $Table Related model. + * @return boolean + */ + private function __isAlreadyJoined(Query $Query, Table $Table) + { + $relatedModelAlias = 'Filter' . $Table->getAlias(); + $containedAliases = array_keys($Query->getContain()); + $joinAliases = $this->__extractJoinAliases($Query); + return in_array($relatedModelAlias, $containedAliases) || in_array($relatedModelAlias, $joinAliases); + } + + /** + * Extract the JOIN clause aliases from the given query object. + * + * @param \Cake\ORM\Query $Query Query object. + * @return string[] + */ + private function __extractJoinAliases(Query $Query) + { + $aliases = []; + $joins = $Query->clause('join'); + foreach ($joins as $join) { + if (array_key_exists('alias', $join)) { + $aliases[] = $join['alias']; + } + } + return $aliases; + } + + /** + * Build join conditions from Model to relatedModel. + * + * @param \Cake\ORM\Table $Table Model table object. + * @param \Cake\ORM\Table $RelatedTable Related model table object. + * @param string $linkModelName Linked model name (alias) in MANY_TO_MANY association. + * @return mixed[] Cake join array. + */ + protected function buildFilterJoin(Table $Table, Table $RelatedTable, $linkModelName) + { + $conditions = array(); + $alias = $Table->getAlias(); + $primaryKey = $Table->getPrimaryKey(); + $relatedTableAlias = $RelatedTable->getAlias(); + $relatedModelAlias = null; + $relationType = null; + $association = null; + $foreignKey = null; + $associationPrimaryKey = null; + $associationConditions = null; + if (!$Table->hasAssociation($relatedTableAlias)) { + return []; + } + $relatedModelAlias = 'Filter'.$relatedTableAlias; + $association = $Table->getAssociation($relatedTableAlias); + $linkModelAlias = null; + if (!empty($linkModelName) && ($association instanceof BelongsToMany)) + { + $linkModelAlias = $association->junction()->getAlias(); + } + $relationType = $association->type(); + $foreignKey = $association->getForeignKey(); + $associationConditions = $association->getConditions(); + $associationPrimaryKey = $RelatedTable->getPrimaryKey(); + $linkConditions = []; + if (!empty($foreignKey) && is_string($foreignKey)) + { + if ($relationType == Association::MANY_TO_ONE && is_string($associationPrimaryKey)) + { + $conditions[] = sprintf + ( + '%s.%s = %s.%s', + $alias, $foreignKey, + $relatedModelAlias, $associationPrimaryKey + ); + } + else if ( + in_array($relationType, [Association::ONE_TO_MANY, Association::ONE_TO_ONE]) && + is_string($primaryKey) + ) { + $conditions[] = sprintf + ( + '%s.%s = %s.%s', + $alias, $primaryKey, + $relatedModelAlias, $foreignKey + ); + } + else if ( + $relationType == Association::MANY_TO_MANY && + is_string($primaryKey) && + is_string($associationPrimaryKey) + ) { + $associationForeignKey = $RelatedTable->getAssociation($alias)->getForeignKey(); + if (is_string($associationForeignKey)) { + $conditions[] = sprintf + ( + '%s.%s = %s.%s', + $linkModelAlias, $associationForeignKey, + $relatedModelAlias, $associationPrimaryKey + ); + } + + $linkConditions[] = sprintf + ( + '%s.%s = %s.%s', + $alias, $primaryKey, + $linkModelAlias, $foreignKey + ); + } + } + + // merge any custom conditions from the relation, but change + // the alias to our $relatedModelAlias + if (!empty($associationConditions)) + { + $customConditions = $associationConditions; + + if (!is_array($associationConditions)) + { + $customConditions = array($customConditions); + } + $formatAlias = sprintf('#(? $RelatedTable->getTable(), + 'alias' => $relatedModelAlias, + 'type' => 'LEFT', + 'conditions' => $conditions, + ) + ); + + if (!empty($linkModelName) && ($association instanceof BelongsToMany)) + { + $return = array + ( + array + ( + 'table' => $association->junction()->getTable(), + 'alias' => $linkModelAlias, + 'type' => 'LEFT', + 'conditions' => $linkConditions, + ), + array + ( + 'table' => $RelatedTable->getTable(), + 'alias' => $relatedModelAlias, + 'type' => 'LEFT', + 'conditions' => $conditions, + ) + ); + } + return $return; + } + + /** + * Build query conditions and add them to $Query. + * + * @param \Cake\ORM\Query $Query Cake query array. + * @param string $field Filter field. + * @param mixed[] $options Configuration options for this field. + * @param mixed $value Field value. + * @return void + */ + protected function buildFilterConditions(Query $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 'select': + if (is_string($value) && strlen(trim(strval($value))) == 0) + { + break; + } + if (is_array($value)) { + $Query->andWhere([$field . ' IN' => $value]); + } else { + $Query->andWhere([$field => $value]); + } + break; + case 'checkbox': + if (is_array($value)) { + $Query->andWhere([$field . ' IN' => $value]); + } else { + $Query->andWhere([$field => $value]); + } + break; + default: + 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->andWhere([$formattedField => $formattedValue]); + break; + default: + { + $Query->andWhere([$field.' '.$condition => $value]); + } + break; + } + + break; + } + } + + /** + * Sets filter values. + * + * @param mixed[] $values Filter values. + * @return void + */ + public function setFilterValues($values = array()) + { + $alias = $this->getTable()->getAlias(); + $this->_filterValues[$alias] = array_merge($this->_filterValues[$alias], (array)$values); + } + + /** + * Gets filter values. + * + * @return mixed[] + */ + public function getFilterValues() + { + return $this->_filterValues; + } + +} diff --git a/Test/Fixture/DocumentCategoryFixture.php b/tests/Fixture/DocumentCategoriesFixture.php similarity index 76% rename from Test/Fixture/DocumentCategoryFixture.php rename to tests/Fixture/DocumentCategoriesFixture.php index b667357..3ffc2fc 100644 --- a/Test/Fixture/DocumentCategoryFixture.php +++ b/tests/Fixture/DocumentCategoriesFixture.php @@ -1,4 +1,9 @@ */ -class DocumentCategoryFixture extends CakeTestFixture +class DocumentCategoriesFixture extends TestFixture { public $name = 'DocumentCategory'; @@ -20,9 +25,12 @@ class DocumentCategoryFixture extends CakeTestFixture */ public $fields = array ( - 'id' => array('type' => 'integer', 'key' => 'primary'), + 'id' => array('type' => 'integer'), 'title' => array('type' => 'string', 'length' => 100, 'null' => false), - 'description' => array('type' => 'string', 'length' => 255) + 'description' => array('type' => 'string', 'length' => 255), + '_constraints' => [ + 'primary' => ['type' => 'primary', 'columns' => ['id']], + ], ); /** diff --git a/Test/Fixture/DocumentFixture.php b/tests/Fixture/DocumentsFixture.php similarity index 87% rename from Test/Fixture/DocumentFixture.php rename to tests/Fixture/DocumentsFixture.php index 0332e8c..e43e6c9 100644 --- a/Test/Fixture/DocumentFixture.php +++ b/tests/Fixture/DocumentsFixture.php @@ -1,4 +1,9 @@ */ -class DocumentFixture extends CakeTestFixture +class DocumentsFixture extends TestFixture { public $name = 'Document'; @@ -20,13 +25,16 @@ class DocumentFixture extends CakeTestFixture */ public $fields = array ( - 'id' => array('type' => 'integer', 'key' => 'primary'), + 'id' => array('type' => 'integer'), '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) + 'updated' => array('type' => 'datetime', 'null' => true), + '_constraints' => [ + 'primary' => ['type' => 'primary', 'columns' => ['id']], + ], ); /** diff --git a/Test/Fixture/ItemFixture.php b/tests/Fixture/ItemsFixture.php similarity index 82% rename from Test/Fixture/ItemFixture.php rename to tests/Fixture/ItemsFixture.php index c7f3424..d5df23f 100644 --- a/Test/Fixture/ItemFixture.php +++ b/tests/Fixture/ItemsFixture.php @@ -1,4 +1,9 @@ */ -class ItemFixture extends CakeTestFixture +class ItemsFixture extends TestFixture { public $name = 'Item'; @@ -20,9 +25,12 @@ class ItemFixture extends CakeTestFixture */ public $fields = array ( - 'id' => array('type' => 'integer', 'key' => 'primary'), + 'id' => array('type' => 'integer'), 'document_id' => array('type' => 'integer', 'null' => false), - 'code' => array('type' => 'string', 'length' => '20', 'null' => false) + 'code' => array('type' => 'string', 'length' => '20', 'null' => false), + '_constraints' => [ + 'primary' => ['type' => 'primary', 'columns' => ['id']], + ], ); /** diff --git a/Test/Fixture/MetadataFixture.php b/tests/Fixture/MetadataFixture.php similarity index 83% rename from Test/Fixture/MetadataFixture.php rename to tests/Fixture/MetadataFixture.php index 4178a22..d967ed1 100644 --- a/Test/Fixture/MetadataFixture.php +++ b/tests/Fixture/MetadataFixture.php @@ -1,4 +1,9 @@ */ -class MetadataFixture extends CakeTestFixture +class MetadataFixture extends TestFixture { public $name = 'Metadata'; @@ -20,11 +25,14 @@ class MetadataFixture extends CakeTestFixture */ public $fields = array ( - 'id' => array('type' => 'integer', 'key' => 'primary'), + 'id' => array('type' => 'integer'), '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), + '_constraints' => [ + 'primary' => ['type' => 'primary', 'columns' => ['id']], + ], ); /** diff --git a/Test/Case/Controller/Component/FilterComponentTest.php b/tests/TestCase/Controller/Component/FilterComponentTest.php similarity index 58% rename from Test/Case/Controller/Component/FilterComponentTest.php rename to tests/TestCase/Controller/Component/FilterComponentTest.php index e4e9901..4527c11 100644 --- a/Test/Case/Controller/Component/FilterComponentTest.php +++ b/tests/TestCase/Controller/Component/FilterComponentTest.php @@ -1,4 +1,13 @@ */ -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'); - -class FilterComponentTest extends CakeTestCase +class FilterComponentTest extends TestCase { /** * @var string[] */ public $fixtures = array ( - 'plugin.filter.document_category', - 'plugin.filter.document', - 'plugin.filter.item', - 'plugin.filter.metadata', + 'plugin.Filter.DocumentCategories', + 'plugin.Filter.Documents', + 'plugin.Filter.Items', + 'plugin.Filter.Metadata', ); /** - * @var \DocumentTestsController + * @var \Filter\Test\TestCase\MockObjects\DocumentTestsController */ public $Controller = null; - public function startTest($method) + public function setUp() { - Router::connect('/', array('controller' => 'document_tests', 'action' => 'index')); - $request = new CakeRequest('/'); - $request->addParams(Router::parse('/')); + parent::setUp(); + $request = new ServerRequest([ + 'params' => [ + 'controller' => 'DocumentTests', + 'action' => 'index', + ], + ]); $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) + public function tearDown() { - $this->Controller->Session->destroy(); + parent::tearDown(); + $this->Controller->getRequest()->getSession()->destroy(); unset($this->Controller); } @@ -82,37 +64,14 @@ public function endTest($method) */ 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->assertFalse($this->Controller->Document->hasBehavior('Filtered')); - $this->Controller->Components->trigger('startup', array($this->Controller)); - $this->assertFalse(in_array('Filter.Filter', $this->Controller->helpers)); + $this->assertFalse(in_array('Filter.Filter', $this->Controller->viewBuilder()->getHelpers())); } - /** - * Test bailing out when a filter model can't be found - * or when the current action has no filters. - * - * @return void - */ - public function testNoModelPresentOrNoActionFilters() + public function testNoActionFilters() { - $testSettings = array - ( - 'index' => array - ( - 'DocumentArse' => array - ( - 'DocumentFeck.drink' => array('type' => 'irrelevant') - ) - ) - ); - - $this->expectException('PHPUnit_Framework_Error_Notice'); - $this->Controller->filters = $testSettings; - $this->Controller->Components->trigger('initialize', array($this->Controller)); - $testSettings = array ( 'someotheraction' => array @@ -124,10 +83,9 @@ public function testNoModelPresentOrNoActionFilters() ) ); - $this->Controller->filters = $testSettings; - $this->Controller->Components->trigger('initialize', array($this->Controller)); - $this->assertFalse($this->Controller->Document->Behaviors->enabled('Filtered')); + $this->Controller->dispatchEvent('Controller.initialize'); + $this->assertFalse($this->Controller->Document->hasBehavior('Filtered')); $testSettings = array ( @@ -141,8 +99,8 @@ public function testNoModelPresentOrNoActionFilters() ); $this->Controller->filters = $testSettings; - $this->Controller->Components->trigger('initialize', array($this->Controller)); - $this->assertTrue($this->Controller->Document->Behaviors->enabled('Filtered')); + $this->Controller->dispatchEvent('Controller.initialize'); + $this->assertTrue($this->Controller->Document->hasBehavior('Filtered')); } /** @@ -166,10 +124,9 @@ public function testBasicFilters() $expected = array ( - $this->Controller->name => $testSettings + $this->Controller->getName() => $testSettings ); - - $this->Controller->Components->trigger('initialize', array($this->Controller)); + $this->Controller->dispatchEvent('Controller.initialize'); $this->assertEquals($expected, $this->Controller->Filter->settings); } @@ -192,9 +149,33 @@ public function testEmptyStartup() ); $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)); + $this->Controller->dispatchEvent('Controller.initialize'); + $this->Controller->dispatchEvent('Controller.startup'); + $this->assertTrue(in_array('Filter.Filter', $this->Controller->viewBuilder()->getHelpers())); + } + + public function testSessionStartupDataFakeNonexistantModel() + { + $testSettings = array + ( + 'index' => array + ( + 'FakeNonexistant' => array + ( + 'drink' => array('type' => 'select') + ) + ) + ); + $this->Controller->filters = $testSettings; + $sessionKey = sprintf( + 'FilterPlugin.Filters.%s.%s', + $this->Controller->getName(), + $this->Controller->getRequest()->getParam('action'), + ); + $filterValues = array(); + $this->Controller->getRequest()->getSession()->write($sessionKey, $filterValues); + $this->expectException('PHPUnit\Framework\Error\Notice'); + $this->Controller->dispatchEvent('Controller.initialize'); } /** @@ -212,42 +193,40 @@ public function testSessionStartupData() ( '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); + $sessionKey = sprintf( + 'FilterPlugin.Filters.%s.%s', + $this->Controller->getName(), + $this->Controller->getRequest()->getParam('action'), + ); $filterValues = array(); - $this->Controller->Session->write($sessionKey, $filterValues); - $this->expectException('PHPUnit_Framework_Error_Notice'); - $this->Controller->Components->trigger('initialize', array($this->Controller)); + $this->Controller->getRequest()->getSession()->write($sessionKey, $filterValues); + $this->Controller->dispatchEvent('Controller.initialize'); - $this->expectException('PHPUnit_Framework_Error_Notice'); - $this->Controller->Components->trigger('startup', array($this->Controller)); + $this->Controller->dispatchEvent('Controller.startup'); $actualFilterValues = $this->Controller->Document->getFilterValues(); $this->assertEquals ( $filterValues, - $actualFilterValues[$this->Controller->Document->alias] + $actualFilterValues[$this->Controller->Document->getAlias()] ); $filterValues = array('Document' => array('title' => 'in')); - $this->Controller->Session->write($sessionKey, $filterValues); + $this->Controller->getRequest()->getSession()->write($sessionKey, $filterValues); - $this->Controller->Components->trigger('startup', array($this->Controller)); + $this->Controller->dispatchEvent('Controller.startup'); $actualFilterValues = $this->Controller->Document->getFilterValues(); $this->assertEquals ( $filterValues, - $actualFilterValues[$this->Controller->Document->alias] + $actualFilterValues[$this->Controller->Document->getAlias()] ); - $this->Controller->Session->delete($sessionKey); + $this->Controller->getRequest()->getSession()->delete($sessionKey); } /** @@ -257,8 +236,16 @@ public function testSessionStartupData() */ public function testPostStartupData() { - $_SERVER['REQUEST_METHOD'] = 'POST'; - + $request = new ServerRequest([ + 'params' => [ + 'controller' => 'DocumentTests', + 'action' => 'index', + ], + 'environment' => [ + 'REQUEST_METHOD' => 'POST', + ], + ]); + $this->Controller = new DocumentTestsController($request); $testSettings = array ( 'index' => array @@ -273,20 +260,24 @@ public function testPostStartupData() $this->Controller->filters = $testSettings; $filterValues = array('Document' => array('title' => 'in'), 'Filter' => array('filterFormId' => 'Document')); - $this->Controller->data = $filterValues; + $this->Controller->request = $this->Controller->getRequest()->withParsedBody($filterValues); - $this->Controller->Components->trigger('initialize', array($this->Controller)); - $this->Controller->Components->trigger('startup', array($this->Controller)); + $this->Controller->dispatchEvent('Controller.initialize'); + $this->Controller->dispatchEvent('Controller.startup'); - $sessionKey = sprintf('FilterPlugin.Filters.%s.%s', $this->Controller->name, $this->Controller->action); - $sessionData = $this->Controller->Session->read($sessionKey); + $sessionKey = sprintf( + 'FilterPlugin.Filters.%s.%s', + $this->Controller->getName(), + $this->Controller->getRequest()->getParam('action'), + ); + $sessionData = $this->Controller->getRequest()->getSession()->read($sessionKey); $this->assertEquals($filterValues, $sessionData); $actualFilterValues = $this->Controller->Document->getFilterValues(); $this->assertEquals ( $filterValues, - $actualFilterValues[$this->Controller->Document->alias] + $actualFilterValues[$this->Controller->Document->getAlias()] ); } @@ -309,9 +300,9 @@ public function testBeforeRenderAbort() ); $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->Controller->dispatchEvent('Controller.initialize'); + $this->Controller->dispatchEvent('Controller.startup'); + $this->Controller->dispatchEvent('Controller.beforeRender'); $this->assertFalse(isset($this->Controller->viewVars['viewFilterParams'])); } @@ -335,15 +326,8 @@ public function testNoModelFound() ) ); $this->Controller->filters = $testSettings; - - $this->expectException('PHPUnit_Framework_Error_Notice'); - $this->Controller->Components->trigger('initialize', array($this->Controller)); - - //$this->expectError(); - $this->Controller->Components->trigger('startup', array($this->Controller)); - - $this->expectException('PHPUnit_Framework_Error_Notice'); - $this->Controller->Components->trigger('beforeRender', array($this->Controller)); + $this->expectException('PHPUnit\Framework\Error\Notice'); + $this->Controller->dispatchEvent('Controller.initialize'); } /** @@ -361,15 +345,19 @@ public function testBasicViewInfo() 'Document' => array ( 'title', - 'DocumentCategory.id' => array('type' => 'select', 'label' => 'Category'), + 'DocumentCategory.id' => array( + 'type' => 'select', + 'label' => 'Category', + 'className' => DocumentCategoriesTable::class, + ), ) ) ); $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->Controller->dispatchEvent('Controller.initialize'); + $this->Controller->dispatchEvent('Controller.startup'); + $this->Controller->dispatchEvent('Controller.beforeRender'); $expected = array ( @@ -416,16 +404,17 @@ public function testAdditionalInputOptions() ( 'type' => 'select', 'label' => 'Category', - 'inputOptions' => array('class' => 'important') + 'inputOptions' => array('class' => 'important'), + 'className' => DocumentCategoriesTable::class, ), ) ) ); $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->Controller->dispatchEvent('Controller.initialize'); + $this->Controller->dispatchEvent('Controller.startup'); + $this->Controller->dispatchEvent('Controller.beforeRender'); $expected = array ( @@ -481,16 +470,19 @@ public function testCustomSelector() 'type' => 'select', 'label' => 'Category', 'selector' => 'customSelector', - 'selectOptions' => array('conditions' => array('DocumentCategory.description LIKE' => '%!%')), + 'selectOptions' => array( + 'conditions' => array('DocumentCategory.description LIKE' => '%!%'), + ), + 'className' => DocumentCategoriesTable::class, ), ) ) ); $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->Controller->dispatchEvent('Controller.initialize'); + $this->Controller->dispatchEvent('Controller.startup'); + $this->Controller->dispatchEvent('Controller.beforeRender'); $expected = array ( @@ -538,9 +530,9 @@ public function testCheckboxOptions() ); $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->Controller->dispatchEvent('Controller.initialize'); + $this->Controller->dispatchEvent('Controller.startup'); + $this->Controller->dispatchEvent('Controller.beforeRender'); $expected = array ( @@ -584,10 +576,10 @@ public function testSelectMultiple() $expected = array ( - $this->Controller->name => $testSettings + $this->Controller->getName() => $testSettings ); - $this->Controller->Components->trigger('initialize', array($this->Controller)); + $this->Controller->dispatchEvent('Controller.initialize'); $this->assertEquals($expected, $this->Controller->Filter->settings); } @@ -607,15 +599,16 @@ public function testSelectInputFromSameModel() 'Document.title' => array ( 'type' => 'select', + 'className' => DocumentsTable::class, ), ) ) ); $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->Controller->dispatchEvent('Controller.initialize'); + $this->Controller->dispatchEvent('Controller.startup'); + $this->Controller->dispatchEvent('Controller.beforeRender'); $expected = array ( @@ -658,27 +651,41 @@ public function testPersistence() ( 'Document.title' => array('type' => 'text') ), - ) + ), ); $this->Controller->filters = $testSettings; + $this->Controller->components()->unload('Filter'); + $this->Controller->loadComponent('Filter.Filter', ['nopersist' => true]); - $sessionKey = sprintf('FilterPlugin.Filters.%s.%s', 'SomeOtherController', $this->Controller->action); + $sessionKey = sprintf( + 'FilterPlugin.Filters.%s.%s', + 'SomeOtherController', + $this->Controller->getRequest()->getParam('action'), + ); $filterValues = array('Document' => array('title' => 'in'), 'Filter' => array('filterFormId' => 'Document')); - $this->Controller->Session->write($sessionKey, $filterValues); + $this->Controller->getRequest()->getSession()->write($sessionKey, $filterValues); - $sessionKey = sprintf('FilterPlugin.Filters.%s.%s', $this->Controller->name, $this->Controller->action); + $sessionKey = sprintf( + 'FilterPlugin.Filters.%s.%s', + $this->Controller->getName(), + $this->Controller->getRequest()->getParam('action'), + ); $filterValues = array('Document' => array('title' => 'in'), 'Filter' => array('filterFormId' => 'Document')); - $this->Controller->Session->write($sessionKey, $filterValues); + $this->Controller->getRequest()->getSession()->write($sessionKey, $filterValues); $this->Controller->Filter->nopersist = array(); - $this->Controller->Filter->nopersist[$this->Controller->name] = true; + $this->Controller->Filter->nopersist[$this->Controller->getName()] = true; $this->Controller->Filter->nopersist['SomeOtherController'] = true; - $this->Controller->Components->trigger('initialize', array($this->Controller)); - $this->Controller->Components->trigger('startup', array($this->Controller)); + $this->Controller->dispatchEvent('Controller.initialize'); + $this->Controller->dispatchEvent('Controller.startup'); - $expected = array($this->Controller->name => array($this->Controller->action => $filterValues)); - $this->assertEquals($expected, $this->Controller->Session->read('FilterPlugin.Filters')); + $expected = array( + $this->Controller->getName() => array( + $this->Controller->getRequest()->getParam('action') => $filterValues, + ), + ); + $this->assertEquals($expected, $this->Controller->getRequest()->getSession()->read('FilterPlugin.Filters')); } /** @@ -701,9 +708,9 @@ public function testBelongsToFilteringByText() ); $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->Controller->dispatchEvent('Controller.initialize'); + $this->Controller->dispatchEvent('Controller.startup'); + $this->Controller->dispatchEvent('Controller.beforeRender'); $expected = array ( diff --git a/tests/TestCase/MockObjects/DocumentCategoriesTable.php b/tests/TestCase/MockObjects/DocumentCategoriesTable.php new file mode 100644 index 0000000..5e71470 --- /dev/null +++ b/tests/TestCase/MockObjects/DocumentCategoriesTable.php @@ -0,0 +1,37 @@ +hasMany('Documents'); + } + + /** + * @param mixed[] $options + * @return mixed[]|int|null + */ + public function customSelector($options = array()) + { + $options['nofilter'] = true; + return $this->find('list', $options) + ->where([ + 'DocumentCategory.title LIKE' => '%T%', + ]) + ->toArray(); + } +} diff --git a/Test/Case/MockObjects/DocumentTestsController.php b/tests/TestCase/MockObjects/DocumentTestsController.php similarity index 50% rename from Test/Case/MockObjects/DocumentTestsController.php rename to tests/TestCase/MockObjects/DocumentTestsController.php index ab122c0..8f53e55 100644 --- a/Test/Case/MockObjects/DocumentTestsController.php +++ b/tests/TestCase/MockObjects/DocumentTestsController.php @@ -1,20 +1,34 @@ Document = $this->getTableLocator()->get('Documents', [ + 'className' => DocumentsTable::class, + ]); + $this->loadComponent('Filter.Filter'); + } + /** * @return void */ @@ -30,7 +44,7 @@ public function index() * @param string|mixed[] $url * @param int|mixed[]|null|string $status * @param bool $exit - * @return \CakeResponse|null + * @return \Cake\Http\Response|null */ public function redirect($url, $status = null, $exit = true) { diff --git a/tests/TestCase/MockObjects/Documents2Table.php b/tests/TestCase/MockObjects/Documents2Table.php new file mode 100644 index 0000000..9d9489a --- /dev/null +++ b/tests/TestCase/MockObjects/Documents2Table.php @@ -0,0 +1,42 @@ +setAlias('Document'); + $this->setTable('documents'); + $this->belongsTo('DocumentCategories'); + $this->hasMany('Items'); + } + + /** + * @var bool + */ + public $returnValue = false; + + /** + * @param \Cake\ORM\Query $query Query. + * @param mixed[] $options + * @return mixed[]|bool + */ + public function beforeDataFilter($query, $options) + { + return $this->returnValue; + } +} diff --git a/tests/TestCase/MockObjects/Documents3Table.php b/tests/TestCase/MockObjects/Documents3Table.php new file mode 100644 index 0000000..9276acf --- /dev/null +++ b/tests/TestCase/MockObjects/Documents3Table.php @@ -0,0 +1,54 @@ +setAlias('Document'); + $this->setTable('documents'); + $this->belongsTo('DocumentCategories'); + $this->hasMany('Items'); + } + + /** + * @var string|null + */ + public $itemToUnset = null; + + /** + * @param \Cake\ORM\Query $query Query. + * @param mixed[] $options + * @return mixed[] + */ + public function afterDataFilter($query, $options) + { + if (!is_string($this->itemToUnset)) + { + return $query; + } + $query->clause('where')->iterateParts(function ($Comparison) { + /** @var \Cake\Database\Expression\Comparison $Comparison */ + $field = $Comparison->getField(); + if ($field == $this->itemToUnset) { + return null; + } + return $Comparison; + }); + return $query; + } +} diff --git a/tests/TestCase/MockObjects/DocumentsTable.php b/tests/TestCase/MockObjects/DocumentsTable.php new file mode 100644 index 0000000..3628ee8 --- /dev/null +++ b/tests/TestCase/MockObjects/DocumentsTable.php @@ -0,0 +1,27 @@ +belongsTo('DocumentCategories'); + $this->hasMany('Items'); + $this->hasOne('Metadata'); + } +} diff --git a/tests/TestCase/MockObjects/ItemsTable.php b/tests/TestCase/MockObjects/ItemsTable.php new file mode 100644 index 0000000..6bc8b66 --- /dev/null +++ b/tests/TestCase/MockObjects/ItemsTable.php @@ -0,0 +1,21 @@ +belongsTo('Documents'); + } +} diff --git a/tests/TestCase/MockObjects/MetadataTable.php b/tests/TestCase/MockObjects/MetadataTable.php new file mode 100644 index 0000000..5d8c016 --- /dev/null +++ b/tests/TestCase/MockObjects/MetadataTable.php @@ -0,0 +1,18 @@ +belongsTo('Documents'); + } +} diff --git a/tests/TestCase/Model/Behavior/FilteredBehaviorTest.php b/tests/TestCase/Model/Behavior/FilteredBehaviorTest.php new file mode 100644 index 0000000..edf18ce --- /dev/null +++ b/tests/TestCase/Model/Behavior/FilteredBehaviorTest.php @@ -0,0 +1,975 @@ + + + Multi-licensed under: + MPL + LGPL + GPL +*/ + +class FilteredBehaviorTest extends TestCase +{ + /** + * @var string[] + */ + public $fixtures = array + ( + 'plugin.Filter.DocumentCategories', + 'plugin.Filter.Documents', + 'plugin.Filter.Items', + 'plugin.Filter.Metadata', + ); + + /** + * @var \Filter\Test\TestCase\MockObjects\DocumentsTable|\Filter\Test\TestCase\MockObjects\Documents2Table|\Filter\Test\TestCase\MockObjects\Documents3Table + */ + public $Document = null; + + public function setUp() + { + $Document = $this->getTableLocator()->get('Documents', ['className' => DocumentsTable::class]); + $this->assertInstanceOf(DocumentsTable::class, $Document); + $this->Document = $Document; + } + + public function tearDown() + { + unset($this->Document); + } + + /** + * Detach and re-attach the behavior to reset the options. + * + * @param mixed[] $options Behavior options. + * @return void + */ + protected function _reattachBehavior($options = array()) + { + if ($this->Document->hasBehavior('Filtered')) { + $this->Document->removeBehavior('Filtered'); + } + $this->Document->addBehavior('Filter.Filtered', $options); + } + + /** + * Test attaching without options. + * + * @return void + */ + public function testBlankAttaching() + { + $this->Document->addBehavior('Filter.Filtered'); + $this->assertTrue($this->Document->hasBehavior('Filtered')); + } + + /** + * Test attaching with options. + * + * @return void + */ + public function testInitSettings() + { + $testOptions = array + ( + 'Documents.title' => array('type' => 'text', 'condition' => 'like'), + 'DocumentCategories.id' => array('type' => 'select', 'filterField' => 'document_category_id'), + 'Documents.is_private' => array('type' => 'checkbox', 'label' => 'Private?') + ); + $this->_reattachBehavior($testOptions); + + $expected = array + ( + 'Documents.title' => array('type' => 'text', 'condition' => 'like', 'required' => false, 'selectOptions' => array()), + 'DocumentCategories.id' => array('type' => 'select', 'filterField' => 'document_category_id', 'condition' => 'like', 'required' => false, 'selectOptions' => array()), + 'Documents.is_private' => array('type' => 'checkbox', 'label' => 'Private?', 'condition' => 'like', 'required' => false, 'selectOptions' => array()) + ); + $Filtered = $this->Document->getBehavior('Filtered'); + $this->assertInstanceOf(FilteredBehavior::class, $Filtered); + $this->assertEquals($expected, $Filtered->settings[$this->Document->getAlias()]); + } + + /** + * Test init settings when only a single field is given, with no extra options. + * + * @return void + */ + public function testInitSettingsSingle() + { + $testOptions = array('Documents.title'); + $this->_reattachBehavior($testOptions); + + $expected = array + ( + 'Documents.title' => array('type' => 'text', 'condition' => 'like', 'required' => false, 'selectOptions' => array()), + ); + $Filtered = $this->Document->getBehavior('Filtered'); + $this->assertInstanceOf(FilteredBehavior::class, $Filtered); + $this->assertEquals($expected, $Filtered->settings[$this->Document->getAlias()]); + } + + /** + * Test setting the filter values for future queries. + * + * @return void + */ + public function testSetFilterValues() + { + $testOptions = array + ( + 'Documents.title' => array('type' => 'text', 'condition' => 'like', 'required' => true), + 'DocumentCategories.id' => array('type' => 'select', 'filterField' => 'document_category_id'), + 'Documents.is_private' => array('type' => 'checkbox', 'label' => 'Private?') + ); + + $this->_reattachBehavior($testOptions); + + $filterValues = array + ( + 'Documents' => array('title' => 'in', 'is_private' => 0), + 'DocumentCategories' => array('id' => 1) + ); + + $this->Document->setFilterValues($filterValues); + $actualFilterValues = $this->Document->getFilterValues(); + $this->assertEquals($filterValues, $actualFilterValues[$this->Document->getAlias()]); + } + + /** + * Test detecting an error in options - when a field is 'required' but no value is given for it. + * + * @return void + */ + public function testLoadingRequiredFieldValueMissing() + { + $testOptions = array + ( + 'Documents.title' => array('type' => 'text', 'condition' => 'like', 'required' => true), + 'DocumentCategories.id' => array('type' => 'select', 'filterField' => 'document_category_id'), + 'Documents.is_private' => array('type' => 'checkbox', 'label' => 'Private?') + ); + $this->_reattachBehavior($testOptions); + + $filterValues = array + ( + 'Documents' => array('is_private' => 0), + 'DocumentCategories' => array('id' => 1) + ); + $this->Document->setFilterValues($filterValues); + + $this->expectException('PHPUnit\Framework\Error\Notice'); + $this->Document->find()->first(); + } + + /** + * Test filtering with conditions from current model and belongsTo model. + * + * @return void + */ + public function testFilteringBelongsTo() + { + $testOptions = array + ( + 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), + 'DocumentCategories.id' => array('type' => 'select') + ); + $this->_reattachBehavior($testOptions); + + $filterValues = array + ( + 'Documents' => array('title' => 'in'), + 'DocumentCategories' => array('id' => 1) + ); + $this->Document->setFilterValues($filterValues); + + $expected = array + ( + array('id' => 1, 'title' => 'Testing Doc', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0), + array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0) + ); + + $result = $this->Document->find() + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + } + + /** + * @return void + */ + public function testFilteringBelongsToTextField() + { + $testOptions = array + ( + 'DocumentCategories.title' => array('type' => 'text') + ); + $this->_reattachBehavior($testOptions); + + $filterValues = array + ( + 'DocumentCategories' => array('title' => 'spec') + ); + $this->Document->setFilterValues($filterValues); + + $expected = array + ( + array('id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0) + ); + + $result = $this->Document->find() + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->contain(['DocumentCategories']) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + } + + /** + * Test filtering with conditions from current model and belongsTo model, + * same as testFilteringBelongsTo() except for a change in filterField format. + * + * @return void + */ + public function testFilteringBelongsToFilterFieldTest() + { + $testOptions = array + ( + 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), + 'DocumentCategories.id' => array('type' => 'select', 'filterField' => 'Documents.document_category_id') + ); + $this->_reattachBehavior($testOptions); + + $filterValues = array + ( + 'Documents' => array('title' => 'in'), + 'DocumentCategories' => array('id' => 1) + ); + $this->Document->setFilterValues($filterValues); + + $expected = array + ( + array('id' => 1, 'title' => 'Testing Doc', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0), + array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0) + ); + + $result = $this->Document->find() + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->contain(['DocumentCategories']) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + } + + /** + * Test various conditions for the type 'text' in filtering (less than, equal, like, etc..) + * + * @return void + */ + public function testFilteringBelongsToDifferentConditions() + { + $testOptions = array + ( + 'title' => array('type' => 'text', 'condition' => '='), + 'DocumentCategories.id' => array('type' => 'select') + ); + $this->_reattachBehavior($testOptions); + + $filterValues = array + ( + 'Documents' => array('title' => 'Illegal explosives DIY'), + 'DocumentCategories' => array('id' => '') + ); + $this->Document->setFilterValues($filterValues); + + $expected = array + ( + array('id' => 4, 'title' => 'Illegal explosives DIY', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 1), + ); + + $result = $this->Document->find() + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->contain(['DocumentCategories']) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + + $testOptions = array + ( + 'id' => array('type' => 'text', 'condition' => '>='), + 'created' => array('type' => 'text', 'condition' => '<=') + ); + $this->_reattachBehavior($testOptions); + + $filterValues = array + ( + 'Documents' => array('id' => 3, 'created' => '2010-03-01') + ); + $this->Document->setFilterValues($filterValues); + + $expected = array + ( + array('id' => 4, 'title' => 'Illegal explosives DIY', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 1), + array('id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0), + array('id' => 6, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0), + array('id' => 7, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0), + ); + + $result = $this->Document->find() + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->contain(['DocumentCategories']) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + } + + /** + * Test filtering with conditions on current model, the belongsTo model + * and hasMany model (behavior adds an INNER JOIN in query). + * + * @return void + */ + public function testFilteringBelongsToAndHasMany() + { + $testOptions = array + ( + 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), + 'DocumentCategories.id' => array('type' => 'select'), + 'Documents.is_private' => array('type' => 'checkbox', 'label' => 'Private?'), + 'Items.code' => array('type' => 'text'), + ); + $this->_reattachBehavior($testOptions); + + $filterValues = array + ( + 'Documents' => array('title' => 'in', 'is_private' => 0), + 'DocumentCategories' => array('id' => 1), + 'Items' => array('code' => '04') + ); + $this->Document->setFilterValues($filterValues); + + $expected = array + ( + array + ( + 'id' => 2, + 'title' => 'Imaginary Spec', + 'document_category_id' => 1, + 'owner_id' => 1, + 'is_private' => 0, + 'document_category' => 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-------'), + 'items' => 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() + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->contain([ + 'DocumentCategories' => [ + 'fields' => ['id', 'title', 'description'], + ], + 'Metadata' => [ + 'fields' => ['id', 'document_id', 'weight', 'size', 'permissions'], + ], + 'Items' => [ + 'fields' => ['id', 'document_id', 'code'], + ], + ]) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + + $expected = array + ( + array + ( + 'id' => 2, + 'title' => 'Imaginary Spec', + 'document_category_id' => 1, + 'owner_id' => 1, + 'is_private' => 0, + 'document_category' => 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() + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->contain([ + 'DocumentCategories' => [ + 'fields' => ['id', 'title', 'description'], + ], + 'Metadata' => [ + 'fields' => ['id', 'document_id', 'weight', 'size', 'permissions'], + ], + ]) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + + $this->Document->associations()->remove('Item'); + $this->Document->hasMany('Item'); + + $result = $this->Document->find() + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->contain([ + 'DocumentCategories' => [ + 'fields' => ['id', 'title', 'description'], + ], + 'Metadata' => [ + 'fields' => ['id', 'document_id', 'weight', 'size', 'permissions'], + ], + ]) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + + $expected = array + ( + array + ( + 'id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, + ) + ); + + $result = $this->Document->find() + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + } + + /** + * Test filtering with join which has some custom + * condition in the relation (both string and array). + * + * @return void + */ + 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 + ( + 'id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0, + 'metadata' => array('id' => 5, 'document_id' => 5, 'weight' => 4, 'size' => 790, 'permissions' => 'rw-rw-r--'), + ) + ); + + $oldConditions = $this->Document->associations()->get('Metadata')->getConditions(); + $this->Document->associations()->get('Metadata')->setConditions(['Metadata.size > 500']); + + $result = $this->Document->find() + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->contain([ + 'Metadata' => [ + 'fields' => ['id', 'document_id', 'weight', 'size', 'permissions'], + ], + ]) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + + $this->Document->associations()->get('Metadata')->setConditions(['Metadata.size > 500']); + $result = $this->Document->find() + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->contain([ + 'Metadata' => [ + 'fields' => ['id', 'document_id', 'weight', 'size', 'permissions'], + ], + ]) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + + $this->Document->associations()->get('Metadata')->setConditions($oldConditions); + } + + /** + * Test for any possible conflicts with Containable behavior. + * + * @return void + */ + public function testFilteringBelongsToAndHasManyWithContainable() + { + $testOptions = array + ( + 'Documents.title' => array('type' => 'text', 'condition' => 'like', 'required' => true), + 'DocumentCategories.id' => array('type' => 'select'), + 'Documents.is_private' => array('type' => 'checkbox', 'label' => 'Private?'), + 'Items.code' => array('type' => 'text'), + ); + + $this->_reattachBehavior($testOptions); + + $filterValues = array + ( + 'Documents' => array('title' => 'in', 'is_private' => 0), + 'DocumentCategories' => array('id' => 1), + 'Items' => array('code' => '04') + ); + $this->Document->setFilterValues($filterValues); + + $expected = array + ( + array + ( + 'id' => 2, + 'title' => 'Imaginary Spec', + 'document_category_id' => 1, + 'owner_id' => 1, + 'is_private' => 0, + 'document_category' => array('id' => 1, 'title' => 'Testing Doc', 'description' => 'It\'s a bleeding test doc!'), + 'items' => 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() + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->contain([ + 'DocumentCategories' => [ + 'fields' => ['id', 'title', 'description'], + ], + 'Items' => [ + 'fields' => ['id', 'document_id', 'code'], + ], + ]) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + + $expected = array + ( + array + ( + 'id' => 2, + 'title' => 'Imaginary Spec', + 'document_category_id' => 1, + 'owner_id' => 1, + 'is_private' => 0, + 'document_category' => array('id' => 1, 'title' => 'Testing Doc', 'description' => 'It\'s a bleeding test doc!'), + ) + ); + + $result = $this->Document->find() + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->contain([ + 'DocumentCategories' => [ + 'fields' => ['id', 'title', 'description'], + ], + ]) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + + $expected = array + ( + array + ( + 'id' => 2, + 'title' => 'Imaginary Spec', + 'document_category_id' => 1, + 'owner_id' => 1, + 'is_private' => 0, + ) + ); + + $result = $this->Document->find() + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + } + + /** + * Test filtering by text input with hasOne relation. + * + * @return void + */ + public function testHasOneAndHasManyWithTextSearch() + { + $testOptions = array + ( + 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), + 'Items.code' => array('type' => 'text'), + 'Metadata.size' => array('type' => 'text', 'condition' => '='), + ); + + $filterValues = array + ( + 'Documents' => array('title' => 'in'), + 'Items' => array('code' => '04'), + 'Metadata' => array('size' => 45), + ); + + $expected = array + ( + array + ( + 'id' => 2, + 'title' => 'Imaginary Spec', + ) + ); + + $this->_reattachBehavior($testOptions); + $this->Document->setFilterValues($filterValues); + + $result = $this->Document->find() + ->select(['id', 'title']) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + } + + /** + * Test filtering with Containable and hasOne Model.field. + * + * @return void + */ + public function testHasOneWithContainable() + { + $testOptions = array + ( + 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), + 'Items.code' => array('type' => 'text'), + 'Metadata.size' => array('type' => 'text', 'condition' => '='), + ); + + $filterValues = array + ( + 'Documents' => array('title' => 'in'), + 'Items' => array('code' => '04'), + 'Metadata' => array('size' => 45), + ); + + $expected = array + ( + array + ( + 'id' => 2, + 'title' => 'Imaginary Spec', + 'document_category_id' => 1, + 'owner_id' => 1, + 'is_private' => 0, + 'metadata' => array('id' => 2, 'document_id' => 2, 'weight' => 0, 'size' => 45, 'permissions' => 'rw-------'), + 'items' => 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->_reattachBehavior($testOptions); + $this->Document->setFilterValues($filterValues); + $result = $this->Document->find() + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->contain([ + 'Metadata' => [ + 'fields' => ['id', 'document_id', 'weight', 'size', 'permissions'], + ], + 'Items' => [ + 'fields' => ['id', 'document_id', 'code'], + ], + ]) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + + // filtered first, containable second + $this->_reattachBehavior($testOptions); + $this->Document->setFilterValues($filterValues); + $result = $this->Document->find() + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->contain([ + 'Metadata' => [ + 'fields' => ['id', 'document_id', 'weight', 'size', 'permissions'], + ], + 'Items' => [ + 'fields' => ['id', 'document_id', 'code'], + ], + ]) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + } + + /** + * Test filtering when a join is already present in the query, + * this should prevent duplicate joins and query errors. + * + * @return void + */ + public function testJoinAlreadyPresent() + { + $testOptions = array + ( + 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), + 'Items.code' => array('type' => 'text'), + 'Metadata.size' => array('type' => 'text', 'condition' => '='), + ); + + $filterValues = array + ( + 'Documents' => array('title' => 'in'), + 'Items' => array('code' => '04'), + 'Metadata' => array('size' => 45), + ); + + $expected = array + ( + array + ( + 'id' => 2, + 'title' => 'Imaginary Spec', + 'document_category_id' => 1, + 'owner_id' => 1, + 'is_private' => 0, + 'document_category' => 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-------'), + 'items' => 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' => 'FilterItems', + 'type' => 'INNER', + 'conditions' => 'Documents.id = FilterItems.document_id', + ); + + $this->_reattachBehavior($testOptions); + $this->Document->setFilterValues($filterValues); + $result = $this->Document->find() + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->contain([ + 'DocumentCategories' => [ + 'fields' => ['id', 'title', 'description'], + ], + 'Metadata' => [ + 'fields' => ['id', 'document_id', 'weight', 'size', 'permissions'], + ], + 'Items' => [ + 'fields' => ['id', 'document_id', 'code'], + ], + ]) + ->join($customJoin) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + } + + /** + * Test the 'nofilter' query param. + * + * @return void + */ + public function testNofilterFindParam() + { + $testOptions = array + ( + 'Documents.title' => array('type' => 'text', 'condition' => 'like'), + 'DocumentCategories.id' => array('type' => 'select'), + 'Documents.is_private' => array('type' => 'checkbox', 'label' => 'Private?', 'default' => 0) + ); + $this->_reattachBehavior($testOptions); + + + $filterValues = array + ( + 'DocumentCategories' => array('id' => 2), + 'Documents' => array('title' => '') + ); + $this->Document->setFilterValues($filterValues); + + $expected = array + ( + array('id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0) + ); + + $result = $this->Document->find('all', ['nofilter' => true]) + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->enableHydration(false) + ->toArray(); + $this->assertNotEquals($expected, $result); + + $result = $this->Document->find('all', ['nofilter' => 'true']) + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + } + + /** + * Test bailing out if no settings exist for the current model. + * + * @return void + */ + public function testExitWhenNoSettings() + { + $this->Document->DocumentCategories->addBehavior('Filter.Filtered'); + + $Filtered = $this->Document->DocumentCategories->behaviors()->get('Filtered'); + $this->assertFalse(isset($Filtered->settings[$this->Document->DocumentCategories->getAlias()])); + + $filterValues = array + ( + 'DocumentCategories' => array('id' => 2) + ); + $this->Document->DocumentCategories->setFilterValues($filterValues); + + $expected = 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!'), + ); + + $result = $this->Document->DocumentCategories->find('all', ['nofilter' => 'true']) + ->select(['id', 'title', 'description']) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + + $this->Document->DocumentCategories->removeBehavior('Filtered'); + } + + /** + * Test beforeDataFilter() callback, used to cancel filtering if necessary. + * + * @return void + */ + public function testBeforeDataFilterCallbackCancel() + { + $Document = $this->getTableLocator()->get('Document2', ['className' => Documents2Table::class]); + $this->assertInstanceOf(Documents2Table::class, $Document); + $this->Document = $Document; + $testOptions = array + ( + 'Documents.title' => array('type' => 'text', 'condition' => 'like'), + 'DocumentCategories.id' => array('type' => 'select'), + 'Documents.is_private' => array('type' => 'checkbox', 'label' => 'Private?') + ); + $this->_reattachBehavior($testOptions); + + + $filterValues = array + ( + 'DocumentCategories' => array('id' => 2) + ); + $this->Document->setFilterValues($filterValues); + + $expected = array + ( + array('id' => 1, 'title' => 'Testing Doc', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0,), + array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0), + array('id' => 3, 'title' => 'Nonexistant data', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0), + array('id' => 4, 'title' => 'Illegal explosives DIY', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 1), + array('id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0), + array('id' => 6, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0), + array('id' => 7, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0), + ); + + $result = $this->Document->find() + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + } + + /** + * Test afterDataFilter() callback, used to modify the conditions after + * filter conditions have been applied. + * + * @return void + */ + public function testAfterDataFilterCallbackQueryChange() + { + $Document = $this->getTableLocator()->get('Document3', ['className' => Documents3Table::class]); + $this->assertInstanceOf(Documents3Table::class, $Document); + $this->Document = $Document; + $this->Document->itemToUnset = 'FilterDocumentCategories.id'; + + $testOptions = array + ( + 'Documents.title' => array('type' => 'text', 'condition' => 'like'), + 'DocumentCategories.id' => array('type' => 'select'), + 'Documents.is_private' => array('type' => 'checkbox', 'label' => 'Private?') + ); + $this->_reattachBehavior($testOptions); + + + $filterValues = array + ( + 'DocumentCategories' => array('id' => 2) + ); + $this->Document->setFilterValues($filterValues); + + $expected = array + ( + array('id' => 1, 'title' => 'Testing Doc', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0), + array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0), + array('id' => 3, 'title' => 'Nonexistant data', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0), + array('id' => 4, 'title' => 'Illegal explosives DIY', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 1), + array('id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0), + array('id' => 6, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0), + array('id' => 7, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0), + ); + + $result = $this->Document->find('all') + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..7b6f124 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,15 @@ + Date: Mon, 28 Aug 2023 13:32:37 +0200 Subject: [PATCH 04/37] Run composer install --- .github/workflows/pull-request.yml | 3 +++ .gitignore | 1 + 2 files changed, 4 insertions(+) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 8f7a8a0..b727f6d 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -22,6 +22,9 @@ jobs: mysql-version: '5.6' - name: Check out repository code uses: actions/checkout@v3 + - name: Install dependencies + run: composer install + shell: bash #- name: Setup CakePHP # uses: ./.github/actions/setup-cakephp #- name: Setup database diff --git a/.gitignore b/.gitignore index 004611e..dd66903 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /.settings/ /.buildpath /.project +/.phplint-cache /composer.lock /vendor/ From 6834444a1e36328771b47e4eefff26e8d6f020ae Mon Sep 17 00:00:00 2001 From: bancer Date: Mon, 28 Aug 2023 13:41:12 +0200 Subject: [PATCH 05/37] Adjust CI commands --- .github/workflows/pull-request.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index b727f6d..3e52958 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -23,7 +23,7 @@ jobs: - name: Check out repository code uses: actions/checkout@v3 - name: Install dependencies - run: composer install + run: composer install cakephp/cakephp shell: bash #- name: Setup CakePHP # uses: ./.github/actions/setup-cakephp @@ -32,7 +32,8 @@ jobs: - name: Create test database run: mysql -u root -e "CREATE DATABASE cakephp_test" shell: bash - #- name: Install PHPUnit + - name: Install PHPUnit + run: composer require 'phpunit/phpunit=5.7' # run: | # cd ./cakephp # composer require 'phpunit/phpunit=5.7' @@ -50,6 +51,8 @@ jobs: php-version: 7.4 - name: Check out repository code uses: actions/checkout@v3 + - name: Install PHPLint + run: composer require --dev overtrue/phplint - name: Run PHPLint run: vendor/bin/phplint @@ -71,4 +74,4 @@ jobs: composer require --dev phpstan/phpstan composer require --dev phpstan/phpstan-phpunit - name: PHPStan - run: vendor/bin/phpstan analyse --level=8 ./src + run: vendor/bin/phpstan analyse --level=8 ./src ./tests From 2e0d708f29d4c8326edbb680406954b9841273c4 Mon Sep 17 00:00:00 2001 From: bancer Date: Mon, 28 Aug 2023 13:44:25 +0200 Subject: [PATCH 06/37] Adjust CI command --- .github/workflows/pull-request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 3e52958..d70d592 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -23,7 +23,7 @@ jobs: - name: Check out repository code uses: actions/checkout@v3 - name: Install dependencies - run: composer install cakephp/cakephp + run: composer require 'cakephp/cakephp=3.10.5' shell: bash #- name: Setup CakePHP # uses: ./.github/actions/setup-cakephp From ef52bfeaf2d1d8197e617ecd883788e613772674 Mon Sep 17 00:00:00 2001 From: bancer Date: Mon, 28 Aug 2023 13:50:03 +0200 Subject: [PATCH 07/37] Relax requirements in composer.json --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 9690c93..3b6e452 100644 --- a/composer.json +++ b/composer.json @@ -8,8 +8,8 @@ "cakephp/cakephp": "^3.10" }, "require-dev": { - "overtrue/phplint": "^3.4", - "phpstan/phpstan": "^1.10", + "overtrue/phplint": "^2.0|^3.4", + "phpstan/phpstan": "^0.1|^1.10", "phpunit/phpunit": "^5.7.14|^6.0" }, "autoload": { From 014a7ba8c584a0c897e8c6bdc2a5a923b7555fb8 Mon Sep 17 00:00:00 2001 From: bancer Date: Mon, 28 Aug 2023 13:54:34 +0200 Subject: [PATCH 08/37] Adjust CI commands --- .github/workflows/pull-request.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index d70d592..279d3b6 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -23,7 +23,7 @@ jobs: - name: Check out repository code uses: actions/checkout@v3 - name: Install dependencies - run: composer require 'cakephp/cakephp=3.10.5' + run: composer require 'cakephp/cakephp=3.10.5' --with-all-dependencies shell: bash #- name: Setup CakePHP # uses: ./.github/actions/setup-cakephp @@ -33,7 +33,7 @@ jobs: run: mysql -u root -e "CREATE DATABASE cakephp_test" shell: bash - name: Install PHPUnit - run: composer require 'phpunit/phpunit=5.7' + run: composer require 'phpunit/phpunit=5.7' --with-all-dependencies # run: | # cd ./cakephp # composer require 'phpunit/phpunit=5.7' From 12c126453c2cbcd08dd4831fda02a5f82554cfd9 Mon Sep 17 00:00:00 2001 From: bancer Date: Mon, 28 Aug 2023 13:59:15 +0200 Subject: [PATCH 09/37] Remove unnecessary commas --- .../Controller/Component/FilterComponentTest.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/TestCase/Controller/Component/FilterComponentTest.php b/tests/TestCase/Controller/Component/FilterComponentTest.php index 4527c11..b762b65 100644 --- a/tests/TestCase/Controller/Component/FilterComponentTest.php +++ b/tests/TestCase/Controller/Component/FilterComponentTest.php @@ -170,7 +170,7 @@ public function testSessionStartupDataFakeNonexistantModel() $sessionKey = sprintf( 'FilterPlugin.Filters.%s.%s', $this->Controller->getName(), - $this->Controller->getRequest()->getParam('action'), + $this->Controller->getRequest()->getParam('action') ); $filterValues = array(); $this->Controller->getRequest()->getSession()->write($sessionKey, $filterValues); @@ -200,7 +200,7 @@ public function testSessionStartupData() $sessionKey = sprintf( 'FilterPlugin.Filters.%s.%s', $this->Controller->getName(), - $this->Controller->getRequest()->getParam('action'), + $this->Controller->getRequest()->getParam('action') ); $filterValues = array(); @@ -268,7 +268,7 @@ public function testPostStartupData() $sessionKey = sprintf( 'FilterPlugin.Filters.%s.%s', $this->Controller->getName(), - $this->Controller->getRequest()->getParam('action'), + $this->Controller->getRequest()->getParam('action') ); $sessionData = $this->Controller->getRequest()->getSession()->read($sessionKey); $this->assertEquals($filterValues, $sessionData); @@ -660,7 +660,7 @@ public function testPersistence() $sessionKey = sprintf( 'FilterPlugin.Filters.%s.%s', 'SomeOtherController', - $this->Controller->getRequest()->getParam('action'), + $this->Controller->getRequest()->getParam('action') ); $filterValues = array('Document' => array('title' => 'in'), 'Filter' => array('filterFormId' => 'Document')); $this->Controller->getRequest()->getSession()->write($sessionKey, $filterValues); @@ -668,7 +668,7 @@ public function testPersistence() $sessionKey = sprintf( 'FilterPlugin.Filters.%s.%s', $this->Controller->getName(), - $this->Controller->getRequest()->getParam('action'), + $this->Controller->getRequest()->getParam('action') ); $filterValues = array('Document' => array('title' => 'in'), 'Filter' => array('filterFormId' => 'Document')); $this->Controller->getRequest()->getSession()->write($sessionKey, $filterValues); From 1d765b21e877a0cb83fa31ce40c550c6fdc3ce85 Mon Sep 17 00:00:00 2001 From: bancer Date: Mon, 28 Aug 2023 14:03:51 +0200 Subject: [PATCH 10/37] Adjust CI commands --- .github/workflows/pull-request.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 279d3b6..72b024a 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -23,7 +23,7 @@ jobs: - name: Check out repository code uses: actions/checkout@v3 - name: Install dependencies - run: composer require 'cakephp/cakephp=3.10.5' --with-all-dependencies + run: composer require 'cakephp/cakephp=3.10.5' --with-all-dependencies --no-dev shell: bash #- name: Setup CakePHP # uses: ./.github/actions/setup-cakephp @@ -33,7 +33,7 @@ jobs: run: mysql -u root -e "CREATE DATABASE cakephp_test" shell: bash - name: Install PHPUnit - run: composer require 'phpunit/phpunit=5.7' --with-all-dependencies + run: composer require 'phpunit/phpunit=5.7' --with-all-dependencies --no-dev # run: | # cd ./cakephp # composer require 'phpunit/phpunit=5.7' From 86f8d3655f73ad88f896e291374f3feeff9bf8e5 Mon Sep 17 00:00:00 2001 From: bancer Date: Mon, 28 Aug 2023 14:07:14 +0200 Subject: [PATCH 11/37] Adjust CI commands --- .github/workflows/pull-request.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 72b024a..5f22bb2 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -23,7 +23,7 @@ jobs: - name: Check out repository code uses: actions/checkout@v3 - name: Install dependencies - run: composer require 'cakephp/cakephp=3.10.5' --with-all-dependencies --no-dev + run: composer require 'cakephp/cakephp=3.10.5' --with-all-dependencies --update-no-dev shell: bash #- name: Setup CakePHP # uses: ./.github/actions/setup-cakephp @@ -33,7 +33,7 @@ jobs: run: mysql -u root -e "CREATE DATABASE cakephp_test" shell: bash - name: Install PHPUnit - run: composer require 'phpunit/phpunit=5.7' --with-all-dependencies --no-dev + run: composer require 'phpunit/phpunit=5.7' --with-all-dependencies --update-no-dev # run: | # cd ./cakephp # composer require 'phpunit/phpunit=5.7' From 06d5cc1a4719d26a09d2b1500eb5da4c5629ab87 Mon Sep 17 00:00:00 2001 From: bancer Date: Mon, 28 Aug 2023 14:11:12 +0200 Subject: [PATCH 12/37] Adjust CI commands --- .github/workflows/pull-request.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 5f22bb2..6fecb90 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -23,7 +23,7 @@ jobs: - name: Check out repository code uses: actions/checkout@v3 - name: Install dependencies - run: composer require 'cakephp/cakephp=3.10.5' --with-all-dependencies --update-no-dev + run: composer require 'cakephp/cakephp=3.10.5' --with-all-dependencies --ignore-platform-req=php shell: bash #- name: Setup CakePHP # uses: ./.github/actions/setup-cakephp @@ -33,7 +33,7 @@ jobs: run: mysql -u root -e "CREATE DATABASE cakephp_test" shell: bash - name: Install PHPUnit - run: composer require 'phpunit/phpunit=5.7' --with-all-dependencies --update-no-dev + run: composer require 'phpunit/phpunit=5.7' --with-all-dependencies --ignore-platform-req=php # run: | # cd ./cakephp # composer require 'phpunit/phpunit=5.7' From 0cebfa12f00e4cb9e0285ec05474fcc3b9169b6e Mon Sep 17 00:00:00 2001 From: bancer Date: Mon, 28 Aug 2023 14:17:00 +0200 Subject: [PATCH 13/37] Adjust CI command --- .github/workflows/pull-request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 6fecb90..476ceb8 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -33,7 +33,7 @@ jobs: run: mysql -u root -e "CREATE DATABASE cakephp_test" shell: bash - name: Install PHPUnit - run: composer require 'phpunit/phpunit=5.7' --with-all-dependencies --ignore-platform-req=php + run: composer require phpunit/phpunit --with-all-dependencies --ignore-platform-req=php # run: | # cd ./cakephp # composer require 'phpunit/phpunit=5.7' From 51cb6de4e0a63d6041da7a3a9ca0d57da4893c77 Mon Sep 17 00:00:00 2001 From: bancer Date: Mon, 28 Aug 2023 14:21:31 +0200 Subject: [PATCH 14/37] Adjust CI command --- .github/workflows/pull-request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 476ceb8..8030a3e 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -33,7 +33,7 @@ jobs: run: mysql -u root -e "CREATE DATABASE cakephp_test" shell: bash - name: Install PHPUnit - run: composer require phpunit/phpunit --with-all-dependencies --ignore-platform-req=php + run: composer require --dev 'phpunit/phpunit=5.7.0' --with-all-dependencies --ignore-platform-req=php # run: | # cd ./cakephp # composer require 'phpunit/phpunit=5.7' From 1e4fd6d885df38b7092befbf92e1bc12644ef3c8 Mon Sep 17 00:00:00 2001 From: bancer Date: Mon, 28 Aug 2023 14:27:11 +0200 Subject: [PATCH 15/37] Adjust CI commands --- .github/workflows/pull-request.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 8030a3e..33cf368 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -23,7 +23,7 @@ jobs: - name: Check out repository code uses: actions/checkout@v3 - name: Install dependencies - run: composer require 'cakephp/cakephp=3.10.5' --with-all-dependencies --ignore-platform-req=php + run: composer require 'cakephp/cakephp=3.10.5' 'phpunit/phpunit=5.7.0' --with-all-dependencies --ignore-platform-req=php shell: bash #- name: Setup CakePHP # uses: ./.github/actions/setup-cakephp @@ -32,8 +32,8 @@ jobs: - name: Create test database run: mysql -u root -e "CREATE DATABASE cakephp_test" shell: bash - - name: Install PHPUnit - run: composer require --dev 'phpunit/phpunit=5.7.0' --with-all-dependencies --ignore-platform-req=php + #- name: Install PHPUnit + # run: composer require --dev 'phpunit/phpunit=5.7.0' --with-all-dependencies --ignore-platform-req=php # run: | # cd ./cakephp # composer require 'phpunit/phpunit=5.7' From 2ddd6d76313520bd6029b22edca9064d3d456708 Mon Sep 17 00:00:00 2001 From: bancer Date: Mon, 28 Aug 2023 14:35:14 +0200 Subject: [PATCH 16/37] Adjust CI command --- .github/workflows/pull-request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 33cf368..3c56c2f 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -23,7 +23,7 @@ jobs: - name: Check out repository code uses: actions/checkout@v3 - name: Install dependencies - run: composer require 'cakephp/cakephp=3.10.5' 'phpunit/phpunit=5.7.0' --with-all-dependencies --ignore-platform-req=php + run: composer require 'cakephp/cakephp=3.10.5' 'phpunit/phpunit=5.7.0' --with-all-dependencies shell: bash #- name: Setup CakePHP # uses: ./.github/actions/setup-cakephp From 7a733e048662ff5a4b81bed52d3fa87830d0fabe Mon Sep 17 00:00:00 2001 From: bancer Date: Mon, 28 Aug 2023 14:39:42 +0200 Subject: [PATCH 17/37] Simplify CI config --- .github/actions/setup-cakephp/action.yml | 37 --------------- .github/actions/setup-database/action.yml | 55 ----------------------- .github/workflows/pull-request.yml | 18 +++----- 3 files changed, 5 insertions(+), 105 deletions(-) delete mode 100644 .github/actions/setup-cakephp/action.yml delete mode 100644 .github/actions/setup-database/action.yml diff --git a/.github/actions/setup-cakephp/action.yml b/.github/actions/setup-cakephp/action.yml deleted file mode 100644 index 399fda1..0000000 --- a/.github/actions/setup-cakephp/action.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: 'Setup CakePHP' -description: 'Clone CakePHP and setup it' - -runs: - using: "composite" - steps: - - - name: Clone CakePHP - uses: actions/checkout@v3 - with: - repository: cakephp/cakephp - path: ./cakephp - ref: 3.10.5 - -# - name: Make tmp folder writable -# run: chmod -R 777 ./cakephp/app/tmp -# shell: bash - -# - name: Fix composer autoload -# run: | -# echo " /tmp/core.php -# echo "require ROOT . '/vendors/autoload.php'; " >> /tmp/core.php -# echo "spl_autoload_unregister(array('App', 'load')); " >> /tmp/core.php -# echo "spl_autoload_register(array('App', 'load'), true, true); " >> /tmp/core.php -# echo "?>" >> /tmp/core.php -# cat ./cakephp/app/Config/core.php >> /tmp/core.php -# cp /tmp/core.php ./cakephp/app/Config/core.php -# shell: bash - -# - name: Copy plugin files to plugins folder -# run: | -# mkdir -p ./cakephp/plugins/Filter -# cp -R ./Controller ./cakephp/plugins/Filter/Controller -# cp -R ./Model ./cakephp/plugins/Filter/Model -# cp -R ./Test ./cakephp/plugins/Filter/Test -# cp -R ./View ./cakephp/plugins/Filter/View -# shell: bash diff --git a/.github/actions/setup-database/action.yml b/.github/actions/setup-database/action.yml deleted file mode 100644 index 128d752..0000000 --- a/.github/actions/setup-database/action.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: 'Setup Database' -description: 'Create test database and generate database configuration file' - -runs: - using: "composite" - steps: - - - name: Create test database - run: mysql -u root -e "CREATE DATABASE cakephp_test" - shell: bash - - - name: Generate database configuration file - run: echo " array( - 'datasource' => 'Database/Mysql', - 'host' => '127.0.0.1', - 'login' => 'root' - ) - ); - public \$default = array( - 'persistent' => false, - 'host' => '', - 'login' => '', - 'password' => '', - 'database' => 'cakephp_test', - 'prefix' => '' - ); - public \$test = array( - 'persistent' => false, - 'host' => '', - 'login' => '', - 'password' => '', - 'database' => 'cakephp_test', - 'prefix' => '' - ); - public function __construct() { - \$db = 'mysql'; - if (!empty(\$_SERVER['DB'])) { - \$db = \$_SERVER['DB']; - } - foreach (array('default', 'test') as \$source) { - \$config = array_merge(\$this->{\$source}, \$this->identities[\$db]); - if (is_array(\$config['database'])) { - \$config['database'] = \$config['database'][\$source]; - } - if (!empty(\$config['schema']) && is_array(\$config['schema'])) { - \$config['schema'] = \$config['schema'][\$source]; - } - \$this->{\$source} = \$config; - } - } - }" > ./cakephp/app/Config/database.php - shell: bash diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 3c56c2f..8653a26 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -23,21 +23,13 @@ jobs: - name: Check out repository code uses: actions/checkout@v3 - name: Install dependencies - run: composer require 'cakephp/cakephp=3.10.5' 'phpunit/phpunit=5.7.0' --with-all-dependencies + run: | + composer remove phpstan/phpstan + composer require 'cakephp/cakephp=3.10.5' 'phpunit/phpunit=5.7.0' --with-all-dependencies shell: bash - #- name: Setup CakePHP - # uses: ./.github/actions/setup-cakephp - #- name: Setup database - # uses: ./.github/actions/setup-database - name: Create test database run: mysql -u root -e "CREATE DATABASE cakephp_test" shell: bash - #- name: Install PHPUnit - # run: composer require --dev 'phpunit/phpunit=5.7.0' --with-all-dependencies --ignore-platform-req=php - # run: | - # cd ./cakephp - # composer require 'phpunit/phpunit=5.7' - # cd ../ - name: Unit Tests run: ./vendor/bin/phpunit ./tests @@ -65,8 +57,8 @@ jobs: php-version: 7.4 - name: Check out repository code uses: actions/checkout@v3 - - name: Setup CakePHP - uses: ./.github/actions/setup-cakephp + - name: Install CakePHP + run: composer require 'cakephp/cakephp=3.10.5' - name: Install PHPUnit run: composer require 'phpunit/phpunit=7.0' - name: Install PHPStan From 55cb35bfaaeb653bbf8ab29b700ae82edc1bd1c9 Mon Sep 17 00:00:00 2001 From: bancer Date: Mon, 28 Aug 2023 14:44:22 +0200 Subject: [PATCH 18/37] Adjust CI commands --- .github/workflows/pull-request.yml | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 8653a26..f08c018 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -22,9 +22,9 @@ jobs: mysql-version: '5.6' - name: Check out repository code uses: actions/checkout@v3 - - name: Install dependencies + - name: Install Dependencies run: | - composer remove phpstan/phpstan + composer remove --dev phpstan/phpstan composer require 'cakephp/cakephp=3.10.5' 'phpunit/phpunit=5.7.0' --with-all-dependencies shell: bash - name: Create test database @@ -57,13 +57,7 @@ jobs: php-version: 7.4 - name: Check out repository code uses: actions/checkout@v3 - - name: Install CakePHP - run: composer require 'cakephp/cakephp=3.10.5' - - name: Install PHPUnit - run: composer require 'phpunit/phpunit=7.0' - - name: Install PHPStan - run: | - composer require --dev phpstan/phpstan - composer require --dev phpstan/phpstan-phpunit + - name: Install Dependencies + run: composer require 'cakephp/cakephp=3.10.5' 'phpunit/phpunit=7.0' phpstan/phpstan phpstan/phpstan-phpunit - name: PHPStan run: vendor/bin/phpstan analyse --level=8 ./src ./tests From 2c90ceac44d3386cd302a3f2937b47ea5a3fff61 Mon Sep 17 00:00:00 2001 From: bancer Date: Mon, 28 Aug 2023 15:08:53 +0200 Subject: [PATCH 19/37] Fix code style errors --- .github/workflows/pull-request.yml | 2 +- composer.json | 9 +++++---- phpstan.neon | 4 ++++ tests/Fixture/DocumentCategoriesFixture.php | 4 +--- tests/Fixture/DocumentsFixture.php | 4 +--- tests/Fixture/ItemsFixture.php | 4 +--- tests/Fixture/MetadataFixture.php | 4 +--- .../Controller/Component/FilterComponentTest.php | 6 ++++++ .../MockObjects/DocumentCategoriesTable.php | 1 + .../MockObjects/DocumentTestsController.php | 8 +++++--- tests/TestCase/MockObjects/Documents2Table.php | 1 + tests/TestCase/MockObjects/Documents3Table.php | 3 ++- tests/TestCase/MockObjects/DocumentsTable.php | 1 + tests/TestCase/MockObjects/ItemsTable.php | 1 + tests/TestCase/MockObjects/MetadataTable.php | 1 + .../Model/Behavior/FilteredBehaviorTest.php | 14 +++++++++----- 16 files changed, 41 insertions(+), 26 deletions(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index f08c018..48697c5 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -58,6 +58,6 @@ jobs: - name: Check out repository code uses: actions/checkout@v3 - name: Install Dependencies - run: composer require 'cakephp/cakephp=3.10.5' 'phpunit/phpunit=7.0' phpstan/phpstan phpstan/phpstan-phpunit + run: composer require 'cakephp/cakephp=3.10.5' 'phpunit/phpunit=7.0' phpstan/phpstan phpstan/phpstan-phpunit --with-all-dependencies - name: PHPStan run: vendor/bin/phpstan analyse --level=8 ./src ./tests diff --git a/composer.json b/composer.json index 3b6e452..25fc4dd 100644 --- a/composer.json +++ b/composer.json @@ -5,12 +5,13 @@ "license": "GPL-3.0-or-later", "minimum-stability": "stable", "require": { - "cakephp/cakephp": "^3.10" + "cakephp/cakephp": "3.10.5", + "phpunit/phpunit": "7.0", + "phpstan/phpstan": "^1.10", + "phpstan/phpstan-phpunit": "^1.3" }, "require-dev": { - "overtrue/phplint": "^2.0|^3.4", - "phpstan/phpstan": "^0.1|^1.10", - "phpunit/phpunit": "^5.7.14|^6.0" + "overtrue/phplint": "^2.0|^3.4" }, "autoload": { "psr-4": { diff --git a/phpstan.neon b/phpstan.neon index d67b3f3..218fba5 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -6,3 +6,7 @@ parameters: - phpstan-bootstrap.php scanDirectories: - ./ + ignoreErrors: + # False positive as __() function can accept more than 2 parameters. + - '/Cannot call method enableHydration\(\) on array\|Cake\\ORM\\Query./' + - '/Cannot call method join\(\) on array\|Cake\\ORM\\Query./' diff --git a/tests/Fixture/DocumentCategoriesFixture.php b/tests/Fixture/DocumentCategoriesFixture.php index 3ffc2fc..cf18be7 100644 --- a/tests/Fixture/DocumentCategoriesFixture.php +++ b/tests/Fixture/DocumentCategoriesFixture.php @@ -18,10 +18,8 @@ class DocumentCategoriesFixture extends TestFixture { - public $name = 'DocumentCategory'; - /** - * @var (bool|int|string)[][] + * @var mixed[] */ public $fields = array ( diff --git a/tests/Fixture/DocumentsFixture.php b/tests/Fixture/DocumentsFixture.php index e43e6c9..3c86a15 100644 --- a/tests/Fixture/DocumentsFixture.php +++ b/tests/Fixture/DocumentsFixture.php @@ -18,10 +18,8 @@ class DocumentsFixture extends TestFixture { - public $name = 'Document'; - /** - * @var (bool|int|string)[][] + * @var mixed[] */ public $fields = array ( diff --git a/tests/Fixture/ItemsFixture.php b/tests/Fixture/ItemsFixture.php index d5df23f..c441337 100644 --- a/tests/Fixture/ItemsFixture.php +++ b/tests/Fixture/ItemsFixture.php @@ -18,10 +18,8 @@ class ItemsFixture extends TestFixture { - public $name = 'Item'; - /** - * @var (bool|int|string)[][] + * @var mixed[] */ public $fields = array ( diff --git a/tests/Fixture/MetadataFixture.php b/tests/Fixture/MetadataFixture.php index d967ed1..f117349 100644 --- a/tests/Fixture/MetadataFixture.php +++ b/tests/Fixture/MetadataFixture.php @@ -18,10 +18,8 @@ class MetadataFixture extends TestFixture { - public $name = 'Metadata'; - /** - * @var (bool|int|string)[][] + * @var mixed[] */ public $fields = array ( diff --git a/tests/TestCase/Controller/Component/FilterComponentTest.php b/tests/TestCase/Controller/Component/FilterComponentTest.php index b762b65..93b63fb 100644 --- a/tests/TestCase/Controller/Component/FilterComponentTest.php +++ b/tests/TestCase/Controller/Component/FilterComponentTest.php @@ -70,6 +70,9 @@ public function testNoFilters() $this->assertFalse(in_array('Filter.Filter', $this->Controller->viewBuilder()->getHelpers())); } + /** + * @return void + */ public function testNoActionFilters() { $testSettings = array @@ -154,6 +157,9 @@ public function testEmptyStartup() $this->assertTrue(in_array('Filter.Filter', $this->Controller->viewBuilder()->getHelpers())); } + /** + * @return void + */ public function testSessionStartupDataFakeNonexistantModel() { $testSettings = array diff --git a/tests/TestCase/MockObjects/DocumentCategoriesTable.php b/tests/TestCase/MockObjects/DocumentCategoriesTable.php index 5e71470..db0cdce 100644 --- a/tests/TestCase/MockObjects/DocumentCategoriesTable.php +++ b/tests/TestCase/MockObjects/DocumentCategoriesTable.php @@ -14,6 +14,7 @@ class DocumentCategoriesTable extends Table /** * {@inheritDoc} * + * @param mixed[] $config * @see \Cake\ORM\Table::initialize() */ public function initialize(array $config) diff --git a/tests/TestCase/MockObjects/DocumentTestsController.php b/tests/TestCase/MockObjects/DocumentTestsController.php index 8f53e55..bbf37bc 100644 --- a/tests/TestCase/MockObjects/DocumentTestsController.php +++ b/tests/TestCase/MockObjects/DocumentTestsController.php @@ -5,8 +5,8 @@ use Cake\Controller\Controller; /** - * @property \Filter\Test\TestCase\MockObjects\DocumentsTable $Documents - * @property \FilterComponent $Filter + * @property \Filter\Test\TestCase\MockObjects\DocumentsTable $Document + * @property \Filter\Controller\Component\FilterComponent $Filter */ class DocumentTestsController extends Controller { @@ -23,9 +23,11 @@ class DocumentTestsController extends Controller public function initialize() { parent::initialize(); - $this->Document = $this->getTableLocator()->get('Documents', [ + /** @var \Filter\Test\TestCase\MockObjects\DocumentsTable $Table */ + $Table = $this->getTableLocator()->get('Documents', [ 'className' => DocumentsTable::class, ]); + $this->Document = $Table; $this->loadComponent('Filter.Filter'); } diff --git a/tests/TestCase/MockObjects/Documents2Table.php b/tests/TestCase/MockObjects/Documents2Table.php index 9d9489a..6d91512 100644 --- a/tests/TestCase/MockObjects/Documents2Table.php +++ b/tests/TestCase/MockObjects/Documents2Table.php @@ -15,6 +15,7 @@ class Documents2Table extends Table /** * {@inheritDoc} * + * @param mixed[] $config * @see \Cake\ORM\Table::initialize() */ public function initialize(array $config) diff --git a/tests/TestCase/MockObjects/Documents3Table.php b/tests/TestCase/MockObjects/Documents3Table.php index 9276acf..ad66f9a 100644 --- a/tests/TestCase/MockObjects/Documents3Table.php +++ b/tests/TestCase/MockObjects/Documents3Table.php @@ -15,6 +15,7 @@ class Documents3Table extends Table /** * {@inheritDoc} * + * @param mixed[] $config * @see \Cake\ORM\Table::initialize() */ public function initialize(array $config) @@ -33,7 +34,7 @@ public function initialize(array $config) /** * @param \Cake\ORM\Query $query Query. * @param mixed[] $options - * @return mixed[] + * @return \Cake\ORM\Query */ public function afterDataFilter($query, $options) { diff --git a/tests/TestCase/MockObjects/DocumentsTable.php b/tests/TestCase/MockObjects/DocumentsTable.php index 3628ee8..821b5cc 100644 --- a/tests/TestCase/MockObjects/DocumentsTable.php +++ b/tests/TestCase/MockObjects/DocumentsTable.php @@ -16,6 +16,7 @@ class DocumentsTable extends Table /** * {@inheritDoc} * + * @param mixed[] $config * @see \Cake\ORM\Table::initialize() */ public function initialize(array $config) diff --git a/tests/TestCase/MockObjects/ItemsTable.php b/tests/TestCase/MockObjects/ItemsTable.php index 6bc8b66..b0aa942 100644 --- a/tests/TestCase/MockObjects/ItemsTable.php +++ b/tests/TestCase/MockObjects/ItemsTable.php @@ -12,6 +12,7 @@ class ItemsTable extends Table /** * {@inheritDoc} * + * @param mixed[] $config * @see \Cake\ORM\Table::initialize() */ public function initialize(array $config) diff --git a/tests/TestCase/MockObjects/MetadataTable.php b/tests/TestCase/MockObjects/MetadataTable.php index 5d8c016..79817e4 100644 --- a/tests/TestCase/MockObjects/MetadataTable.php +++ b/tests/TestCase/MockObjects/MetadataTable.php @@ -9,6 +9,7 @@ class MetadataTable extends Table /** * {@inheritDoc} * + * @param mixed[] $config * @see \Cake\ORM\Table::initialize() */ public function initialize(array $config) diff --git a/tests/TestCase/Model/Behavior/FilteredBehaviorTest.php b/tests/TestCase/Model/Behavior/FilteredBehaviorTest.php index edf18ce..7a698ac 100644 --- a/tests/TestCase/Model/Behavior/FilteredBehaviorTest.php +++ b/tests/TestCase/Model/Behavior/FilteredBehaviorTest.php @@ -7,6 +7,7 @@ use Filter\Test\TestCase\MockObjects\DocumentsTable; use Cake\TestSuite\TestCase; use Filter\Test\TestCase\MockObjects\Documents2Table; +use Cake\ORM\Association; /** CakePHP Filter Plugin @@ -40,6 +41,7 @@ class FilteredBehaviorTest extends TestCase public function setUp() { + parent::setUp(); $Document = $this->getTableLocator()->get('Documents', ['className' => DocumentsTable::class]); $this->assertInstanceOf(DocumentsTable::class, $Document); $this->Document = $Document; @@ -47,6 +49,7 @@ public function setUp() public function tearDown() { + parent::tearDown(); unset($this->Document); } @@ -486,9 +489,10 @@ public function testCustomJoinConditions() 'metadata' => array('id' => 5, 'document_id' => 5, 'weight' => 4, 'size' => 790, 'permissions' => 'rw-rw-r--'), ) ); - - $oldConditions = $this->Document->associations()->get('Metadata')->getConditions(); - $this->Document->associations()->get('Metadata')->setConditions(['Metadata.size > 500']); + $Metadata = $this->Document->associations()->get('Metadata'); + $this->assertInstanceOf(Association::class, $Metadata); + $oldConditions = $Metadata->getConditions(); + $Metadata->setConditions(['Metadata.size > 500']); $result = $this->Document->find() ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) @@ -501,7 +505,7 @@ public function testCustomJoinConditions() ->toArray(); $this->assertEquals($expected, $result); - $this->Document->associations()->get('Metadata')->setConditions(['Metadata.size > 500']); + $Metadata->setConditions(['Metadata.size > 500']); $result = $this->Document->find() ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) ->contain([ @@ -513,7 +517,7 @@ public function testCustomJoinConditions() ->toArray(); $this->assertEquals($expected, $result); - $this->Document->associations()->get('Metadata')->setConditions($oldConditions); + $Metadata->setConditions($oldConditions); } /** From e0c1d59dccea5dce5b9ae263251db542b5209f13 Mon Sep 17 00:00:00 2001 From: bancer Date: Mon, 28 Aug 2023 15:14:43 +0200 Subject: [PATCH 20/37] Rollback composer changes --- composer.json | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 25fc4dd..3b6e452 100644 --- a/composer.json +++ b/composer.json @@ -5,13 +5,12 @@ "license": "GPL-3.0-or-later", "minimum-stability": "stable", "require": { - "cakephp/cakephp": "3.10.5", - "phpunit/phpunit": "7.0", - "phpstan/phpstan": "^1.10", - "phpstan/phpstan-phpunit": "^1.3" + "cakephp/cakephp": "^3.10" }, "require-dev": { - "overtrue/phplint": "^2.0|^3.4" + "overtrue/phplint": "^2.0|^3.4", + "phpstan/phpstan": "^0.1|^1.10", + "phpunit/phpunit": "^5.7.14|^6.0" }, "autoload": { "psr-4": { From 21f3fc329b271a134520dcbb97c5adeac94c3a9c Mon Sep 17 00:00:00 2001 From: bancer Date: Mon, 28 Aug 2023 15:18:20 +0200 Subject: [PATCH 21/37] Update CI command --- .github/workflows/pull-request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 48697c5..e471490 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -24,7 +24,7 @@ jobs: uses: actions/checkout@v3 - name: Install Dependencies run: | - composer remove --dev phpstan/phpstan + composer remove --dev phpstan/phpstan --no-update composer require 'cakephp/cakephp=3.10.5' 'phpunit/phpunit=5.7.0' --with-all-dependencies shell: bash - name: Create test database From 998c482b1b8a121e7847b92edebf0874ce79ec0a Mon Sep 17 00:00:00 2001 From: bancer Date: Mon, 28 Aug 2023 15:32:07 +0200 Subject: [PATCH 22/37] Install phpcs --- .github/workflows/pull-request.yml | 16 +++++++++++++++- composer.json | 6 ++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index e471490..01baabf 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -24,7 +24,7 @@ jobs: uses: actions/checkout@v3 - name: Install Dependencies run: | - composer remove --dev phpstan/phpstan --no-update + composer remove --dev phpstan/phpstan cakephp/cakephp-codesniffer --no-update composer require 'cakephp/cakephp=3.10.5' 'phpunit/phpunit=5.7.0' --with-all-dependencies shell: bash - name: Create test database @@ -61,3 +61,17 @@ jobs: run: composer require 'cakephp/cakephp=3.10.5' 'phpunit/phpunit=7.0' phpstan/phpstan phpstan/phpstan-phpunit --with-all-dependencies - name: PHPStan run: vendor/bin/phpstan analyse --level=8 ./src ./tests + + PHP-Code-Sniffer: + runs-on: ubuntu-latest + steps: + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 5.6 + - name: Check out repository code + uses: actions/checkout@v3 + - name: Install PHPCS + run: composer require --dev cakephp/cakephp-codesniffer + - name: Run PHPCS + run: vendor/bin/phpcs --colors --parallel=16 -p --standard=CakePHP src/ tests/ diff --git a/composer.json b/composer.json index 3b6e452..b108016 100644 --- a/composer.json +++ b/composer.json @@ -8,6 +8,7 @@ "cakephp/cakephp": "^3.10" }, "require-dev": { + "cakephp/cakephp-codesniffer": "^5.1", "overtrue/phplint": "^2.0|^3.4", "phpstan/phpstan": "^0.1|^1.10", "phpunit/phpunit": "^5.7.14|^6.0" @@ -17,5 +18,10 @@ "Filter\\": "src/", "Filter\\Test\\": "tests/" } + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } } } From 6a732161fabf569fcfa00a39c9e94ce6fb6cdc09 Mon Sep 17 00:00:00 2001 From: bancer Date: Mon, 28 Aug 2023 15:38:52 +0200 Subject: [PATCH 23/37] Adjust CI command --- .github/workflows/pull-request.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 01baabf..a10bf4b 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -24,7 +24,7 @@ jobs: uses: actions/checkout@v3 - name: Install Dependencies run: | - composer remove --dev phpstan/phpstan cakephp/cakephp-codesniffer --no-update + composer remove --dev phpstan/phpstan cakephp/cakephp-codesniffer overtrue/phplint --no-update composer require 'cakephp/cakephp=3.10.5' 'phpunit/phpunit=5.7.0' --with-all-dependencies shell: bash - name: Create test database @@ -72,6 +72,8 @@ jobs: - name: Check out repository code uses: actions/checkout@v3 - name: Install PHPCS - run: composer require --dev cakephp/cakephp-codesniffer + run: | + composer remove --dev phpstan/phpstan phpunit/phpunit overtrue/phplint --no-update + composer require cakephp/cakephp-codesniffer - name: Run PHPCS run: vendor/bin/phpcs --colors --parallel=16 -p --standard=CakePHP src/ tests/ From c2c3429a087c2042ebc11a19505d63020b353a0a Mon Sep 17 00:00:00 2001 From: bancer Date: Mon, 28 Aug 2023 15:48:08 +0200 Subject: [PATCH 24/37] Set phpcs standard path --- .github/workflows/pull-request.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index a10bf4b..31b9b7d 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -75,5 +75,6 @@ jobs: run: | composer remove --dev phpstan/phpstan phpunit/phpunit overtrue/phplint --no-update composer require cakephp/cakephp-codesniffer + vendor/bin/phpcs --config-set installed_paths /home/runner/work/cakephp-filter-plugin/cakephp-filter-plugin/vendor/cakephp/cakephp-codesniffer - name: Run PHPCS run: vendor/bin/phpcs --colors --parallel=16 -p --standard=CakePHP src/ tests/ From 20759c911efcbbffdd20b8285fbc10ed141bc7ca Mon Sep 17 00:00:00 2001 From: bancer Date: Mon, 28 Aug 2023 15:52:44 +0200 Subject: [PATCH 25/37] Replace tabs with spaces --- View/Elements/filter_form_begin.ctp | 34 - View/Elements/filter_form_end.ctp | 17 - View/Elements/filter_form_fields.ctp | 27 - View/Helper/FilterHelper.php | 114 --- src/Controller/Component/FilterComponent.php | 884 ++++++++--------- src/Model/Behavior/FilteredBehavior.php | 954 +++++++++---------- src/View/Elements/filter_form_begin.ctp | 34 + src/View/Elements/filter_form_end.ctp | 17 + src/View/Elements/filter_form_fields.ctp | 27 + src/View/Helper/FilterHelper.php | 118 +++ 10 files changed, 1115 insertions(+), 1111 deletions(-) delete mode 100644 View/Elements/filter_form_begin.ctp delete mode 100644 View/Elements/filter_form_end.ctp delete mode 100644 View/Elements/filter_form_fields.ctp delete mode 100644 View/Helper/FilterHelper.php create mode 100644 src/View/Elements/filter_form_begin.ctp create mode 100644 src/View/Elements/filter_form_end.ctp create mode 100644 src/View/Elements/filter_form_fields.ctp create mode 100644 src/View/Helper/FilterHelper.php diff --git a/View/Elements/filter_form_begin.ctp b/View/Elements/filter_form_begin.ctp deleted file mode 100644 index 7bba9dc..0000000 --- a/View/Elements/filter_form_begin.ctp +++ /dev/null @@ -1,34 +0,0 @@ - - - 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)); ?> diff --git a/View/Elements/filter_form_end.ctp b/View/Elements/filter_form_end.ctp deleted file mode 100644 index 4985522..0000000 --- a/View/Elements/filter_form_end.ctp +++ /dev/null @@ -1,17 +0,0 @@ - - - Multi-licensed under: - MPL - LGPL - GPL -*/ -?> -
- Form->submit(__('Submit')); ?> - Form->end(); ?> -
diff --git a/View/Elements/filter_form_fields.ctp b/View/Elements/filter_form_fields.ctp deleted file mode 100644 index 0d1ba57..0000000 --- a/View/Elements/filter_form_fields.ctp +++ /dev/null @@ -1,27 +0,0 @@ - - - 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']); - } - } -} diff --git a/View/Helper/FilterHelper.php b/View/Helper/FilterHelper.php deleted file mode 100644 index fb3b1b7..0000000 --- a/View/Helper/FilterHelper.php +++ /dev/null @@ -1,114 +0,0 @@ - - - Multi-licensed under: - MPL - LGPL - GPL -*/ -App::uses('AppHelper', 'View/Helper'); - -class FilterHelper extends AppHelper -{ - /** - * @param string $modelName - * @param mixed[] $options - * @return string - */ - public function filterForm($modelName, $options) - { - $view =& $this->_View; - - $output = $view->element - ( - 'filter_form_begin', - array - ( - 'plugin' => 'Filter', - 'modelName' => $modelName, - 'options' => $options - ), - array('plugin' => 'Filter') - ); - - $output .= $view->element - ( - 'filter_form_fields', - array('plugin' => 'Filter'), - array('plugin' => 'Filter') - ); - - $output .= $view->element - ( - 'filter_form_end', - array('plugin' => 'Filter'), - array('plugin' => 'Filter') - ); - - return $output; - } - - /** - * @param string $modelName - * @param mixed[] $options - * @return string - */ - public function beginForm($modelName, $options) - { - $view =& $this->_View; - $output = $view->element - ( - 'filter_form_begin', - array - ( - 'plugin' => 'Filter', - 'modelName' => $modelName, - 'options' => $options - ), - array('plugin' => 'Filter') - ); - - return $output; - } - - /** - * @param string[] $fields - * @return string - */ - public function inputFields($fields = array()) - { - $view =& $this->_View; - $output = $view->element - ( - 'filter_form_fields', - array - ( - 'plugin' => 'Filter', - 'includeFields' => $fields - ), - array('plugin' => 'Filter') - ); - - return $output; - } - - /** - * @return string - */ - public function endForm() - { - $view = $this->_View; - $output = $view->element - ( - 'filter_form_end', - array(), - array('plugin' => 'Filter') - ); - - return $output; - } -} diff --git a/src/Controller/Component/FilterComponent.php b/src/Controller/Component/FilterComponent.php index d5c67fc..d58b0fc 100644 --- a/src/Controller/Component/FilterComponent.php +++ b/src/Controller/Component/FilterComponent.php @@ -7,15 +7,15 @@ use Cake\Event\Event; /** - CakePHP Filter Plugin + CakePHP Filter Plugin - Copyright (C) 2009-3827 dr. Hannibal Lecter / lecterror - + Copyright (C) 2009-3827 dr. Hannibal Lecter / lecterror + - Multi-licensed under: - MPL - LGPL - GPL + Multi-licensed under: + MPL + LGPL + GPL */ /** @@ -24,439 +24,439 @@ */ class FilterComponent extends Component { - /** - * @var string[] - */ - public $components = array('Session'); - - /** - * @var mixed[] - */ - public $settings = array(); - - /** - * @var mixed[] - */ - public $nopersist = array(); - - /** - * @var mixed[] - */ - public $formData = array(); - - /** - * @var mixed[] - */ - protected $_request_settings = array(); - - /** - * {@inheritDoc} - * - * @param \Cake\Controller\ComponentRegistry $registry A ComponentRegistry this component can use to lazy load its components - * @param mixed[] $config Array of configuration settings. - */ - public function __construct(ComponentRegistry $registry, array $config = []) - { - parent::__construct($registry, $config); - $this->_request_settings = $config; - } - - /** - * Is called before the controller’s beforeFilter method, but after the controller’s initialize() method. - * - * @param \Cake\Event\Event $event Event object. - * @return void - */ - public function beforeFilter(Event $event) - { - $controller = $this->getController(); - if (!isset($controller->filters)) - { - return; - } - - $this->__updatePersistence($this->_request_settings); - $controllerName = $controller->getName(); - $this->settings[$controllerName] = $controller->filters; - - $action = $controller->getRequest()->getParam('action'); - if (!isset($this->settings[$controllerName][$action])) - { - return; - } - - $settings = $this->settings[$controllerName][$action]; - - foreach ($settings as $model => $filter) - { - if (!isset($controller->{$model})) - { - trigger_error(sprintf('Filter model not found: %s', $model)); - continue; - } - - $controller->$model->addBehavior('Filter.Filtered', $filter); - } - } - - /** - * Is called after the controller’s beforeFilter method but before the controller executes the current action handler. - * - * @param \Cake\Event\Event $event Event object. - * @return void - */ - public function startup(Event $event) - { - $controller = $this->getController(); - $controllerName = $controller->getName(); - $action = $controller->getRequest()->getParam('action'); - if (!isset($this->settings[$controllerName][$action])) - { - return; - } - - $settings = $this->settings[$controllerName][$action]; - - if (!in_array('Filter.Filter', $controller->viewBuilder()->getHelpers())) - { - $controller->viewBuilder()->setHelpers(['Filter.Filter']); - } - - $sessionKey = sprintf('FilterPlugin.Filters.%s.%s', $controllerName, $action); - $Session = $controller->getRequest()->getSession(); - $filterFormId = $controller->request->getQuery('filterFormId'); - if ($controller->request->is('get') && !empty($filterFormId)) - { - /** @var mixed[] $requestData */ - $requestData = $controller->request->getQuery('data', []); - $this->formData = $requestData; - } - elseif (!$controller->request->is('post') || $controller->request->getData('Filter.filterFormId') === null) - { - $persistedData = array(); - - if ($Session->check($sessionKey)) - { - $persistedData = $Session->read($sessionKey); - } - - if (empty($persistedData)) - { - return; - } - - $this->formData = $persistedData; - } - else - { - /** @var mixed[] $requestData */ - $requestData = $controller->request->getData(); - $this->formData = $requestData; - if ($Session->started()) - { - $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); - } - } - - /** - * Is called after the controller executes the requested action’s logic, but before the controller renders views and layout. - * - * @param \Cake\Event\Event $event Event object. - * @return void - */ - public function beforeRender(Event $event) - { - $controller = $this->getController(); - $controllerName = $controller->getName(); - $action = $controller->getRequest()->getParam('action'); - if (!isset($this->settings[$controllerName][$action])) - { - return; - } - - $models = $this->settings[$controllerName][$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; - $className = null; - if (isset($settings['className'])) { - $className = $settings['className']; - } - 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 'select': - $options['type'] = 'select'; - - $selectOptions = array(); - $TableLocator = $this->getController()->getTableLocator(); - if ($TableLocator->exists($fieldModel)) { - $workingModel = $TableLocator->get($fieldModel); - } else { - if ($className !== null) { - $workingModel = $TableLocator->get($fieldModel, [ - 'className' => $className, - ]); - } else { - $workingModel = $TableLocator->get($fieldModel); - } - } - - if (isset($settings['selectOptions'])) - { - $selectOptions = $settings['selectOptions']; - } - - if (isset($settings['selector'])) - { - if (!method_exists($workingModel, $settings['selector'])) - { - trigger_error - ( - sprintf( - '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) - { - $listOptions = array_merge( - $selectOptions, - [ - 'nofilter' => true, - 'keyField' => $fieldName, - 'valueField' => $fieldName, - 'fields' => array($fieldName, $fieldName), - ] - ); - } - else - { - $listOptions = array_merge($selectOptions, array('nofilter' => true)); - } - $options['options'] = $workingModel->find('list', $listOptions) - ->toArray(); - } - - 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: - $options['type'] = $settings['type']; - break; - } - - // 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($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 mixed[] $settings - * @return void - */ - private function __updatePersistence($settings) - { - $controller = $this->getController(); - $controllerName = $controller->getName(); - $Session = $controller->getRequest()->getSession(); - if ($Session->check('FilterPlugin.NoPersist')) - { - $this->nopersist = $Session->read('FilterPlugin.NoPersist'); - } - - if (isset($settings['nopersist'])) - { - $this->nopersist[$controllerName] = $settings['nopersist']; - if ($Session->started()) - { - $Session->write('FilterPlugin.NoPersist', $this->nopersist); - } - } - else if (isset($this->nopersist[$controllerName])) - { - unset($this->nopersist[$controllerName]); - if ($Session->started()) - { - $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) && $controllerName != $nopersistController) - { - if ($Session->check(sprintf('FilterPlugin.Filters.%s', $nopersistController))) - { - $Session->delete(sprintf('FilterPlugin.Filters.%s', $nopersistController)); - continue; - } - } - - $action = $controller->getRequest()->getParam('action'); - foreach ($actions as $noPersistAction) - { - if ($controllerName == $nopersistController && $noPersistAction == $action) - { - continue; - } - - if ($Session->check(sprintf('FilterPlugin.Filters.%s.%s', $nopersistController, $noPersistAction))) - { - $Session->delete(sprintf('FilterPlugin.Filters.%s.%s', $nopersistController, $noPersistAction)); - } - } - } - } - } + /** + * @var string[] + */ + public $components = array('Session'); + + /** + * @var mixed[] + */ + public $settings = array(); + + /** + * @var mixed[] + */ + public $nopersist = array(); + + /** + * @var mixed[] + */ + public $formData = array(); + + /** + * @var mixed[] + */ + protected $_request_settings = array(); + + /** + * {@inheritDoc} + * + * @param \Cake\Controller\ComponentRegistry $registry A ComponentRegistry this component can use to lazy load its components + * @param mixed[] $config Array of configuration settings. + */ + public function __construct(ComponentRegistry $registry, array $config = []) + { + parent::__construct($registry, $config); + $this->_request_settings = $config; + } + + /** + * Is called before the controller’s beforeFilter method, but after the controller’s initialize() method. + * + * @param \Cake\Event\Event $event Event object. + * @return void + */ + public function beforeFilter(Event $event) + { + $controller = $this->getController(); + if (!isset($controller->filters)) + { + return; + } + + $this->__updatePersistence($this->_request_settings); + $controllerName = $controller->getName(); + $this->settings[$controllerName] = $controller->filters; + + $action = $controller->getRequest()->getParam('action'); + if (!isset($this->settings[$controllerName][$action])) + { + return; + } + + $settings = $this->settings[$controllerName][$action]; + + foreach ($settings as $model => $filter) + { + if (!isset($controller->{$model})) + { + trigger_error(sprintf('Filter model not found: %s', $model)); + continue; + } + + $controller->$model->addBehavior('Filter.Filtered', $filter); + } + } + + /** + * Is called after the controller’s beforeFilter method but before the controller executes the current action handler. + * + * @param \Cake\Event\Event $event Event object. + * @return void + */ + public function startup(Event $event) + { + $controller = $this->getController(); + $controllerName = $controller->getName(); + $action = $controller->getRequest()->getParam('action'); + if (!isset($this->settings[$controllerName][$action])) + { + return; + } + + $settings = $this->settings[$controllerName][$action]; + + if (!in_array('Filter.Filter', $controller->viewBuilder()->getHelpers())) + { + $controller->viewBuilder()->setHelpers(['Filter.Filter']); + } + + $sessionKey = sprintf('FilterPlugin.Filters.%s.%s', $controllerName, $action); + $Session = $controller->getRequest()->getSession(); + $filterFormId = $controller->request->getQuery('filterFormId'); + if ($controller->request->is('get') && !empty($filterFormId)) + { + /** @var mixed[] $requestData */ + $requestData = $controller->request->getQuery('data', []); + $this->formData = $requestData; + } + elseif (!$controller->request->is('post') || $controller->request->getData('Filter.filterFormId') === null) + { + $persistedData = array(); + + if ($Session->check($sessionKey)) + { + $persistedData = $Session->read($sessionKey); + } + + if (empty($persistedData)) + { + return; + } + + $this->formData = $persistedData; + } + else + { + /** @var mixed[] $requestData */ + $requestData = $controller->request->getData(); + $this->formData = $requestData; + if ($Session->started()) + { + $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); + } + } + + /** + * Is called after the controller executes the requested action’s logic, but before the controller renders views and layout. + * + * @param \Cake\Event\Event $event Event object. + * @return void + */ + public function beforeRender(Event $event) + { + $controller = $this->getController(); + $controllerName = $controller->getName(); + $action = $controller->getRequest()->getParam('action'); + if (!isset($this->settings[$controllerName][$action])) + { + return; + } + + $models = $this->settings[$controllerName][$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; + $className = null; + if (isset($settings['className'])) { + $className = $settings['className']; + } + 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 'select': + $options['type'] = 'select'; + + $selectOptions = array(); + $TableLocator = $this->getController()->getTableLocator(); + if ($TableLocator->exists($fieldModel)) { + $workingModel = $TableLocator->get($fieldModel); + } else { + if ($className !== null) { + $workingModel = $TableLocator->get($fieldModel, [ + 'className' => $className, + ]); + } else { + $workingModel = $TableLocator->get($fieldModel); + } + } + + if (isset($settings['selectOptions'])) + { + $selectOptions = $settings['selectOptions']; + } + + if (isset($settings['selector'])) + { + if (!method_exists($workingModel, $settings['selector'])) + { + trigger_error + ( + sprintf( + '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) + { + $listOptions = array_merge( + $selectOptions, + [ + 'nofilter' => true, + 'keyField' => $fieldName, + 'valueField' => $fieldName, + 'fields' => array($fieldName, $fieldName), + ] + ); + } + else + { + $listOptions = array_merge($selectOptions, array('nofilter' => true)); + } + $options['options'] = $workingModel->find('list', $listOptions) + ->toArray(); + } + + 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: + $options['type'] = $settings['type']; + break; + } + + // 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($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 mixed[] $settings + * @return void + */ + private function __updatePersistence($settings) + { + $controller = $this->getController(); + $controllerName = $controller->getName(); + $Session = $controller->getRequest()->getSession(); + if ($Session->check('FilterPlugin.NoPersist')) + { + $this->nopersist = $Session->read('FilterPlugin.NoPersist'); + } + + if (isset($settings['nopersist'])) + { + $this->nopersist[$controllerName] = $settings['nopersist']; + if ($Session->started()) + { + $Session->write('FilterPlugin.NoPersist', $this->nopersist); + } + } + else if (isset($this->nopersist[$controllerName])) + { + unset($this->nopersist[$controllerName]); + if ($Session->started()) + { + $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) && $controllerName != $nopersistController) + { + if ($Session->check(sprintf('FilterPlugin.Filters.%s', $nopersistController))) + { + $Session->delete(sprintf('FilterPlugin.Filters.%s', $nopersistController)); + continue; + } + } + + $action = $controller->getRequest()->getParam('action'); + foreach ($actions as $noPersistAction) + { + if ($controllerName == $nopersistController && $noPersistAction == $action) + { + continue; + } + + if ($Session->check(sprintf('FilterPlugin.Filters.%s.%s', $nopersistController, $noPersistAction))) + { + $Session->delete(sprintf('FilterPlugin.Filters.%s.%s', $nopersistController, $noPersistAction)); + } + } + } + } + } } diff --git a/src/Model/Behavior/FilteredBehavior.php b/src/Model/Behavior/FilteredBehavior.php index 1135910..b525a74 100644 --- a/src/Model/Behavior/FilteredBehavior.php +++ b/src/Model/Behavior/FilteredBehavior.php @@ -11,488 +11,488 @@ use Cake\ORM\Table; /** - CakePHP Filter Plugin + CakePHP Filter Plugin - Copyright (C) 2009-3827 dr. Hannibal Lecter / lecterror - + Copyright (C) 2009-3827 dr. Hannibal Lecter / lecterror + - Multi-licensed under: - MPL - LGPL - GPL + Multi-licensed under: + MPL + LGPL + GPL */ class FilteredBehavior extends Behavior { - /** - * Keeps current values after filter form post. - * - * @var mixed[] - */ - protected $_filterValues = array(); - - /** - * 2.x compartible settings (supports having dots in the keys, f.ex. 'Model.id'). - * - * @var mixed[] - */ - public $settings = []; - - /** - * {@inheritDoc} - * - * @param mixed[] $settings The configuration settings provided to this behavior. - * @return void - */ - public function initialize(array $settings) - { - foreach ($settings as $key => $value) - { - if (!is_array($value)) - { - $key = $value; - $value = array(); - } - - $this->settings[$this->getTable()->getAlias()][$key] = array_merge - ( - array - ( - 'type' => 'text', - 'condition' => 'like', - 'required' => false, - 'selectOptions' => array() - ), - $value - ); - } - - $this->_filterValues[$this->getTable()->getAlias()] = array(); - } - - /** - * {@inheritDoc} - * - * Callback method that listens to the `beforeFind` event in the bound - * table. It modifies the passed query by applying search filters. - * - * @param \Cake\Event\Event $event The beforeFind event that was fired. - * @param \Cake\ORM\Query $Query Query. - * @param \ArrayObject $options The options for the query. - * @return void - */ - public function beforeFind(Event $event, Query $Query, ArrayObject $options) - { - if (isset($Query->getOptions()['nofilter']) && $Query->getOptions()['nofilter'] === true) - { - return; - } - $Table = $this->getTable(); - $alias = $Table->getAlias(); - if (method_exists($Table, 'beforeDataFilter')) - { - $callbackOptions['values'] = $this->_filterValues[$alias]; - $callbackOptions['settings'] = $this->settings[$alias]; - - if (!$Table->beforeDataFilter($Query, $callbackOptions)) - { - return; - } - } - - if (!isset($this->settings[$alias])) - { - return; - } - - $settings = $this->settings[$alias]; - $values = $this->_filterValues[$alias]; - - foreach ($settings as $field => $options) - { - $this->addFieldToFilter($Table, $Query, $values, $field, $options); - } - - if (method_exists($Table, 'afterDataFilter')) - { - $callbackOptions['values'] = $this->_filterValues[$alias]; - $callbackOptions['settings'] = $this->settings[$alias]; - - $Table->afterDataFilter($Query, $callbackOptions); - } - } - - /** - * Adds field filters. - * - * @param \Cake\ORM\Table $Table Model table object. - * @param \Cake\ORM\Query $Query Query object. - * @param mixed[] $values Filter values. - * @param string $field Field name. - * @param mixed[] $fieldOptions Field options. - * @return void - */ - protected function addFieldToFilter(Table $Table, Query $Query, $values, $field, $fieldOptions) - { - $configurationModelName = $Table->getAlias(); - $configurationFieldName = $field; - - if (strpos($field, '.') !== false) - { - list($configurationModelName, $configurationFieldName) = explode('.', $field); - } - - if (!isset($values[$configurationModelName][$configurationFieldName]) && isset($fieldOptions['default'])) - { - $values[$configurationModelName][$configurationFieldName] = $fieldOptions['default']; - } - - if ($fieldOptions['required'] && !isset($values[$configurationModelName][$configurationFieldName])) - { - // TODO: implement a bit of a user friendly handling of this scenario.. - trigger_error(sprintf('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 != $Table->getAlias()) - { - if ($Table->hasAssociation($configurationModelName)) { - $relationType = $Table->getAssociation($configurationModelName)->type(); - if ($relationType == Association::MANY_TO_MANY) { - $linkModelName = $Table->{$configurationModelName}->junction()->getAlias(); - } - $filterModelName = 'Filter'.$configurationModelName; - } - } - - if (isset($fieldOptions['filterField'])) - { - if (strpos($fieldOptions['filterField'], '.') !== false) - { - list($filterModelName, $filterFieldName) = explode('.', $fieldOptions['filterField']); - - if ($filterModelName != $Table->getAlias()) - { - $filterModelName = 'Filter'.$filterModelName; - } - } - else - { - $filterModelName = $Table->getAlias(); - $filterFieldName = $fieldOptions['filterField']; - } - } - - $realFilterField = sprintf('%s.%s', $filterModelName, $filterFieldName); - if ($Table->hasAssociation($configurationModelName)) - { - $relatedModel = $Table->{$configurationModelName}->getTarget(); - if (!$this->__isAlreadyJoined($Query, $relatedModel)) - { - $joinStatements = $this->buildFilterJoin($Table, $relatedModel, $linkModelName); - foreach ($joinStatements as $joinStatement) - { - $Query->join($joinStatement); - } - } - } - - $this->buildFilterConditions - ( - $Query, - $realFilterField, - $fieldOptions, - $values[$configurationModelName][$configurationFieldName] - ); - } - - /** - * Checks whether the given query object already contains a given table join. - * - * @param \Cake\ORM\Query $Query Query object. - * @param \Cake\ORM\Table $Table Related model. - * @return boolean - */ - private function __isAlreadyJoined(Query $Query, Table $Table) - { - $relatedModelAlias = 'Filter' . $Table->getAlias(); - $containedAliases = array_keys($Query->getContain()); - $joinAliases = $this->__extractJoinAliases($Query); - return in_array($relatedModelAlias, $containedAliases) || in_array($relatedModelAlias, $joinAliases); - } - - /** - * Extract the JOIN clause aliases from the given query object. - * - * @param \Cake\ORM\Query $Query Query object. - * @return string[] - */ - private function __extractJoinAliases(Query $Query) - { - $aliases = []; - $joins = $Query->clause('join'); - foreach ($joins as $join) { - if (array_key_exists('alias', $join)) { - $aliases[] = $join['alias']; - } - } - return $aliases; - } - - /** - * Build join conditions from Model to relatedModel. - * - * @param \Cake\ORM\Table $Table Model table object. - * @param \Cake\ORM\Table $RelatedTable Related model table object. - * @param string $linkModelName Linked model name (alias) in MANY_TO_MANY association. - * @return mixed[] Cake join array. - */ - protected function buildFilterJoin(Table $Table, Table $RelatedTable, $linkModelName) - { - $conditions = array(); - $alias = $Table->getAlias(); - $primaryKey = $Table->getPrimaryKey(); - $relatedTableAlias = $RelatedTable->getAlias(); - $relatedModelAlias = null; - $relationType = null; - $association = null; - $foreignKey = null; - $associationPrimaryKey = null; - $associationConditions = null; - if (!$Table->hasAssociation($relatedTableAlias)) { - return []; - } - $relatedModelAlias = 'Filter'.$relatedTableAlias; - $association = $Table->getAssociation($relatedTableAlias); - $linkModelAlias = null; - if (!empty($linkModelName) && ($association instanceof BelongsToMany)) - { - $linkModelAlias = $association->junction()->getAlias(); - } - $relationType = $association->type(); - $foreignKey = $association->getForeignKey(); - $associationConditions = $association->getConditions(); - $associationPrimaryKey = $RelatedTable->getPrimaryKey(); - $linkConditions = []; - if (!empty($foreignKey) && is_string($foreignKey)) - { - if ($relationType == Association::MANY_TO_ONE && is_string($associationPrimaryKey)) - { - $conditions[] = sprintf - ( - '%s.%s = %s.%s', - $alias, $foreignKey, - $relatedModelAlias, $associationPrimaryKey - ); - } - else if ( - in_array($relationType, [Association::ONE_TO_MANY, Association::ONE_TO_ONE]) && - is_string($primaryKey) - ) { - $conditions[] = sprintf - ( - '%s.%s = %s.%s', - $alias, $primaryKey, - $relatedModelAlias, $foreignKey - ); - } - else if ( - $relationType == Association::MANY_TO_MANY && - is_string($primaryKey) && - is_string($associationPrimaryKey) - ) { - $associationForeignKey = $RelatedTable->getAssociation($alias)->getForeignKey(); - if (is_string($associationForeignKey)) { - $conditions[] = sprintf - ( - '%s.%s = %s.%s', - $linkModelAlias, $associationForeignKey, - $relatedModelAlias, $associationPrimaryKey - ); - } - - $linkConditions[] = sprintf - ( - '%s.%s = %s.%s', - $alias, $primaryKey, - $linkModelAlias, $foreignKey - ); - } - } - - // merge any custom conditions from the relation, but change - // the alias to our $relatedModelAlias - if (!empty($associationConditions)) - { - $customConditions = $associationConditions; - - if (!is_array($associationConditions)) - { - $customConditions = array($customConditions); - } - $formatAlias = sprintf('#(? $RelatedTable->getTable(), - 'alias' => $relatedModelAlias, - 'type' => 'LEFT', - 'conditions' => $conditions, - ) - ); - - if (!empty($linkModelName) && ($association instanceof BelongsToMany)) - { - $return = array - ( - array - ( - 'table' => $association->junction()->getTable(), - 'alias' => $linkModelAlias, - 'type' => 'LEFT', - 'conditions' => $linkConditions, - ), - array - ( - 'table' => $RelatedTable->getTable(), - 'alias' => $relatedModelAlias, - 'type' => 'LEFT', - 'conditions' => $conditions, - ) - ); - } - return $return; - } - - /** - * Build query conditions and add them to $Query. - * - * @param \Cake\ORM\Query $Query Cake query array. - * @param string $field Filter field. - * @param mixed[] $options Configuration options for this field. - * @param mixed $value Field value. - * @return void - */ - protected function buildFilterConditions(Query $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 'select': - if (is_string($value) && strlen(trim(strval($value))) == 0) - { - break; - } - if (is_array($value)) { - $Query->andWhere([$field . ' IN' => $value]); - } else { - $Query->andWhere([$field => $value]); - } - break; - case 'checkbox': - if (is_array($value)) { - $Query->andWhere([$field . ' IN' => $value]); - } else { - $Query->andWhere([$field => $value]); - } - break; - default: - 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->andWhere([$formattedField => $formattedValue]); - break; - default: - { - $Query->andWhere([$field.' '.$condition => $value]); - } - break; - } - - break; - } - } - - /** - * Sets filter values. - * - * @param mixed[] $values Filter values. - * @return void - */ - public function setFilterValues($values = array()) - { - $alias = $this->getTable()->getAlias(); - $this->_filterValues[$alias] = array_merge($this->_filterValues[$alias], (array)$values); - } - - /** - * Gets filter values. - * - * @return mixed[] - */ - public function getFilterValues() - { - return $this->_filterValues; - } + /** + * Keeps current values after filter form post. + * + * @var mixed[] + */ + protected $_filterValues = array(); + + /** + * 2.x compartible settings (supports having dots in the keys, f.ex. 'Model.id'). + * + * @var mixed[] + */ + public $settings = []; + + /** + * {@inheritDoc} + * + * @param mixed[] $settings The configuration settings provided to this behavior. + * @return void + */ + public function initialize(array $settings) + { + foreach ($settings as $key => $value) + { + if (!is_array($value)) + { + $key = $value; + $value = array(); + } + + $this->settings[$this->getTable()->getAlias()][$key] = array_merge + ( + array + ( + 'type' => 'text', + 'condition' => 'like', + 'required' => false, + 'selectOptions' => array() + ), + $value + ); + } + + $this->_filterValues[$this->getTable()->getAlias()] = array(); + } + + /** + * {@inheritDoc} + * + * Callback method that listens to the `beforeFind` event in the bound + * table. It modifies the passed query by applying search filters. + * + * @param \Cake\Event\Event $event The beforeFind event that was fired. + * @param \Cake\ORM\Query $Query Query. + * @param \ArrayObject $options The options for the query. + * @return void + */ + public function beforeFind(Event $event, Query $Query, ArrayObject $options) + { + if (isset($Query->getOptions()['nofilter']) && $Query->getOptions()['nofilter'] === true) + { + return; + } + $Table = $this->getTable(); + $alias = $Table->getAlias(); + if (method_exists($Table, 'beforeDataFilter')) + { + $callbackOptions['values'] = $this->_filterValues[$alias]; + $callbackOptions['settings'] = $this->settings[$alias]; + + if (!$Table->beforeDataFilter($Query, $callbackOptions)) + { + return; + } + } + + if (!isset($this->settings[$alias])) + { + return; + } + + $settings = $this->settings[$alias]; + $values = $this->_filterValues[$alias]; + + foreach ($settings as $field => $options) + { + $this->addFieldToFilter($Table, $Query, $values, $field, $options); + } + + if (method_exists($Table, 'afterDataFilter')) + { + $callbackOptions['values'] = $this->_filterValues[$alias]; + $callbackOptions['settings'] = $this->settings[$alias]; + + $Table->afterDataFilter($Query, $callbackOptions); + } + } + + /** + * Adds field filters. + * + * @param \Cake\ORM\Table $Table Model table object. + * @param \Cake\ORM\Query $Query Query object. + * @param mixed[] $values Filter values. + * @param string $field Field name. + * @param mixed[] $fieldOptions Field options. + * @return void + */ + protected function addFieldToFilter(Table $Table, Query $Query, $values, $field, $fieldOptions) + { + $configurationModelName = $Table->getAlias(); + $configurationFieldName = $field; + + if (strpos($field, '.') !== false) + { + list($configurationModelName, $configurationFieldName) = explode('.', $field); + } + + if (!isset($values[$configurationModelName][$configurationFieldName]) && isset($fieldOptions['default'])) + { + $values[$configurationModelName][$configurationFieldName] = $fieldOptions['default']; + } + + if ($fieldOptions['required'] && !isset($values[$configurationModelName][$configurationFieldName])) + { + // TODO: implement a bit of a user friendly handling of this scenario.. + trigger_error(sprintf('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 != $Table->getAlias()) + { + if ($Table->hasAssociation($configurationModelName)) { + $relationType = $Table->getAssociation($configurationModelName)->type(); + if ($relationType == Association::MANY_TO_MANY) { + $linkModelName = $Table->{$configurationModelName}->junction()->getAlias(); + } + $filterModelName = 'Filter'.$configurationModelName; + } + } + + if (isset($fieldOptions['filterField'])) + { + if (strpos($fieldOptions['filterField'], '.') !== false) + { + list($filterModelName, $filterFieldName) = explode('.', $fieldOptions['filterField']); + + if ($filterModelName != $Table->getAlias()) + { + $filterModelName = 'Filter'.$filterModelName; + } + } + else + { + $filterModelName = $Table->getAlias(); + $filterFieldName = $fieldOptions['filterField']; + } + } + + $realFilterField = sprintf('%s.%s', $filterModelName, $filterFieldName); + if ($Table->hasAssociation($configurationModelName)) + { + $relatedModel = $Table->{$configurationModelName}->getTarget(); + if (!$this->__isAlreadyJoined($Query, $relatedModel)) + { + $joinStatements = $this->buildFilterJoin($Table, $relatedModel, $linkModelName); + foreach ($joinStatements as $joinStatement) + { + $Query->join($joinStatement); + } + } + } + + $this->buildFilterConditions + ( + $Query, + $realFilterField, + $fieldOptions, + $values[$configurationModelName][$configurationFieldName] + ); + } + + /** + * Checks whether the given query object already contains a given table join. + * + * @param \Cake\ORM\Query $Query Query object. + * @param \Cake\ORM\Table $Table Related model. + * @return boolean + */ + private function __isAlreadyJoined(Query $Query, Table $Table) + { + $relatedModelAlias = 'Filter' . $Table->getAlias(); + $containedAliases = array_keys($Query->getContain()); + $joinAliases = $this->__extractJoinAliases($Query); + return in_array($relatedModelAlias, $containedAliases) || in_array($relatedModelAlias, $joinAliases); + } + + /** + * Extract the JOIN clause aliases from the given query object. + * + * @param \Cake\ORM\Query $Query Query object. + * @return string[] + */ + private function __extractJoinAliases(Query $Query) + { + $aliases = []; + $joins = $Query->clause('join'); + foreach ($joins as $join) { + if (array_key_exists('alias', $join)) { + $aliases[] = $join['alias']; + } + } + return $aliases; + } + + /** + * Build join conditions from Model to relatedModel. + * + * @param \Cake\ORM\Table $Table Model table object. + * @param \Cake\ORM\Table $RelatedTable Related model table object. + * @param string $linkModelName Linked model name (alias) in MANY_TO_MANY association. + * @return mixed[] Cake join array. + */ + protected function buildFilterJoin(Table $Table, Table $RelatedTable, $linkModelName) + { + $conditions = array(); + $alias = $Table->getAlias(); + $primaryKey = $Table->getPrimaryKey(); + $relatedTableAlias = $RelatedTable->getAlias(); + $relatedModelAlias = null; + $relationType = null; + $association = null; + $foreignKey = null; + $associationPrimaryKey = null; + $associationConditions = null; + if (!$Table->hasAssociation($relatedTableAlias)) { + return []; + } + $relatedModelAlias = 'Filter'.$relatedTableAlias; + $association = $Table->getAssociation($relatedTableAlias); + $linkModelAlias = null; + if (!empty($linkModelName) && ($association instanceof BelongsToMany)) + { + $linkModelAlias = $association->junction()->getAlias(); + } + $relationType = $association->type(); + $foreignKey = $association->getForeignKey(); + $associationConditions = $association->getConditions(); + $associationPrimaryKey = $RelatedTable->getPrimaryKey(); + $linkConditions = []; + if (!empty($foreignKey) && is_string($foreignKey)) + { + if ($relationType == Association::MANY_TO_ONE && is_string($associationPrimaryKey)) + { + $conditions[] = sprintf + ( + '%s.%s = %s.%s', + $alias, $foreignKey, + $relatedModelAlias, $associationPrimaryKey + ); + } + else if ( + in_array($relationType, [Association::ONE_TO_MANY, Association::ONE_TO_ONE]) && + is_string($primaryKey) + ) { + $conditions[] = sprintf + ( + '%s.%s = %s.%s', + $alias, $primaryKey, + $relatedModelAlias, $foreignKey + ); + } + else if ( + $relationType == Association::MANY_TO_MANY && + is_string($primaryKey) && + is_string($associationPrimaryKey) + ) { + $associationForeignKey = $RelatedTable->getAssociation($alias)->getForeignKey(); + if (is_string($associationForeignKey)) { + $conditions[] = sprintf + ( + '%s.%s = %s.%s', + $linkModelAlias, $associationForeignKey, + $relatedModelAlias, $associationPrimaryKey + ); + } + + $linkConditions[] = sprintf + ( + '%s.%s = %s.%s', + $alias, $primaryKey, + $linkModelAlias, $foreignKey + ); + } + } + + // merge any custom conditions from the relation, but change + // the alias to our $relatedModelAlias + if (!empty($associationConditions)) + { + $customConditions = $associationConditions; + + if (!is_array($associationConditions)) + { + $customConditions = array($customConditions); + } + $formatAlias = sprintf('#(? $RelatedTable->getTable(), + 'alias' => $relatedModelAlias, + 'type' => 'LEFT', + 'conditions' => $conditions, + ) + ); + + if (!empty($linkModelName) && ($association instanceof BelongsToMany)) + { + $return = array + ( + array + ( + 'table' => $association->junction()->getTable(), + 'alias' => $linkModelAlias, + 'type' => 'LEFT', + 'conditions' => $linkConditions, + ), + array + ( + 'table' => $RelatedTable->getTable(), + 'alias' => $relatedModelAlias, + 'type' => 'LEFT', + 'conditions' => $conditions, + ) + ); + } + return $return; + } + + /** + * Build query conditions and add them to $Query. + * + * @param \Cake\ORM\Query $Query Cake query array. + * @param string $field Filter field. + * @param mixed[] $options Configuration options for this field. + * @param mixed $value Field value. + * @return void + */ + protected function buildFilterConditions(Query $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 'select': + if (is_string($value) && strlen(trim(strval($value))) == 0) + { + break; + } + if (is_array($value)) { + $Query->andWhere([$field . ' IN' => $value]); + } else { + $Query->andWhere([$field => $value]); + } + break; + case 'checkbox': + if (is_array($value)) { + $Query->andWhere([$field . ' IN' => $value]); + } else { + $Query->andWhere([$field => $value]); + } + break; + default: + 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->andWhere([$formattedField => $formattedValue]); + break; + default: + { + $Query->andWhere([$field.' '.$condition => $value]); + } + break; + } + + break; + } + } + + /** + * Sets filter values. + * + * @param mixed[] $values Filter values. + * @return void + */ + public function setFilterValues($values = array()) + { + $alias = $this->getTable()->getAlias(); + $this->_filterValues[$alias] = array_merge($this->_filterValues[$alias], (array)$values); + } + + /** + * Gets filter values. + * + * @return mixed[] + */ + public function getFilterValues() + { + return $this->_filterValues; + } } diff --git a/src/View/Elements/filter_form_begin.ctp b/src/View/Elements/filter_form_begin.ctp new file mode 100644 index 0000000..a2e3cd4 --- /dev/null +++ b/src/View/Elements/filter_form_begin.ctp @@ -0,0 +1,34 @@ + + + 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)); ?> diff --git a/src/View/Elements/filter_form_end.ctp b/src/View/Elements/filter_form_end.ctp new file mode 100644 index 0000000..85c8f9d --- /dev/null +++ b/src/View/Elements/filter_form_end.ctp @@ -0,0 +1,17 @@ + + + Multi-licensed under: + MPL + LGPL + GPL +*/ +?> +
+ Form->submit(__('Submit')); ?> + Form->end(); ?> +
diff --git a/src/View/Elements/filter_form_fields.ctp b/src/View/Elements/filter_form_fields.ctp new file mode 100644 index 0000000..62daa80 --- /dev/null +++ b/src/View/Elements/filter_form_fields.ctp @@ -0,0 +1,27 @@ + + + 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']); + } + } +} diff --git a/src/View/Helper/FilterHelper.php b/src/View/Helper/FilterHelper.php new file mode 100644 index 0000000..8246ab4 --- /dev/null +++ b/src/View/Helper/FilterHelper.php @@ -0,0 +1,118 @@ + + + Multi-licensed under: + MPL + LGPL + GPL +*/ + +class FilterHelper extends Helper +{ + /** + * @param string $modelName + * @param mixed[] $options + * @return string + */ + public function filterForm($modelName, $options) + { + $view =& $this->_View; + + $output = $view->element + ( + 'filter_form_begin', + array + ( + 'plugin' => 'Filter', + 'modelName' => $modelName, + 'options' => $options + ), + array('plugin' => 'Filter') + ); + + $output .= $view->element + ( + 'filter_form_fields', + array('plugin' => 'Filter'), + array('plugin' => 'Filter') + ); + + $output .= $view->element + ( + 'filter_form_end', + array('plugin' => 'Filter'), + array('plugin' => 'Filter') + ); + + return $output; + } + + /** + * @param string $modelName + * @param mixed[] $options + * @return string + */ + public function beginForm($modelName, $options) + { + $view =& $this->_View; + $output = $view->element + ( + 'filter_form_begin', + array + ( + 'plugin' => 'Filter', + 'modelName' => $modelName, + 'options' => $options + ), + array('plugin' => 'Filter') + ); + + return $output; + } + + /** + * @param string[] $fields + * @return string + */ + public function inputFields($fields = array()) + { + $view =& $this->_View; + $output = $view->element + ( + 'filter_form_fields', + array + ( + 'plugin' => 'Filter', + 'includeFields' => $fields + ), + array('plugin' => 'Filter') + ); + + return $output; + } + + /** + * @return string + */ + public function endForm() + { + $view = $this->_View; + $output = $view->element + ( + 'filter_form_end', + array(), + array('plugin' => 'Filter') + ); + + return $output; + } +} From 543a3f5d03e34dcc27f8681f9b4e01cc4d4bd561 Mon Sep 17 00:00:00 2001 From: bancer Date: Mon, 28 Aug 2023 15:54:24 +0200 Subject: [PATCH 26/37] Replace tabs with spaces --- tests/Fixture/DocumentCategoriesFixture.php | 60 +- tests/Fixture/DocumentsFixture.php | 72 +- tests/Fixture/ItemsFixture.php | 70 +- tests/Fixture/MetadataFixture.php | 64 +- .../Component/FilterComponentTest.php | 1434 ++++++------ .../MockObjects/DocumentCategoriesTable.php | 46 +- .../MockObjects/DocumentTestsController.php | 78 +- .../TestCase/MockObjects/Documents2Table.php | 52 +- .../TestCase/MockObjects/Documents3Table.php | 76 +- tests/TestCase/MockObjects/DocumentsTable.php | 24 +- tests/TestCase/MockObjects/ItemsTable.php | 20 +- tests/TestCase/MockObjects/MetadataTable.php | 20 +- .../Model/Behavior/FilteredBehaviorTest.php | 1920 ++++++++--------- 13 files changed, 1968 insertions(+), 1968 deletions(-) diff --git a/tests/Fixture/DocumentCategoriesFixture.php b/tests/Fixture/DocumentCategoriesFixture.php index cf18be7..9a223c4 100644 --- a/tests/Fixture/DocumentCategoriesFixture.php +++ b/tests/Fixture/DocumentCategoriesFixture.php @@ -5,41 +5,41 @@ use Cake\TestSuite\Fixture\TestFixture; /** - CakePHP Filter Plugin + CakePHP Filter Plugin - Copyright (C) 2009-3827 dr. Hannibal Lecter / lecterror - + Copyright (C) 2009-3827 dr. Hannibal Lecter / lecterror + - Multi-licensed under: - MPL - LGPL - GPL + Multi-licensed under: + MPL + LGPL + GPL */ class DocumentCategoriesFixture extends TestFixture { - /** - * @var mixed[] - */ - public $fields = array - ( - 'id' => array('type' => 'integer'), - 'title' => array('type' => 'string', 'length' => 100, 'null' => false), - 'description' => array('type' => 'string', 'length' => 255), - '_constraints' => [ - 'primary' => ['type' => 'primary', 'columns' => ['id']], - ], - ); + /** + * @var mixed[] + */ + public $fields = array + ( + 'id' => array('type' => 'integer'), + 'title' => array('type' => 'string', 'length' => 100, 'null' => false), + 'description' => array('type' => 'string', 'length' => 255), + '_constraints' => [ + 'primary' => ['type' => 'primary', 'columns' => ['id']], + ], + ); - /** - * @var (int|string)[][] - */ - 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 (int|string)[][] + */ + 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/tests/Fixture/DocumentsFixture.php b/tests/Fixture/DocumentsFixture.php index 3c86a15..6172f95 100644 --- a/tests/Fixture/DocumentsFixture.php +++ b/tests/Fixture/DocumentsFixture.php @@ -5,47 +5,47 @@ use Cake\TestSuite\Fixture\TestFixture; /** - CakePHP Filter Plugin + CakePHP Filter Plugin - Copyright (C) 2009-3827 dr. Hannibal Lecter / lecterror - + Copyright (C) 2009-3827 dr. Hannibal Lecter / lecterror + - Multi-licensed under: - MPL - LGPL - GPL + Multi-licensed under: + MPL + LGPL + GPL */ class DocumentsFixture extends TestFixture { - /** - * @var mixed[] - */ - public $fields = array - ( - 'id' => array('type' => 'integer'), - '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), - '_constraints' => [ - 'primary' => ['type' => 'primary', 'columns' => ['id']], - ], - ); + /** + * @var mixed[] + */ + public $fields = array + ( + 'id' => array('type' => 'integer'), + '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), + '_constraints' => [ + 'primary' => ['type' => 'primary', 'columns' => ['id']], + ], + ); - /** - * @var (int|string)[][] - */ - 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 (int|string)[][] + */ + 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/tests/Fixture/ItemsFixture.php b/tests/Fixture/ItemsFixture.php index c441337..5a42918 100644 --- a/tests/Fixture/ItemsFixture.php +++ b/tests/Fixture/ItemsFixture.php @@ -5,46 +5,46 @@ use Cake\TestSuite\Fixture\TestFixture; /** - CakePHP Filter Plugin + CakePHP Filter Plugin - Copyright (C) 2009-3827 dr. Hannibal Lecter / lecterror - + Copyright (C) 2009-3827 dr. Hannibal Lecter / lecterror + - Multi-licensed under: - MPL - LGPL - GPL + Multi-licensed under: + MPL + LGPL + GPL */ class ItemsFixture extends TestFixture { - /** - * @var mixed[] - */ - public $fields = array - ( - 'id' => array('type' => 'integer'), - 'document_id' => array('type' => 'integer', 'null' => false), - 'code' => array('type' => 'string', 'length' => '20', 'null' => false), - '_constraints' => [ - 'primary' => ['type' => 'primary', 'columns' => ['id']], - ], - ); + /** + * @var mixed[] + */ + public $fields = array + ( + 'id' => array('type' => 'integer'), + 'document_id' => array('type' => 'integer', 'null' => false), + 'code' => array('type' => 'string', 'length' => '20', 'null' => false), + '_constraints' => [ + 'primary' => ['type' => 'primary', 'columns' => ['id']], + ], + ); - /** - * @var (int|string)[][] - */ - 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 (int|string)[][] + */ + 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/tests/Fixture/MetadataFixture.php b/tests/Fixture/MetadataFixture.php index f117349..b4ee141 100644 --- a/tests/Fixture/MetadataFixture.php +++ b/tests/Fixture/MetadataFixture.php @@ -5,43 +5,43 @@ use Cake\TestSuite\Fixture\TestFixture; /** - CakePHP Filter Plugin + CakePHP Filter Plugin - Copyright (C) 2009-3827 dr. Hannibal Lecter / lecterror - + Copyright (C) 2009-3827 dr. Hannibal Lecter / lecterror + - Multi-licensed under: - MPL - LGPL - GPL + Multi-licensed under: + MPL + LGPL + GPL */ class MetadataFixture extends TestFixture { - /** - * @var mixed[] - */ - public $fields = array - ( - 'id' => array('type' => 'integer'), - '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), - '_constraints' => [ - 'primary' => ['type' => 'primary', 'columns' => ['id']], - ], - ); + /** + * @var mixed[] + */ + public $fields = array + ( + 'id' => array('type' => 'integer'), + '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), + '_constraints' => [ + 'primary' => ['type' => 'primary', 'columns' => ['id']], + ], + ); - /** - * @var (int|string)[][] - */ - 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 (int|string)[][] + */ + 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/tests/TestCase/Controller/Component/FilterComponentTest.php b/tests/TestCase/Controller/Component/FilterComponentTest.php index 93b63fb..0c37045 100644 --- a/tests/TestCase/Controller/Component/FilterComponentTest.php +++ b/tests/TestCase/Controller/Component/FilterComponentTest.php @@ -9,727 +9,727 @@ use Filter\Test\TestCase\MockObjects\DocumentsTable; /** - CakePHP Filter Plugin + CakePHP Filter Plugin - Copyright (C) 2009-3827 dr. Hannibal Lecter / lecterror - + Copyright (C) 2009-3827 dr. Hannibal Lecter / lecterror + - Multi-licensed under: - MPL - LGPL - GPL + Multi-licensed under: + MPL + LGPL + GPL */ class FilterComponentTest extends TestCase { - /** - * @var string[] - */ - public $fixtures = array - ( - 'plugin.Filter.DocumentCategories', - 'plugin.Filter.Documents', - 'plugin.Filter.Items', - 'plugin.Filter.Metadata', - ); - - /** - * @var \Filter\Test\TestCase\MockObjects\DocumentTestsController - */ - public $Controller = null; - - public function setUp() - { - parent::setUp(); - $request = new ServerRequest([ - 'params' => [ - 'controller' => 'DocumentTests', - 'action' => 'index', - ], - ]); - $this->Controller = new DocumentTestsController($request); - } - - public function tearDown() - { - parent::tearDown(); - $this->Controller->getRequest()->getSession()->destroy(); - unset($this->Controller); - } - - /** - * Test bailing out when no filters are present. - * - * @return void - */ - public function testNoFilters() - { - $this->assertEmpty($this->Controller->Filter->settings); - $this->assertFalse($this->Controller->Document->hasBehavior('Filtered')); - - $this->assertFalse(in_array('Filter.Filter', $this->Controller->viewBuilder()->getHelpers())); - } - - /** - * @return void - */ - public function testNoActionFilters() - { - $testSettings = array - ( - 'someotheraction' => array - ( - 'Document' => array - ( - 'Document.title' => array('type' => 'text') - ) - ) - ); - - $this->Controller->filters = $testSettings; - $this->Controller->dispatchEvent('Controller.initialize'); - $this->assertFalse($this->Controller->Document->hasBehavior('Filtered')); - - $testSettings = array - ( - 'index' => array - ( - 'Document' => array - ( - 'Document.title' => array('type' => 'text') - ) - ), - ); - - $this->Controller->filters = $testSettings; - $this->Controller->dispatchEvent('Controller.initialize'); - $this->assertTrue($this->Controller->Document->hasBehavior('Filtered')); - } - - /** - * Test basic filter settings. - * - * @return void - */ - public function testBasicFilters() - { - $testSettings = array - ( - 'index' => array - ( - 'Document' => array - ( - 'Document.title' => array('type' => 'text') - ) - ) - ); - $this->Controller->filters = $testSettings; - - $expected = array - ( - $this->Controller->getName() => $testSettings - ); - $this->Controller->dispatchEvent('Controller.initialize'); - $this->assertEquals($expected, $this->Controller->Filter->settings); - } - - /** - * Test running a component with no filter data. - * - * @return void - */ - public function testEmptyStartup() - { - $testSettings = array - ( - 'index' => array - ( - 'Document' => array - ( - 'Document.title' => array('type' => 'text') - ) - ) - ); - $this->Controller->filters = $testSettings; - - $this->Controller->dispatchEvent('Controller.initialize'); - $this->Controller->dispatchEvent('Controller.startup'); - $this->assertTrue(in_array('Filter.Filter', $this->Controller->viewBuilder()->getHelpers())); - } - - /** - * @return void - */ - public function testSessionStartupDataFakeNonexistantModel() - { - $testSettings = array - ( - 'index' => array - ( - 'FakeNonexistant' => array - ( - 'drink' => array('type' => 'select') - ) - ) - ); - $this->Controller->filters = $testSettings; - $sessionKey = sprintf( - 'FilterPlugin.Filters.%s.%s', - $this->Controller->getName(), - $this->Controller->getRequest()->getParam('action') - ); - $filterValues = array(); - $this->Controller->getRequest()->getSession()->write($sessionKey, $filterValues); - $this->expectException('PHPUnit\Framework\Error\Notice'); - $this->Controller->dispatchEvent('Controller.initialize'); - } - - /** - * Test loading filter data from session (both full and empty). - * - * @return void - */ - public function testSessionStartupData() - { - $testSettings = array - ( - 'index' => array - ( - 'Document' => array - ( - 'Document.title' => array('type' => 'text') - ), - ) - ); - $this->Controller->filters = $testSettings; - - $sessionKey = sprintf( - 'FilterPlugin.Filters.%s.%s', - $this->Controller->getName(), - $this->Controller->getRequest()->getParam('action') - ); - - $filterValues = array(); - $this->Controller->getRequest()->getSession()->write($sessionKey, $filterValues); - $this->Controller->dispatchEvent('Controller.initialize'); - - $this->Controller->dispatchEvent('Controller.startup'); - $actualFilterValues = $this->Controller->Document->getFilterValues(); - $this->assertEquals - ( - $filterValues, - $actualFilterValues[$this->Controller->Document->getAlias()] - ); - - $filterValues = array('Document' => array('title' => 'in')); - $this->Controller->getRequest()->getSession()->write($sessionKey, $filterValues); - - $this->Controller->dispatchEvent('Controller.startup'); - $actualFilterValues = $this->Controller->Document->getFilterValues(); - $this->assertEquals - ( - $filterValues, - $actualFilterValues[$this->Controller->Document->getAlias()] - ); - - $this->Controller->getRequest()->getSession()->delete($sessionKey); - } - - /** - * Test loading filter data from a post request. - * - * @return void - */ - public function testPostStartupData() - { - $request = new ServerRequest([ - 'params' => [ - 'controller' => 'DocumentTests', - 'action' => 'index', - ], - 'environment' => [ - 'REQUEST_METHOD' => 'POST', - ], - ]); - $this->Controller = new DocumentTestsController($request); - $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->request = $this->Controller->getRequest()->withParsedBody($filterValues); - - $this->Controller->dispatchEvent('Controller.initialize'); - $this->Controller->dispatchEvent('Controller.startup'); - - $sessionKey = sprintf( - 'FilterPlugin.Filters.%s.%s', - $this->Controller->getName(), - $this->Controller->getRequest()->getParam('action') - ); - $sessionData = $this->Controller->getRequest()->getSession()->read($sessionKey); - $this->assertEquals($filterValues, $sessionData); - - $actualFilterValues = $this->Controller->Document->getFilterValues(); - $this->assertEquals - ( - $filterValues, - $actualFilterValues[$this->Controller->Document->getAlias()] - ); - } - - /** - * Test exiting beforeRender when in an action with no settings. - * - * @return void - */ - public function testBeforeRenderAbort() - { - $testSettings = array - ( - 'veryMuchNotIndex' => array - ( - 'Document' => array - ( - 'Document.title' => array('type' => 'text') - ) - ) - ); - $this->Controller->filters = $testSettings; - - $this->Controller->dispatchEvent('Controller.initialize'); - $this->Controller->dispatchEvent('Controller.startup'); - $this->Controller->dispatchEvent('Controller.beforeRender'); - - $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. - * - * @return void - */ - public function testNoModelFound() - { - $testSettings = array - ( - 'index' => array - ( - 'ThisModelDoesNotExist' => array - ( - 'ThisModelDoesNotExist.title' => array('type' => 'text') - ) - ) - ); - $this->Controller->filters = $testSettings; - $this->expectException('PHPUnit\Framework\Error\Notice'); - $this->Controller->dispatchEvent('Controller.initialize'); - } - - /** - * Test the view variable generation for very basic filtering. - * Also tests model name detection and custom label. - * - * @return void - */ - public function testBasicViewInfo() - { - $testSettings = array - ( - 'index' => array - ( - 'Document' => array - ( - 'title', - 'DocumentCategory.id' => array( - 'type' => 'select', - 'label' => 'Category', - 'className' => DocumentCategoriesTable::class, - ), - ) - ) - ); - $this->Controller->filters = $testSettings; - - $this->Controller->dispatchEvent('Controller.initialize'); - $this->Controller->dispatchEvent('Controller.startup'); - $this->Controller->dispatchEvent('Controller.beforeRender'); - - $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. - * - * @return void - */ - public function testAdditionalInputOptions() - { - $testSettings = array - ( - 'index' => array - ( - 'Document' => array - ( - 'title' => array('inputOptions' => 'disabled'), - 'DocumentCategory.id' => array - ( - 'type' => 'select', - 'label' => 'Category', - 'inputOptions' => array('class' => 'important'), - 'className' => DocumentCategoriesTable::class, - ), - ) - ) - ); - $this->Controller->filters = $testSettings; - - $this->Controller->dispatchEvent('Controller.initialize'); - $this->Controller->dispatchEvent('Controller.startup'); - $this->Controller->dispatchEvent('Controller.beforeRender'); - - $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. - * - * @return void - */ - 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' => '%!%'), - ), - 'className' => DocumentCategoriesTable::class, - ), - ) - ) - ); - $this->Controller->filters = $testSettings; - - $this->Controller->dispatchEvent('Controller.initialize'); - $this->Controller->dispatchEvent('Controller.startup'); - $this->Controller->dispatchEvent('Controller.beforeRender'); - - $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. - * - * @return void - */ - 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->dispatchEvent('Controller.initialize'); - $this->Controller->dispatchEvent('Controller.startup'); - $this->Controller->dispatchEvent('Controller.beforeRender'); - - $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. - * - * @return void - */ - public function testSelectMultiple() - { - $testSettings = array - ( - 'index' => array - ( - 'Document' => array - ( - 'DocumentCategory.id' => array - ( - 'type' => 'select', - 'multiple' => true, - ) - ) - ) - ); - $this->Controller->filters = $testSettings; - - $expected = array - ( - $this->Controller->getName() => $testSettings - ); - - $this->Controller->dispatchEvent('Controller.initialize'); - $this->assertEquals($expected, $this->Controller->Filter->settings); - } - - /** - * Test select input for the model filtered. - * - * @return void - */ - public function testSelectInputFromSameModel() - { - $testSettings = array - ( - 'index' => array - ( - 'Document' => array - ( - 'Document.title' => array - ( - 'type' => 'select', - 'className' => DocumentsTable::class, - ), - ) - ) - ); - $this->Controller->filters = $testSettings; - - $this->Controller->dispatchEvent('Controller.initialize'); - $this->Controller->dispatchEvent('Controller.startup'); - $this->Controller->dispatchEvent('Controller.beforeRender'); - - $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. - * - * @return void - */ - public function testPersistence() - { - $testSettings = array - ( - 'index' => array - ( - 'Document' => array - ( - 'Document.title' => array('type' => 'text') - ), - ), - ); - $this->Controller->filters = $testSettings; - $this->Controller->components()->unload('Filter'); - $this->Controller->loadComponent('Filter.Filter', ['nopersist' => true]); - - $sessionKey = sprintf( - 'FilterPlugin.Filters.%s.%s', - 'SomeOtherController', - $this->Controller->getRequest()->getParam('action') - ); - $filterValues = array('Document' => array('title' => 'in'), 'Filter' => array('filterFormId' => 'Document')); - $this->Controller->getRequest()->getSession()->write($sessionKey, $filterValues); - - $sessionKey = sprintf( - 'FilterPlugin.Filters.%s.%s', - $this->Controller->getName(), - $this->Controller->getRequest()->getParam('action') - ); - $filterValues = array('Document' => array('title' => 'in'), 'Filter' => array('filterFormId' => 'Document')); - $this->Controller->getRequest()->getSession()->write($sessionKey, $filterValues); - - $this->Controller->Filter->nopersist = array(); - $this->Controller->Filter->nopersist[$this->Controller->getName()] = true; - $this->Controller->Filter->nopersist['SomeOtherController'] = true; - - $this->Controller->dispatchEvent('Controller.initialize'); - $this->Controller->dispatchEvent('Controller.startup'); - - $expected = array( - $this->Controller->getName() => array( - $this->Controller->getRequest()->getParam('action') => $filterValues, - ), - ); - $this->assertEquals($expected, $this->Controller->getRequest()->getSession()->read('FilterPlugin.Filters')); - } - - /** - * Test whether filtering by belongsTo model text field - * works correctly. - * - * @return void - */ - public function testBelongsToFilteringByText() - { - $testSettings = array - ( - 'index' => array - ( - 'Document' => array - ( - 'DocumentCategory.title' => array('type' => 'text') - ), - ) - ); - $this->Controller->filters = $testSettings; - - $this->Controller->dispatchEvent('Controller.initialize'); - $this->Controller->dispatchEvent('Controller.startup'); - $this->Controller->dispatchEvent('Controller.beforeRender'); - - $expected = array - ( - array - ( - 'name' => 'DocumentCategory.title', - 'options' => array - ( - 'type' => 'text', - ) - ), - ); - - $this->assertEquals($expected, $this->Controller->viewVars['viewFilterParams']); - } + /** + * @var string[] + */ + public $fixtures = array + ( + 'plugin.Filter.DocumentCategories', + 'plugin.Filter.Documents', + 'plugin.Filter.Items', + 'plugin.Filter.Metadata', + ); + + /** + * @var \Filter\Test\TestCase\MockObjects\DocumentTestsController + */ + public $Controller = null; + + public function setUp() + { + parent::setUp(); + $request = new ServerRequest([ + 'params' => [ + 'controller' => 'DocumentTests', + 'action' => 'index', + ], + ]); + $this->Controller = new DocumentTestsController($request); + } + + public function tearDown() + { + parent::tearDown(); + $this->Controller->getRequest()->getSession()->destroy(); + unset($this->Controller); + } + + /** + * Test bailing out when no filters are present. + * + * @return void + */ + public function testNoFilters() + { + $this->assertEmpty($this->Controller->Filter->settings); + $this->assertFalse($this->Controller->Document->hasBehavior('Filtered')); + + $this->assertFalse(in_array('Filter.Filter', $this->Controller->viewBuilder()->getHelpers())); + } + + /** + * @return void + */ + public function testNoActionFilters() + { + $testSettings = array + ( + 'someotheraction' => array + ( + 'Document' => array + ( + 'Document.title' => array('type' => 'text') + ) + ) + ); + + $this->Controller->filters = $testSettings; + $this->Controller->dispatchEvent('Controller.initialize'); + $this->assertFalse($this->Controller->Document->hasBehavior('Filtered')); + + $testSettings = array + ( + 'index' => array + ( + 'Document' => array + ( + 'Document.title' => array('type' => 'text') + ) + ), + ); + + $this->Controller->filters = $testSettings; + $this->Controller->dispatchEvent('Controller.initialize'); + $this->assertTrue($this->Controller->Document->hasBehavior('Filtered')); + } + + /** + * Test basic filter settings. + * + * @return void + */ + public function testBasicFilters() + { + $testSettings = array + ( + 'index' => array + ( + 'Document' => array + ( + 'Document.title' => array('type' => 'text') + ) + ) + ); + $this->Controller->filters = $testSettings; + + $expected = array + ( + $this->Controller->getName() => $testSettings + ); + $this->Controller->dispatchEvent('Controller.initialize'); + $this->assertEquals($expected, $this->Controller->Filter->settings); + } + + /** + * Test running a component with no filter data. + * + * @return void + */ + public function testEmptyStartup() + { + $testSettings = array + ( + 'index' => array + ( + 'Document' => array + ( + 'Document.title' => array('type' => 'text') + ) + ) + ); + $this->Controller->filters = $testSettings; + + $this->Controller->dispatchEvent('Controller.initialize'); + $this->Controller->dispatchEvent('Controller.startup'); + $this->assertTrue(in_array('Filter.Filter', $this->Controller->viewBuilder()->getHelpers())); + } + + /** + * @return void + */ + public function testSessionStartupDataFakeNonexistantModel() + { + $testSettings = array + ( + 'index' => array + ( + 'FakeNonexistant' => array + ( + 'drink' => array('type' => 'select') + ) + ) + ); + $this->Controller->filters = $testSettings; + $sessionKey = sprintf( + 'FilterPlugin.Filters.%s.%s', + $this->Controller->getName(), + $this->Controller->getRequest()->getParam('action') + ); + $filterValues = array(); + $this->Controller->getRequest()->getSession()->write($sessionKey, $filterValues); + $this->expectException('PHPUnit\Framework\Error\Notice'); + $this->Controller->dispatchEvent('Controller.initialize'); + } + + /** + * Test loading filter data from session (both full and empty). + * + * @return void + */ + public function testSessionStartupData() + { + $testSettings = array + ( + 'index' => array + ( + 'Document' => array + ( + 'Document.title' => array('type' => 'text') + ), + ) + ); + $this->Controller->filters = $testSettings; + + $sessionKey = sprintf( + 'FilterPlugin.Filters.%s.%s', + $this->Controller->getName(), + $this->Controller->getRequest()->getParam('action') + ); + + $filterValues = array(); + $this->Controller->getRequest()->getSession()->write($sessionKey, $filterValues); + $this->Controller->dispatchEvent('Controller.initialize'); + + $this->Controller->dispatchEvent('Controller.startup'); + $actualFilterValues = $this->Controller->Document->getFilterValues(); + $this->assertEquals + ( + $filterValues, + $actualFilterValues[$this->Controller->Document->getAlias()] + ); + + $filterValues = array('Document' => array('title' => 'in')); + $this->Controller->getRequest()->getSession()->write($sessionKey, $filterValues); + + $this->Controller->dispatchEvent('Controller.startup'); + $actualFilterValues = $this->Controller->Document->getFilterValues(); + $this->assertEquals + ( + $filterValues, + $actualFilterValues[$this->Controller->Document->getAlias()] + ); + + $this->Controller->getRequest()->getSession()->delete($sessionKey); + } + + /** + * Test loading filter data from a post request. + * + * @return void + */ + public function testPostStartupData() + { + $request = new ServerRequest([ + 'params' => [ + 'controller' => 'DocumentTests', + 'action' => 'index', + ], + 'environment' => [ + 'REQUEST_METHOD' => 'POST', + ], + ]); + $this->Controller = new DocumentTestsController($request); + $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->request = $this->Controller->getRequest()->withParsedBody($filterValues); + + $this->Controller->dispatchEvent('Controller.initialize'); + $this->Controller->dispatchEvent('Controller.startup'); + + $sessionKey = sprintf( + 'FilterPlugin.Filters.%s.%s', + $this->Controller->getName(), + $this->Controller->getRequest()->getParam('action') + ); + $sessionData = $this->Controller->getRequest()->getSession()->read($sessionKey); + $this->assertEquals($filterValues, $sessionData); + + $actualFilterValues = $this->Controller->Document->getFilterValues(); + $this->assertEquals + ( + $filterValues, + $actualFilterValues[$this->Controller->Document->getAlias()] + ); + } + + /** + * Test exiting beforeRender when in an action with no settings. + * + * @return void + */ + public function testBeforeRenderAbort() + { + $testSettings = array + ( + 'veryMuchNotIndex' => array + ( + 'Document' => array + ( + 'Document.title' => array('type' => 'text') + ) + ) + ); + $this->Controller->filters = $testSettings; + + $this->Controller->dispatchEvent('Controller.initialize'); + $this->Controller->dispatchEvent('Controller.startup'); + $this->Controller->dispatchEvent('Controller.beforeRender'); + + $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. + * + * @return void + */ + public function testNoModelFound() + { + $testSettings = array + ( + 'index' => array + ( + 'ThisModelDoesNotExist' => array + ( + 'ThisModelDoesNotExist.title' => array('type' => 'text') + ) + ) + ); + $this->Controller->filters = $testSettings; + $this->expectException('PHPUnit\Framework\Error\Notice'); + $this->Controller->dispatchEvent('Controller.initialize'); + } + + /** + * Test the view variable generation for very basic filtering. + * Also tests model name detection and custom label. + * + * @return void + */ + public function testBasicViewInfo() + { + $testSettings = array + ( + 'index' => array + ( + 'Document' => array + ( + 'title', + 'DocumentCategory.id' => array( + 'type' => 'select', + 'label' => 'Category', + 'className' => DocumentCategoriesTable::class, + ), + ) + ) + ); + $this->Controller->filters = $testSettings; + + $this->Controller->dispatchEvent('Controller.initialize'); + $this->Controller->dispatchEvent('Controller.startup'); + $this->Controller->dispatchEvent('Controller.beforeRender'); + + $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. + * + * @return void + */ + public function testAdditionalInputOptions() + { + $testSettings = array + ( + 'index' => array + ( + 'Document' => array + ( + 'title' => array('inputOptions' => 'disabled'), + 'DocumentCategory.id' => array + ( + 'type' => 'select', + 'label' => 'Category', + 'inputOptions' => array('class' => 'important'), + 'className' => DocumentCategoriesTable::class, + ), + ) + ) + ); + $this->Controller->filters = $testSettings; + + $this->Controller->dispatchEvent('Controller.initialize'); + $this->Controller->dispatchEvent('Controller.startup'); + $this->Controller->dispatchEvent('Controller.beforeRender'); + + $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. + * + * @return void + */ + 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' => '%!%'), + ), + 'className' => DocumentCategoriesTable::class, + ), + ) + ) + ); + $this->Controller->filters = $testSettings; + + $this->Controller->dispatchEvent('Controller.initialize'); + $this->Controller->dispatchEvent('Controller.startup'); + $this->Controller->dispatchEvent('Controller.beforeRender'); + + $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. + * + * @return void + */ + 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->dispatchEvent('Controller.initialize'); + $this->Controller->dispatchEvent('Controller.startup'); + $this->Controller->dispatchEvent('Controller.beforeRender'); + + $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. + * + * @return void + */ + public function testSelectMultiple() + { + $testSettings = array + ( + 'index' => array + ( + 'Document' => array + ( + 'DocumentCategory.id' => array + ( + 'type' => 'select', + 'multiple' => true, + ) + ) + ) + ); + $this->Controller->filters = $testSettings; + + $expected = array + ( + $this->Controller->getName() => $testSettings + ); + + $this->Controller->dispatchEvent('Controller.initialize'); + $this->assertEquals($expected, $this->Controller->Filter->settings); + } + + /** + * Test select input for the model filtered. + * + * @return void + */ + public function testSelectInputFromSameModel() + { + $testSettings = array + ( + 'index' => array + ( + 'Document' => array + ( + 'Document.title' => array + ( + 'type' => 'select', + 'className' => DocumentsTable::class, + ), + ) + ) + ); + $this->Controller->filters = $testSettings; + + $this->Controller->dispatchEvent('Controller.initialize'); + $this->Controller->dispatchEvent('Controller.startup'); + $this->Controller->dispatchEvent('Controller.beforeRender'); + + $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. + * + * @return void + */ + public function testPersistence() + { + $testSettings = array + ( + 'index' => array + ( + 'Document' => array + ( + 'Document.title' => array('type' => 'text') + ), + ), + ); + $this->Controller->filters = $testSettings; + $this->Controller->components()->unload('Filter'); + $this->Controller->loadComponent('Filter.Filter', ['nopersist' => true]); + + $sessionKey = sprintf( + 'FilterPlugin.Filters.%s.%s', + 'SomeOtherController', + $this->Controller->getRequest()->getParam('action') + ); + $filterValues = array('Document' => array('title' => 'in'), 'Filter' => array('filterFormId' => 'Document')); + $this->Controller->getRequest()->getSession()->write($sessionKey, $filterValues); + + $sessionKey = sprintf( + 'FilterPlugin.Filters.%s.%s', + $this->Controller->getName(), + $this->Controller->getRequest()->getParam('action') + ); + $filterValues = array('Document' => array('title' => 'in'), 'Filter' => array('filterFormId' => 'Document')); + $this->Controller->getRequest()->getSession()->write($sessionKey, $filterValues); + + $this->Controller->Filter->nopersist = array(); + $this->Controller->Filter->nopersist[$this->Controller->getName()] = true; + $this->Controller->Filter->nopersist['SomeOtherController'] = true; + + $this->Controller->dispatchEvent('Controller.initialize'); + $this->Controller->dispatchEvent('Controller.startup'); + + $expected = array( + $this->Controller->getName() => array( + $this->Controller->getRequest()->getParam('action') => $filterValues, + ), + ); + $this->assertEquals($expected, $this->Controller->getRequest()->getSession()->read('FilterPlugin.Filters')); + } + + /** + * Test whether filtering by belongsTo model text field + * works correctly. + * + * @return void + */ + public function testBelongsToFilteringByText() + { + $testSettings = array + ( + 'index' => array + ( + 'Document' => array + ( + 'DocumentCategory.title' => array('type' => 'text') + ), + ) + ); + $this->Controller->filters = $testSettings; + + $this->Controller->dispatchEvent('Controller.initialize'); + $this->Controller->dispatchEvent('Controller.startup'); + $this->Controller->dispatchEvent('Controller.beforeRender'); + + $expected = array + ( + array + ( + 'name' => 'DocumentCategory.title', + 'options' => array + ( + 'type' => 'text', + ) + ), + ); + + $this->assertEquals($expected, $this->Controller->viewVars['viewFilterParams']); + } } diff --git a/tests/TestCase/MockObjects/DocumentCategoriesTable.php b/tests/TestCase/MockObjects/DocumentCategoriesTable.php index db0cdce..cdb3017 100644 --- a/tests/TestCase/MockObjects/DocumentCategoriesTable.php +++ b/tests/TestCase/MockObjects/DocumentCategoriesTable.php @@ -11,28 +11,28 @@ */ class DocumentCategoriesTable extends Table { - /** - * {@inheritDoc} - * - * @param mixed[] $config - * @see \Cake\ORM\Table::initialize() - */ - public function initialize(array $config) - { - $this->hasMany('Documents'); - } + /** + * {@inheritDoc} + * + * @param mixed[] $config + * @see \Cake\ORM\Table::initialize() + */ + public function initialize(array $config) + { + $this->hasMany('Documents'); + } - /** - * @param mixed[] $options - * @return mixed[]|int|null - */ - public function customSelector($options = array()) - { - $options['nofilter'] = true; - return $this->find('list', $options) - ->where([ - 'DocumentCategory.title LIKE' => '%T%', - ]) - ->toArray(); - } + /** + * @param mixed[] $options + * @return mixed[]|int|null + */ + public function customSelector($options = array()) + { + $options['nofilter'] = true; + return $this->find('list', $options) + ->where([ + 'DocumentCategory.title LIKE' => '%T%', + ]) + ->toArray(); + } } diff --git a/tests/TestCase/MockObjects/DocumentTestsController.php b/tests/TestCase/MockObjects/DocumentTestsController.php index bbf37bc..0809dce 100644 --- a/tests/TestCase/MockObjects/DocumentTestsController.php +++ b/tests/TestCase/MockObjects/DocumentTestsController.php @@ -10,46 +10,46 @@ */ class DocumentTestsController extends Controller { - /** - * @var mixed[] - */ - public $filters; + /** + * @var mixed[] + */ + public $filters; - /** - * {@inheritDoc} - * - * @see \Cake\Controller\Controller::initialize() - */ - public function initialize() - { - parent::initialize(); - /** @var \Filter\Test\TestCase\MockObjects\DocumentsTable $Table */ - $Table = $this->getTableLocator()->get('Documents', [ - 'className' => DocumentsTable::class, - ]); - $this->Document = $Table; - $this->loadComponent('Filter.Filter'); - } + /** + * {@inheritDoc} + * + * @see \Cake\Controller\Controller::initialize() + */ + public function initialize() + { + parent::initialize(); + /** @var \Filter\Test\TestCase\MockObjects\DocumentsTable $Table */ + $Table = $this->getTableLocator()->get('Documents', [ + 'className' => DocumentsTable::class, + ]); + $this->Document = $Table; + $this->loadComponent('Filter.Filter'); + } - /** - * @return void - */ - public function index() - { - } + /** + * @return void + */ + public function index() + { + } - /** - * must override this or the tests never complete. - * - * @TODO: mock partial? - * - * @param string|mixed[] $url - * @param int|mixed[]|null|string $status - * @param bool $exit - * @return \Cake\Http\Response|null - */ - public function redirect($url, $status = null, $exit = true) - { - return null; - } + /** + * must override this or the tests never complete. + * + * @TODO: mock partial? + * + * @param string|mixed[] $url + * @param int|mixed[]|null|string $status + * @param bool $exit + * @return \Cake\Http\Response|null + */ + public function redirect($url, $status = null, $exit = true) + { + return null; + } } diff --git a/tests/TestCase/MockObjects/Documents2Table.php b/tests/TestCase/MockObjects/Documents2Table.php index 6d91512..b8eb41e 100644 --- a/tests/TestCase/MockObjects/Documents2Table.php +++ b/tests/TestCase/MockObjects/Documents2Table.php @@ -12,32 +12,32 @@ */ class Documents2Table extends Table { - /** - * {@inheritDoc} - * - * @param mixed[] $config - * @see \Cake\ORM\Table::initialize() - */ - public function initialize(array $config) - { - $this->setAlias('Document'); - $this->setTable('documents'); - $this->belongsTo('DocumentCategories'); - $this->hasMany('Items'); - } + /** + * {@inheritDoc} + * + * @param mixed[] $config + * @see \Cake\ORM\Table::initialize() + */ + public function initialize(array $config) + { + $this->setAlias('Document'); + $this->setTable('documents'); + $this->belongsTo('DocumentCategories'); + $this->hasMany('Items'); + } - /** - * @var bool - */ - public $returnValue = false; + /** + * @var bool + */ + public $returnValue = false; - /** - * @param \Cake\ORM\Query $query Query. - * @param mixed[] $options - * @return mixed[]|bool - */ - public function beforeDataFilter($query, $options) - { - return $this->returnValue; - } + /** + * @param \Cake\ORM\Query $query Query. + * @param mixed[] $options + * @return mixed[]|bool + */ + public function beforeDataFilter($query, $options) + { + return $this->returnValue; + } } diff --git a/tests/TestCase/MockObjects/Documents3Table.php b/tests/TestCase/MockObjects/Documents3Table.php index ad66f9a..06f0b3c 100644 --- a/tests/TestCase/MockObjects/Documents3Table.php +++ b/tests/TestCase/MockObjects/Documents3Table.php @@ -12,44 +12,44 @@ */ class Documents3Table extends Table { - /** - * {@inheritDoc} - * - * @param mixed[] $config - * @see \Cake\ORM\Table::initialize() - */ - public function initialize(array $config) - { - $this->setAlias('Document'); - $this->setTable('documents'); - $this->belongsTo('DocumentCategories'); - $this->hasMany('Items'); - } + /** + * {@inheritDoc} + * + * @param mixed[] $config + * @see \Cake\ORM\Table::initialize() + */ + public function initialize(array $config) + { + $this->setAlias('Document'); + $this->setTable('documents'); + $this->belongsTo('DocumentCategories'); + $this->hasMany('Items'); + } - /** - * @var string|null - */ - public $itemToUnset = null; + /** + * @var string|null + */ + public $itemToUnset = null; - /** - * @param \Cake\ORM\Query $query Query. - * @param mixed[] $options - * @return \Cake\ORM\Query - */ - public function afterDataFilter($query, $options) - { - if (!is_string($this->itemToUnset)) - { - return $query; - } - $query->clause('where')->iterateParts(function ($Comparison) { - /** @var \Cake\Database\Expression\Comparison $Comparison */ - $field = $Comparison->getField(); - if ($field == $this->itemToUnset) { - return null; - } - return $Comparison; - }); - return $query; - } + /** + * @param \Cake\ORM\Query $query Query. + * @param mixed[] $options + * @return \Cake\ORM\Query + */ + public function afterDataFilter($query, $options) + { + if (!is_string($this->itemToUnset)) + { + return $query; + } + $query->clause('where')->iterateParts(function ($Comparison) { + /** @var \Cake\Database\Expression\Comparison $Comparison */ + $field = $Comparison->getField(); + if ($field == $this->itemToUnset) { + return null; + } + return $Comparison; + }); + return $query; + } } diff --git a/tests/TestCase/MockObjects/DocumentsTable.php b/tests/TestCase/MockObjects/DocumentsTable.php index 821b5cc..15792f0 100644 --- a/tests/TestCase/MockObjects/DocumentsTable.php +++ b/tests/TestCase/MockObjects/DocumentsTable.php @@ -13,16 +13,16 @@ */ class DocumentsTable extends Table { - /** - * {@inheritDoc} - * - * @param mixed[] $config - * @see \Cake\ORM\Table::initialize() - */ - public function initialize(array $config) - { - $this->belongsTo('DocumentCategories'); - $this->hasMany('Items'); - $this->hasOne('Metadata'); - } + /** + * {@inheritDoc} + * + * @param mixed[] $config + * @see \Cake\ORM\Table::initialize() + */ + public function initialize(array $config) + { + $this->belongsTo('DocumentCategories'); + $this->hasMany('Items'); + $this->hasOne('Metadata'); + } } diff --git a/tests/TestCase/MockObjects/ItemsTable.php b/tests/TestCase/MockObjects/ItemsTable.php index b0aa942..29f8c98 100644 --- a/tests/TestCase/MockObjects/ItemsTable.php +++ b/tests/TestCase/MockObjects/ItemsTable.php @@ -9,14 +9,14 @@ */ class ItemsTable extends Table { - /** - * {@inheritDoc} - * - * @param mixed[] $config - * @see \Cake\ORM\Table::initialize() - */ - public function initialize(array $config) - { - $this->belongsTo('Documents'); - } + /** + * {@inheritDoc} + * + * @param mixed[] $config + * @see \Cake\ORM\Table::initialize() + */ + public function initialize(array $config) + { + $this->belongsTo('Documents'); + } } diff --git a/tests/TestCase/MockObjects/MetadataTable.php b/tests/TestCase/MockObjects/MetadataTable.php index 79817e4..be7ab50 100644 --- a/tests/TestCase/MockObjects/MetadataTable.php +++ b/tests/TestCase/MockObjects/MetadataTable.php @@ -6,14 +6,14 @@ class MetadataTable extends Table { - /** - * {@inheritDoc} - * - * @param mixed[] $config - * @see \Cake\ORM\Table::initialize() - */ - public function initialize(array $config) - { - $this->belongsTo('Documents'); - } + /** + * {@inheritDoc} + * + * @param mixed[] $config + * @see \Cake\ORM\Table::initialize() + */ + public function initialize(array $config) + { + $this->belongsTo('Documents'); + } } diff --git a/tests/TestCase/Model/Behavior/FilteredBehaviorTest.php b/tests/TestCase/Model/Behavior/FilteredBehaviorTest.php index 7a698ac..fdce0fb 100644 --- a/tests/TestCase/Model/Behavior/FilteredBehaviorTest.php +++ b/tests/TestCase/Model/Behavior/FilteredBehaviorTest.php @@ -10,970 +10,970 @@ use Cake\ORM\Association; /** - CakePHP Filter Plugin + CakePHP Filter Plugin - Copyright (C) 2009-3827 dr. Hannibal Lecter / lecterror - + Copyright (C) 2009-3827 dr. Hannibal Lecter / lecterror + - Multi-licensed under: - MPL - LGPL - GPL + Multi-licensed under: + MPL + LGPL + GPL */ class FilteredBehaviorTest extends TestCase { - /** - * @var string[] - */ - public $fixtures = array - ( - 'plugin.Filter.DocumentCategories', - 'plugin.Filter.Documents', - 'plugin.Filter.Items', - 'plugin.Filter.Metadata', - ); - - /** - * @var \Filter\Test\TestCase\MockObjects\DocumentsTable|\Filter\Test\TestCase\MockObjects\Documents2Table|\Filter\Test\TestCase\MockObjects\Documents3Table - */ - public $Document = null; - - public function setUp() - { - parent::setUp(); - $Document = $this->getTableLocator()->get('Documents', ['className' => DocumentsTable::class]); - $this->assertInstanceOf(DocumentsTable::class, $Document); - $this->Document = $Document; - } - - public function tearDown() - { - parent::tearDown(); - unset($this->Document); - } - - /** - * Detach and re-attach the behavior to reset the options. - * - * @param mixed[] $options Behavior options. - * @return void - */ - protected function _reattachBehavior($options = array()) - { - if ($this->Document->hasBehavior('Filtered')) { - $this->Document->removeBehavior('Filtered'); - } - $this->Document->addBehavior('Filter.Filtered', $options); - } - - /** - * Test attaching without options. - * - * @return void - */ - public function testBlankAttaching() - { - $this->Document->addBehavior('Filter.Filtered'); - $this->assertTrue($this->Document->hasBehavior('Filtered')); - } - - /** - * Test attaching with options. - * - * @return void - */ - public function testInitSettings() - { - $testOptions = array - ( - 'Documents.title' => array('type' => 'text', 'condition' => 'like'), - 'DocumentCategories.id' => array('type' => 'select', 'filterField' => 'document_category_id'), - 'Documents.is_private' => array('type' => 'checkbox', 'label' => 'Private?') - ); - $this->_reattachBehavior($testOptions); - - $expected = array - ( - 'Documents.title' => array('type' => 'text', 'condition' => 'like', 'required' => false, 'selectOptions' => array()), - 'DocumentCategories.id' => array('type' => 'select', 'filterField' => 'document_category_id', 'condition' => 'like', 'required' => false, 'selectOptions' => array()), - 'Documents.is_private' => array('type' => 'checkbox', 'label' => 'Private?', 'condition' => 'like', 'required' => false, 'selectOptions' => array()) - ); - $Filtered = $this->Document->getBehavior('Filtered'); - $this->assertInstanceOf(FilteredBehavior::class, $Filtered); - $this->assertEquals($expected, $Filtered->settings[$this->Document->getAlias()]); - } - - /** - * Test init settings when only a single field is given, with no extra options. - * - * @return void - */ - public function testInitSettingsSingle() - { - $testOptions = array('Documents.title'); - $this->_reattachBehavior($testOptions); - - $expected = array - ( - 'Documents.title' => array('type' => 'text', 'condition' => 'like', 'required' => false, 'selectOptions' => array()), - ); - $Filtered = $this->Document->getBehavior('Filtered'); - $this->assertInstanceOf(FilteredBehavior::class, $Filtered); - $this->assertEquals($expected, $Filtered->settings[$this->Document->getAlias()]); - } - - /** - * Test setting the filter values for future queries. - * - * @return void - */ - public function testSetFilterValues() - { - $testOptions = array - ( - 'Documents.title' => array('type' => 'text', 'condition' => 'like', 'required' => true), - 'DocumentCategories.id' => array('type' => 'select', 'filterField' => 'document_category_id'), - 'Documents.is_private' => array('type' => 'checkbox', 'label' => 'Private?') - ); - - $this->_reattachBehavior($testOptions); - - $filterValues = array - ( - 'Documents' => array('title' => 'in', 'is_private' => 0), - 'DocumentCategories' => array('id' => 1) - ); - - $this->Document->setFilterValues($filterValues); - $actualFilterValues = $this->Document->getFilterValues(); - $this->assertEquals($filterValues, $actualFilterValues[$this->Document->getAlias()]); - } - - /** - * Test detecting an error in options - when a field is 'required' but no value is given for it. - * - * @return void - */ - public function testLoadingRequiredFieldValueMissing() - { - $testOptions = array - ( - 'Documents.title' => array('type' => 'text', 'condition' => 'like', 'required' => true), - 'DocumentCategories.id' => array('type' => 'select', 'filterField' => 'document_category_id'), - 'Documents.is_private' => array('type' => 'checkbox', 'label' => 'Private?') - ); - $this->_reattachBehavior($testOptions); - - $filterValues = array - ( - 'Documents' => array('is_private' => 0), - 'DocumentCategories' => array('id' => 1) - ); - $this->Document->setFilterValues($filterValues); - - $this->expectException('PHPUnit\Framework\Error\Notice'); - $this->Document->find()->first(); - } - - /** - * Test filtering with conditions from current model and belongsTo model. - * - * @return void - */ - public function testFilteringBelongsTo() - { - $testOptions = array - ( - 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), - 'DocumentCategories.id' => array('type' => 'select') - ); - $this->_reattachBehavior($testOptions); - - $filterValues = array - ( - 'Documents' => array('title' => 'in'), - 'DocumentCategories' => array('id' => 1) - ); - $this->Document->setFilterValues($filterValues); - - $expected = array - ( - array('id' => 1, 'title' => 'Testing Doc', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0), - array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0) - ); - - $result = $this->Document->find() - ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) - ->enableHydration(false) - ->toArray(); - $this->assertEquals($expected, $result); - } - - /** - * @return void - */ - public function testFilteringBelongsToTextField() - { - $testOptions = array - ( - 'DocumentCategories.title' => array('type' => 'text') - ); - $this->_reattachBehavior($testOptions); - - $filterValues = array - ( - 'DocumentCategories' => array('title' => 'spec') - ); - $this->Document->setFilterValues($filterValues); - - $expected = array - ( - array('id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0) - ); - - $result = $this->Document->find() - ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) - ->contain(['DocumentCategories']) - ->enableHydration(false) - ->toArray(); - $this->assertEquals($expected, $result); - } - - /** - * Test filtering with conditions from current model and belongsTo model, - * same as testFilteringBelongsTo() except for a change in filterField format. - * - * @return void - */ - public function testFilteringBelongsToFilterFieldTest() - { - $testOptions = array - ( - 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), - 'DocumentCategories.id' => array('type' => 'select', 'filterField' => 'Documents.document_category_id') - ); - $this->_reattachBehavior($testOptions); - - $filterValues = array - ( - 'Documents' => array('title' => 'in'), - 'DocumentCategories' => array('id' => 1) - ); - $this->Document->setFilterValues($filterValues); - - $expected = array - ( - array('id' => 1, 'title' => 'Testing Doc', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0), - array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0) - ); - - $result = $this->Document->find() - ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) - ->contain(['DocumentCategories']) - ->enableHydration(false) - ->toArray(); - $this->assertEquals($expected, $result); - } - - /** - * Test various conditions for the type 'text' in filtering (less than, equal, like, etc..) - * - * @return void - */ - public function testFilteringBelongsToDifferentConditions() - { - $testOptions = array - ( - 'title' => array('type' => 'text', 'condition' => '='), - 'DocumentCategories.id' => array('type' => 'select') - ); - $this->_reattachBehavior($testOptions); - - $filterValues = array - ( - 'Documents' => array('title' => 'Illegal explosives DIY'), - 'DocumentCategories' => array('id' => '') - ); - $this->Document->setFilterValues($filterValues); - - $expected = array - ( - array('id' => 4, 'title' => 'Illegal explosives DIY', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 1), - ); - - $result = $this->Document->find() - ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) - ->contain(['DocumentCategories']) - ->enableHydration(false) - ->toArray(); - $this->assertEquals($expected, $result); - - $testOptions = array - ( - 'id' => array('type' => 'text', 'condition' => '>='), - 'created' => array('type' => 'text', 'condition' => '<=') - ); - $this->_reattachBehavior($testOptions); - - $filterValues = array - ( - 'Documents' => array('id' => 3, 'created' => '2010-03-01') - ); - $this->Document->setFilterValues($filterValues); - - $expected = array - ( - array('id' => 4, 'title' => 'Illegal explosives DIY', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 1), - array('id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0), - array('id' => 6, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0), - array('id' => 7, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0), - ); - - $result = $this->Document->find() - ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) - ->contain(['DocumentCategories']) - ->enableHydration(false) - ->toArray(); - $this->assertEquals($expected, $result); - } - - /** - * Test filtering with conditions on current model, the belongsTo model - * and hasMany model (behavior adds an INNER JOIN in query). - * - * @return void - */ - public function testFilteringBelongsToAndHasMany() - { - $testOptions = array - ( - 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), - 'DocumentCategories.id' => array('type' => 'select'), - 'Documents.is_private' => array('type' => 'checkbox', 'label' => 'Private?'), - 'Items.code' => array('type' => 'text'), - ); - $this->_reattachBehavior($testOptions); - - $filterValues = array - ( - 'Documents' => array('title' => 'in', 'is_private' => 0), - 'DocumentCategories' => array('id' => 1), - 'Items' => array('code' => '04') - ); - $this->Document->setFilterValues($filterValues); - - $expected = array - ( - array - ( - 'id' => 2, - 'title' => 'Imaginary Spec', - 'document_category_id' => 1, - 'owner_id' => 1, - 'is_private' => 0, - 'document_category' => 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-------'), - 'items' => 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() - ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) - ->contain([ - 'DocumentCategories' => [ - 'fields' => ['id', 'title', 'description'], - ], - 'Metadata' => [ - 'fields' => ['id', 'document_id', 'weight', 'size', 'permissions'], - ], - 'Items' => [ - 'fields' => ['id', 'document_id', 'code'], - ], - ]) - ->enableHydration(false) - ->toArray(); - $this->assertEquals($expected, $result); - - $expected = array - ( - array - ( - 'id' => 2, - 'title' => 'Imaginary Spec', - 'document_category_id' => 1, - 'owner_id' => 1, - 'is_private' => 0, - 'document_category' => 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() - ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) - ->contain([ - 'DocumentCategories' => [ - 'fields' => ['id', 'title', 'description'], - ], - 'Metadata' => [ - 'fields' => ['id', 'document_id', 'weight', 'size', 'permissions'], - ], - ]) - ->enableHydration(false) - ->toArray(); - $this->assertEquals($expected, $result); - - $this->Document->associations()->remove('Item'); - $this->Document->hasMany('Item'); - - $result = $this->Document->find() - ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) - ->contain([ - 'DocumentCategories' => [ - 'fields' => ['id', 'title', 'description'], - ], - 'Metadata' => [ - 'fields' => ['id', 'document_id', 'weight', 'size', 'permissions'], - ], - ]) - ->enableHydration(false) - ->toArray(); - $this->assertEquals($expected, $result); - - $expected = array - ( - array - ( - 'id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, - ) - ); - - $result = $this->Document->find() - ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) - ->enableHydration(false) - ->toArray(); - $this->assertEquals($expected, $result); - } - - /** - * Test filtering with join which has some custom - * condition in the relation (both string and array). - * - * @return void - */ - 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 - ( - 'id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0, - 'metadata' => array('id' => 5, 'document_id' => 5, 'weight' => 4, 'size' => 790, 'permissions' => 'rw-rw-r--'), - ) - ); - $Metadata = $this->Document->associations()->get('Metadata'); - $this->assertInstanceOf(Association::class, $Metadata); - $oldConditions = $Metadata->getConditions(); - $Metadata->setConditions(['Metadata.size > 500']); - - $result = $this->Document->find() - ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) - ->contain([ - 'Metadata' => [ - 'fields' => ['id', 'document_id', 'weight', 'size', 'permissions'], - ], - ]) - ->enableHydration(false) - ->toArray(); - $this->assertEquals($expected, $result); - - $Metadata->setConditions(['Metadata.size > 500']); - $result = $this->Document->find() - ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) - ->contain([ - 'Metadata' => [ - 'fields' => ['id', 'document_id', 'weight', 'size', 'permissions'], - ], - ]) - ->enableHydration(false) - ->toArray(); - $this->assertEquals($expected, $result); - - $Metadata->setConditions($oldConditions); - } - - /** - * Test for any possible conflicts with Containable behavior. - * - * @return void - */ - public function testFilteringBelongsToAndHasManyWithContainable() - { - $testOptions = array - ( - 'Documents.title' => array('type' => 'text', 'condition' => 'like', 'required' => true), - 'DocumentCategories.id' => array('type' => 'select'), - 'Documents.is_private' => array('type' => 'checkbox', 'label' => 'Private?'), - 'Items.code' => array('type' => 'text'), - ); - - $this->_reattachBehavior($testOptions); - - $filterValues = array - ( - 'Documents' => array('title' => 'in', 'is_private' => 0), - 'DocumentCategories' => array('id' => 1), - 'Items' => array('code' => '04') - ); - $this->Document->setFilterValues($filterValues); - - $expected = array - ( - array - ( - 'id' => 2, - 'title' => 'Imaginary Spec', - 'document_category_id' => 1, - 'owner_id' => 1, - 'is_private' => 0, - 'document_category' => array('id' => 1, 'title' => 'Testing Doc', 'description' => 'It\'s a bleeding test doc!'), - 'items' => 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() - ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) - ->contain([ - 'DocumentCategories' => [ - 'fields' => ['id', 'title', 'description'], - ], - 'Items' => [ - 'fields' => ['id', 'document_id', 'code'], - ], - ]) - ->enableHydration(false) - ->toArray(); - $this->assertEquals($expected, $result); - - $expected = array - ( - array - ( - 'id' => 2, - 'title' => 'Imaginary Spec', - 'document_category_id' => 1, - 'owner_id' => 1, - 'is_private' => 0, - 'document_category' => array('id' => 1, 'title' => 'Testing Doc', 'description' => 'It\'s a bleeding test doc!'), - ) - ); - - $result = $this->Document->find() - ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) - ->contain([ - 'DocumentCategories' => [ - 'fields' => ['id', 'title', 'description'], - ], - ]) - ->enableHydration(false) - ->toArray(); - $this->assertEquals($expected, $result); - - $expected = array - ( - array - ( - 'id' => 2, - 'title' => 'Imaginary Spec', - 'document_category_id' => 1, - 'owner_id' => 1, - 'is_private' => 0, - ) - ); - - $result = $this->Document->find() - ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) - ->enableHydration(false) - ->toArray(); - $this->assertEquals($expected, $result); - } - - /** - * Test filtering by text input with hasOne relation. - * - * @return void - */ - public function testHasOneAndHasManyWithTextSearch() - { - $testOptions = array - ( - 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), - 'Items.code' => array('type' => 'text'), - 'Metadata.size' => array('type' => 'text', 'condition' => '='), - ); - - $filterValues = array - ( - 'Documents' => array('title' => 'in'), - 'Items' => array('code' => '04'), - 'Metadata' => array('size' => 45), - ); - - $expected = array - ( - array - ( - 'id' => 2, - 'title' => 'Imaginary Spec', - ) - ); - - $this->_reattachBehavior($testOptions); - $this->Document->setFilterValues($filterValues); - - $result = $this->Document->find() - ->select(['id', 'title']) - ->enableHydration(false) - ->toArray(); - $this->assertEquals($expected, $result); - } - - /** - * Test filtering with Containable and hasOne Model.field. - * - * @return void - */ - public function testHasOneWithContainable() - { - $testOptions = array - ( - 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), - 'Items.code' => array('type' => 'text'), - 'Metadata.size' => array('type' => 'text', 'condition' => '='), - ); - - $filterValues = array - ( - 'Documents' => array('title' => 'in'), - 'Items' => array('code' => '04'), - 'Metadata' => array('size' => 45), - ); - - $expected = array - ( - array - ( - 'id' => 2, - 'title' => 'Imaginary Spec', - 'document_category_id' => 1, - 'owner_id' => 1, - 'is_private' => 0, - 'metadata' => array('id' => 2, 'document_id' => 2, 'weight' => 0, 'size' => 45, 'permissions' => 'rw-------'), - 'items' => 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->_reattachBehavior($testOptions); - $this->Document->setFilterValues($filterValues); - $result = $this->Document->find() - ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) - ->contain([ - 'Metadata' => [ - 'fields' => ['id', 'document_id', 'weight', 'size', 'permissions'], - ], - 'Items' => [ - 'fields' => ['id', 'document_id', 'code'], - ], - ]) - ->enableHydration(false) - ->toArray(); - $this->assertEquals($expected, $result); - - // filtered first, containable second - $this->_reattachBehavior($testOptions); - $this->Document->setFilterValues($filterValues); - $result = $this->Document->find() - ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) - ->contain([ - 'Metadata' => [ - 'fields' => ['id', 'document_id', 'weight', 'size', 'permissions'], - ], - 'Items' => [ - 'fields' => ['id', 'document_id', 'code'], - ], - ]) - ->enableHydration(false) - ->toArray(); - $this->assertEquals($expected, $result); - } - - /** - * Test filtering when a join is already present in the query, - * this should prevent duplicate joins and query errors. - * - * @return void - */ - public function testJoinAlreadyPresent() - { - $testOptions = array - ( - 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), - 'Items.code' => array('type' => 'text'), - 'Metadata.size' => array('type' => 'text', 'condition' => '='), - ); - - $filterValues = array - ( - 'Documents' => array('title' => 'in'), - 'Items' => array('code' => '04'), - 'Metadata' => array('size' => 45), - ); - - $expected = array - ( - array - ( - 'id' => 2, - 'title' => 'Imaginary Spec', - 'document_category_id' => 1, - 'owner_id' => 1, - 'is_private' => 0, - 'document_category' => 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-------'), - 'items' => 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' => 'FilterItems', - 'type' => 'INNER', - 'conditions' => 'Documents.id = FilterItems.document_id', - ); - - $this->_reattachBehavior($testOptions); - $this->Document->setFilterValues($filterValues); - $result = $this->Document->find() - ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) - ->contain([ - 'DocumentCategories' => [ - 'fields' => ['id', 'title', 'description'], - ], - 'Metadata' => [ - 'fields' => ['id', 'document_id', 'weight', 'size', 'permissions'], - ], - 'Items' => [ - 'fields' => ['id', 'document_id', 'code'], - ], - ]) - ->join($customJoin) - ->enableHydration(false) - ->toArray(); - $this->assertEquals($expected, $result); - } - - /** - * Test the 'nofilter' query param. - * - * @return void - */ - public function testNofilterFindParam() - { - $testOptions = array - ( - 'Documents.title' => array('type' => 'text', 'condition' => 'like'), - 'DocumentCategories.id' => array('type' => 'select'), - 'Documents.is_private' => array('type' => 'checkbox', 'label' => 'Private?', 'default' => 0) - ); - $this->_reattachBehavior($testOptions); - - - $filterValues = array - ( - 'DocumentCategories' => array('id' => 2), - 'Documents' => array('title' => '') - ); - $this->Document->setFilterValues($filterValues); - - $expected = array - ( - array('id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0) - ); - - $result = $this->Document->find('all', ['nofilter' => true]) - ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) - ->enableHydration(false) - ->toArray(); - $this->assertNotEquals($expected, $result); - - $result = $this->Document->find('all', ['nofilter' => 'true']) - ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) - ->enableHydration(false) - ->toArray(); - $this->assertEquals($expected, $result); - } - - /** - * Test bailing out if no settings exist for the current model. - * - * @return void - */ - public function testExitWhenNoSettings() - { - $this->Document->DocumentCategories->addBehavior('Filter.Filtered'); - - $Filtered = $this->Document->DocumentCategories->behaviors()->get('Filtered'); - $this->assertFalse(isset($Filtered->settings[$this->Document->DocumentCategories->getAlias()])); - - $filterValues = array - ( - 'DocumentCategories' => array('id' => 2) - ); - $this->Document->DocumentCategories->setFilterValues($filterValues); - - $expected = 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!'), - ); - - $result = $this->Document->DocumentCategories->find('all', ['nofilter' => 'true']) - ->select(['id', 'title', 'description']) - ->enableHydration(false) - ->toArray(); - $this->assertEquals($expected, $result); - - $this->Document->DocumentCategories->removeBehavior('Filtered'); - } - - /** - * Test beforeDataFilter() callback, used to cancel filtering if necessary. - * - * @return void - */ - public function testBeforeDataFilterCallbackCancel() - { - $Document = $this->getTableLocator()->get('Document2', ['className' => Documents2Table::class]); - $this->assertInstanceOf(Documents2Table::class, $Document); - $this->Document = $Document; - $testOptions = array - ( - 'Documents.title' => array('type' => 'text', 'condition' => 'like'), - 'DocumentCategories.id' => array('type' => 'select'), - 'Documents.is_private' => array('type' => 'checkbox', 'label' => 'Private?') - ); - $this->_reattachBehavior($testOptions); - - - $filterValues = array - ( - 'DocumentCategories' => array('id' => 2) - ); - $this->Document->setFilterValues($filterValues); - - $expected = array - ( - array('id' => 1, 'title' => 'Testing Doc', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0,), - array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0), - array('id' => 3, 'title' => 'Nonexistant data', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0), - array('id' => 4, 'title' => 'Illegal explosives DIY', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 1), - array('id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0), - array('id' => 6, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0), - array('id' => 7, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0), - ); - - $result = $this->Document->find() - ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) - ->enableHydration(false) - ->toArray(); - $this->assertEquals($expected, $result); - } - - /** - * Test afterDataFilter() callback, used to modify the conditions after - * filter conditions have been applied. - * - * @return void - */ - public function testAfterDataFilterCallbackQueryChange() - { - $Document = $this->getTableLocator()->get('Document3', ['className' => Documents3Table::class]); - $this->assertInstanceOf(Documents3Table::class, $Document); - $this->Document = $Document; - $this->Document->itemToUnset = 'FilterDocumentCategories.id'; - - $testOptions = array - ( - 'Documents.title' => array('type' => 'text', 'condition' => 'like'), - 'DocumentCategories.id' => array('type' => 'select'), - 'Documents.is_private' => array('type' => 'checkbox', 'label' => 'Private?') - ); - $this->_reattachBehavior($testOptions); - - - $filterValues = array - ( - 'DocumentCategories' => array('id' => 2) - ); - $this->Document->setFilterValues($filterValues); - - $expected = array - ( - array('id' => 1, 'title' => 'Testing Doc', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0), - array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0), - array('id' => 3, 'title' => 'Nonexistant data', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0), - array('id' => 4, 'title' => 'Illegal explosives DIY', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 1), - array('id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0), - array('id' => 6, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0), - array('id' => 7, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0), - ); - - $result = $this->Document->find('all') - ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) - ->enableHydration(false) - ->toArray(); - $this->assertEquals($expected, $result); - } + /** + * @var string[] + */ + public $fixtures = array + ( + 'plugin.Filter.DocumentCategories', + 'plugin.Filter.Documents', + 'plugin.Filter.Items', + 'plugin.Filter.Metadata', + ); + + /** + * @var \Filter\Test\TestCase\MockObjects\DocumentsTable|\Filter\Test\TestCase\MockObjects\Documents2Table|\Filter\Test\TestCase\MockObjects\Documents3Table + */ + public $Document = null; + + public function setUp() + { + parent::setUp(); + $Document = $this->getTableLocator()->get('Documents', ['className' => DocumentsTable::class]); + $this->assertInstanceOf(DocumentsTable::class, $Document); + $this->Document = $Document; + } + + public function tearDown() + { + parent::tearDown(); + unset($this->Document); + } + + /** + * Detach and re-attach the behavior to reset the options. + * + * @param mixed[] $options Behavior options. + * @return void + */ + protected function _reattachBehavior($options = array()) + { + if ($this->Document->hasBehavior('Filtered')) { + $this->Document->removeBehavior('Filtered'); + } + $this->Document->addBehavior('Filter.Filtered', $options); + } + + /** + * Test attaching without options. + * + * @return void + */ + public function testBlankAttaching() + { + $this->Document->addBehavior('Filter.Filtered'); + $this->assertTrue($this->Document->hasBehavior('Filtered')); + } + + /** + * Test attaching with options. + * + * @return void + */ + public function testInitSettings() + { + $testOptions = array + ( + 'Documents.title' => array('type' => 'text', 'condition' => 'like'), + 'DocumentCategories.id' => array('type' => 'select', 'filterField' => 'document_category_id'), + 'Documents.is_private' => array('type' => 'checkbox', 'label' => 'Private?') + ); + $this->_reattachBehavior($testOptions); + + $expected = array + ( + 'Documents.title' => array('type' => 'text', 'condition' => 'like', 'required' => false, 'selectOptions' => array()), + 'DocumentCategories.id' => array('type' => 'select', 'filterField' => 'document_category_id', 'condition' => 'like', 'required' => false, 'selectOptions' => array()), + 'Documents.is_private' => array('type' => 'checkbox', 'label' => 'Private?', 'condition' => 'like', 'required' => false, 'selectOptions' => array()) + ); + $Filtered = $this->Document->getBehavior('Filtered'); + $this->assertInstanceOf(FilteredBehavior::class, $Filtered); + $this->assertEquals($expected, $Filtered->settings[$this->Document->getAlias()]); + } + + /** + * Test init settings when only a single field is given, with no extra options. + * + * @return void + */ + public function testInitSettingsSingle() + { + $testOptions = array('Documents.title'); + $this->_reattachBehavior($testOptions); + + $expected = array + ( + 'Documents.title' => array('type' => 'text', 'condition' => 'like', 'required' => false, 'selectOptions' => array()), + ); + $Filtered = $this->Document->getBehavior('Filtered'); + $this->assertInstanceOf(FilteredBehavior::class, $Filtered); + $this->assertEquals($expected, $Filtered->settings[$this->Document->getAlias()]); + } + + /** + * Test setting the filter values for future queries. + * + * @return void + */ + public function testSetFilterValues() + { + $testOptions = array + ( + 'Documents.title' => array('type' => 'text', 'condition' => 'like', 'required' => true), + 'DocumentCategories.id' => array('type' => 'select', 'filterField' => 'document_category_id'), + 'Documents.is_private' => array('type' => 'checkbox', 'label' => 'Private?') + ); + + $this->_reattachBehavior($testOptions); + + $filterValues = array + ( + 'Documents' => array('title' => 'in', 'is_private' => 0), + 'DocumentCategories' => array('id' => 1) + ); + + $this->Document->setFilterValues($filterValues); + $actualFilterValues = $this->Document->getFilterValues(); + $this->assertEquals($filterValues, $actualFilterValues[$this->Document->getAlias()]); + } + + /** + * Test detecting an error in options - when a field is 'required' but no value is given for it. + * + * @return void + */ + public function testLoadingRequiredFieldValueMissing() + { + $testOptions = array + ( + 'Documents.title' => array('type' => 'text', 'condition' => 'like', 'required' => true), + 'DocumentCategories.id' => array('type' => 'select', 'filterField' => 'document_category_id'), + 'Documents.is_private' => array('type' => 'checkbox', 'label' => 'Private?') + ); + $this->_reattachBehavior($testOptions); + + $filterValues = array + ( + 'Documents' => array('is_private' => 0), + 'DocumentCategories' => array('id' => 1) + ); + $this->Document->setFilterValues($filterValues); + + $this->expectException('PHPUnit\Framework\Error\Notice'); + $this->Document->find()->first(); + } + + /** + * Test filtering with conditions from current model and belongsTo model. + * + * @return void + */ + public function testFilteringBelongsTo() + { + $testOptions = array + ( + 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), + 'DocumentCategories.id' => array('type' => 'select') + ); + $this->_reattachBehavior($testOptions); + + $filterValues = array + ( + 'Documents' => array('title' => 'in'), + 'DocumentCategories' => array('id' => 1) + ); + $this->Document->setFilterValues($filterValues); + + $expected = array + ( + array('id' => 1, 'title' => 'Testing Doc', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0), + array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0) + ); + + $result = $this->Document->find() + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + } + + /** + * @return void + */ + public function testFilteringBelongsToTextField() + { + $testOptions = array + ( + 'DocumentCategories.title' => array('type' => 'text') + ); + $this->_reattachBehavior($testOptions); + + $filterValues = array + ( + 'DocumentCategories' => array('title' => 'spec') + ); + $this->Document->setFilterValues($filterValues); + + $expected = array + ( + array('id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0) + ); + + $result = $this->Document->find() + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->contain(['DocumentCategories']) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + } + + /** + * Test filtering with conditions from current model and belongsTo model, + * same as testFilteringBelongsTo() except for a change in filterField format. + * + * @return void + */ + public function testFilteringBelongsToFilterFieldTest() + { + $testOptions = array + ( + 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), + 'DocumentCategories.id' => array('type' => 'select', 'filterField' => 'Documents.document_category_id') + ); + $this->_reattachBehavior($testOptions); + + $filterValues = array + ( + 'Documents' => array('title' => 'in'), + 'DocumentCategories' => array('id' => 1) + ); + $this->Document->setFilterValues($filterValues); + + $expected = array + ( + array('id' => 1, 'title' => 'Testing Doc', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0), + array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0) + ); + + $result = $this->Document->find() + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->contain(['DocumentCategories']) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + } + + /** + * Test various conditions for the type 'text' in filtering (less than, equal, like, etc..) + * + * @return void + */ + public function testFilteringBelongsToDifferentConditions() + { + $testOptions = array + ( + 'title' => array('type' => 'text', 'condition' => '='), + 'DocumentCategories.id' => array('type' => 'select') + ); + $this->_reattachBehavior($testOptions); + + $filterValues = array + ( + 'Documents' => array('title' => 'Illegal explosives DIY'), + 'DocumentCategories' => array('id' => '') + ); + $this->Document->setFilterValues($filterValues); + + $expected = array + ( + array('id' => 4, 'title' => 'Illegal explosives DIY', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 1), + ); + + $result = $this->Document->find() + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->contain(['DocumentCategories']) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + + $testOptions = array + ( + 'id' => array('type' => 'text', 'condition' => '>='), + 'created' => array('type' => 'text', 'condition' => '<=') + ); + $this->_reattachBehavior($testOptions); + + $filterValues = array + ( + 'Documents' => array('id' => 3, 'created' => '2010-03-01') + ); + $this->Document->setFilterValues($filterValues); + + $expected = array + ( + array('id' => 4, 'title' => 'Illegal explosives DIY', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 1), + array('id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0), + array('id' => 6, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0), + array('id' => 7, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0), + ); + + $result = $this->Document->find() + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->contain(['DocumentCategories']) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + } + + /** + * Test filtering with conditions on current model, the belongsTo model + * and hasMany model (behavior adds an INNER JOIN in query). + * + * @return void + */ + public function testFilteringBelongsToAndHasMany() + { + $testOptions = array + ( + 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), + 'DocumentCategories.id' => array('type' => 'select'), + 'Documents.is_private' => array('type' => 'checkbox', 'label' => 'Private?'), + 'Items.code' => array('type' => 'text'), + ); + $this->_reattachBehavior($testOptions); + + $filterValues = array + ( + 'Documents' => array('title' => 'in', 'is_private' => 0), + 'DocumentCategories' => array('id' => 1), + 'Items' => array('code' => '04') + ); + $this->Document->setFilterValues($filterValues); + + $expected = array + ( + array + ( + 'id' => 2, + 'title' => 'Imaginary Spec', + 'document_category_id' => 1, + 'owner_id' => 1, + 'is_private' => 0, + 'document_category' => 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-------'), + 'items' => 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() + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->contain([ + 'DocumentCategories' => [ + 'fields' => ['id', 'title', 'description'], + ], + 'Metadata' => [ + 'fields' => ['id', 'document_id', 'weight', 'size', 'permissions'], + ], + 'Items' => [ + 'fields' => ['id', 'document_id', 'code'], + ], + ]) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + + $expected = array + ( + array + ( + 'id' => 2, + 'title' => 'Imaginary Spec', + 'document_category_id' => 1, + 'owner_id' => 1, + 'is_private' => 0, + 'document_category' => 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() + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->contain([ + 'DocumentCategories' => [ + 'fields' => ['id', 'title', 'description'], + ], + 'Metadata' => [ + 'fields' => ['id', 'document_id', 'weight', 'size', 'permissions'], + ], + ]) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + + $this->Document->associations()->remove('Item'); + $this->Document->hasMany('Item'); + + $result = $this->Document->find() + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->contain([ + 'DocumentCategories' => [ + 'fields' => ['id', 'title', 'description'], + ], + 'Metadata' => [ + 'fields' => ['id', 'document_id', 'weight', 'size', 'permissions'], + ], + ]) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + + $expected = array + ( + array + ( + 'id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, + ) + ); + + $result = $this->Document->find() + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + } + + /** + * Test filtering with join which has some custom + * condition in the relation (both string and array). + * + * @return void + */ + 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 + ( + 'id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0, + 'metadata' => array('id' => 5, 'document_id' => 5, 'weight' => 4, 'size' => 790, 'permissions' => 'rw-rw-r--'), + ) + ); + $Metadata = $this->Document->associations()->get('Metadata'); + $this->assertInstanceOf(Association::class, $Metadata); + $oldConditions = $Metadata->getConditions(); + $Metadata->setConditions(['Metadata.size > 500']); + + $result = $this->Document->find() + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->contain([ + 'Metadata' => [ + 'fields' => ['id', 'document_id', 'weight', 'size', 'permissions'], + ], + ]) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + + $Metadata->setConditions(['Metadata.size > 500']); + $result = $this->Document->find() + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->contain([ + 'Metadata' => [ + 'fields' => ['id', 'document_id', 'weight', 'size', 'permissions'], + ], + ]) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + + $Metadata->setConditions($oldConditions); + } + + /** + * Test for any possible conflicts with Containable behavior. + * + * @return void + */ + public function testFilteringBelongsToAndHasManyWithContainable() + { + $testOptions = array + ( + 'Documents.title' => array('type' => 'text', 'condition' => 'like', 'required' => true), + 'DocumentCategories.id' => array('type' => 'select'), + 'Documents.is_private' => array('type' => 'checkbox', 'label' => 'Private?'), + 'Items.code' => array('type' => 'text'), + ); + + $this->_reattachBehavior($testOptions); + + $filterValues = array + ( + 'Documents' => array('title' => 'in', 'is_private' => 0), + 'DocumentCategories' => array('id' => 1), + 'Items' => array('code' => '04') + ); + $this->Document->setFilterValues($filterValues); + + $expected = array + ( + array + ( + 'id' => 2, + 'title' => 'Imaginary Spec', + 'document_category_id' => 1, + 'owner_id' => 1, + 'is_private' => 0, + 'document_category' => array('id' => 1, 'title' => 'Testing Doc', 'description' => 'It\'s a bleeding test doc!'), + 'items' => 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() + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->contain([ + 'DocumentCategories' => [ + 'fields' => ['id', 'title', 'description'], + ], + 'Items' => [ + 'fields' => ['id', 'document_id', 'code'], + ], + ]) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + + $expected = array + ( + array + ( + 'id' => 2, + 'title' => 'Imaginary Spec', + 'document_category_id' => 1, + 'owner_id' => 1, + 'is_private' => 0, + 'document_category' => array('id' => 1, 'title' => 'Testing Doc', 'description' => 'It\'s a bleeding test doc!'), + ) + ); + + $result = $this->Document->find() + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->contain([ + 'DocumentCategories' => [ + 'fields' => ['id', 'title', 'description'], + ], + ]) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + + $expected = array + ( + array + ( + 'id' => 2, + 'title' => 'Imaginary Spec', + 'document_category_id' => 1, + 'owner_id' => 1, + 'is_private' => 0, + ) + ); + + $result = $this->Document->find() + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + } + + /** + * Test filtering by text input with hasOne relation. + * + * @return void + */ + public function testHasOneAndHasManyWithTextSearch() + { + $testOptions = array + ( + 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), + 'Items.code' => array('type' => 'text'), + 'Metadata.size' => array('type' => 'text', 'condition' => '='), + ); + + $filterValues = array + ( + 'Documents' => array('title' => 'in'), + 'Items' => array('code' => '04'), + 'Metadata' => array('size' => 45), + ); + + $expected = array + ( + array + ( + 'id' => 2, + 'title' => 'Imaginary Spec', + ) + ); + + $this->_reattachBehavior($testOptions); + $this->Document->setFilterValues($filterValues); + + $result = $this->Document->find() + ->select(['id', 'title']) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + } + + /** + * Test filtering with Containable and hasOne Model.field. + * + * @return void + */ + public function testHasOneWithContainable() + { + $testOptions = array + ( + 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), + 'Items.code' => array('type' => 'text'), + 'Metadata.size' => array('type' => 'text', 'condition' => '='), + ); + + $filterValues = array + ( + 'Documents' => array('title' => 'in'), + 'Items' => array('code' => '04'), + 'Metadata' => array('size' => 45), + ); + + $expected = array + ( + array + ( + 'id' => 2, + 'title' => 'Imaginary Spec', + 'document_category_id' => 1, + 'owner_id' => 1, + 'is_private' => 0, + 'metadata' => array('id' => 2, 'document_id' => 2, 'weight' => 0, 'size' => 45, 'permissions' => 'rw-------'), + 'items' => 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->_reattachBehavior($testOptions); + $this->Document->setFilterValues($filterValues); + $result = $this->Document->find() + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->contain([ + 'Metadata' => [ + 'fields' => ['id', 'document_id', 'weight', 'size', 'permissions'], + ], + 'Items' => [ + 'fields' => ['id', 'document_id', 'code'], + ], + ]) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + + // filtered first, containable second + $this->_reattachBehavior($testOptions); + $this->Document->setFilterValues($filterValues); + $result = $this->Document->find() + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->contain([ + 'Metadata' => [ + 'fields' => ['id', 'document_id', 'weight', 'size', 'permissions'], + ], + 'Items' => [ + 'fields' => ['id', 'document_id', 'code'], + ], + ]) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + } + + /** + * Test filtering when a join is already present in the query, + * this should prevent duplicate joins and query errors. + * + * @return void + */ + public function testJoinAlreadyPresent() + { + $testOptions = array + ( + 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), + 'Items.code' => array('type' => 'text'), + 'Metadata.size' => array('type' => 'text', 'condition' => '='), + ); + + $filterValues = array + ( + 'Documents' => array('title' => 'in'), + 'Items' => array('code' => '04'), + 'Metadata' => array('size' => 45), + ); + + $expected = array + ( + array + ( + 'id' => 2, + 'title' => 'Imaginary Spec', + 'document_category_id' => 1, + 'owner_id' => 1, + 'is_private' => 0, + 'document_category' => 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-------'), + 'items' => 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' => 'FilterItems', + 'type' => 'INNER', + 'conditions' => 'Documents.id = FilterItems.document_id', + ); + + $this->_reattachBehavior($testOptions); + $this->Document->setFilterValues($filterValues); + $result = $this->Document->find() + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->contain([ + 'DocumentCategories' => [ + 'fields' => ['id', 'title', 'description'], + ], + 'Metadata' => [ + 'fields' => ['id', 'document_id', 'weight', 'size', 'permissions'], + ], + 'Items' => [ + 'fields' => ['id', 'document_id', 'code'], + ], + ]) + ->join($customJoin) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + } + + /** + * Test the 'nofilter' query param. + * + * @return void + */ + public function testNofilterFindParam() + { + $testOptions = array + ( + 'Documents.title' => array('type' => 'text', 'condition' => 'like'), + 'DocumentCategories.id' => array('type' => 'select'), + 'Documents.is_private' => array('type' => 'checkbox', 'label' => 'Private?', 'default' => 0) + ); + $this->_reattachBehavior($testOptions); + + + $filterValues = array + ( + 'DocumentCategories' => array('id' => 2), + 'Documents' => array('title' => '') + ); + $this->Document->setFilterValues($filterValues); + + $expected = array + ( + array('id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0) + ); + + $result = $this->Document->find('all', ['nofilter' => true]) + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->enableHydration(false) + ->toArray(); + $this->assertNotEquals($expected, $result); + + $result = $this->Document->find('all', ['nofilter' => 'true']) + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + } + + /** + * Test bailing out if no settings exist for the current model. + * + * @return void + */ + public function testExitWhenNoSettings() + { + $this->Document->DocumentCategories->addBehavior('Filter.Filtered'); + + $Filtered = $this->Document->DocumentCategories->behaviors()->get('Filtered'); + $this->assertFalse(isset($Filtered->settings[$this->Document->DocumentCategories->getAlias()])); + + $filterValues = array + ( + 'DocumentCategories' => array('id' => 2) + ); + $this->Document->DocumentCategories->setFilterValues($filterValues); + + $expected = 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!'), + ); + + $result = $this->Document->DocumentCategories->find('all', ['nofilter' => 'true']) + ->select(['id', 'title', 'description']) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + + $this->Document->DocumentCategories->removeBehavior('Filtered'); + } + + /** + * Test beforeDataFilter() callback, used to cancel filtering if necessary. + * + * @return void + */ + public function testBeforeDataFilterCallbackCancel() + { + $Document = $this->getTableLocator()->get('Document2', ['className' => Documents2Table::class]); + $this->assertInstanceOf(Documents2Table::class, $Document); + $this->Document = $Document; + $testOptions = array + ( + 'Documents.title' => array('type' => 'text', 'condition' => 'like'), + 'DocumentCategories.id' => array('type' => 'select'), + 'Documents.is_private' => array('type' => 'checkbox', 'label' => 'Private?') + ); + $this->_reattachBehavior($testOptions); + + + $filterValues = array + ( + 'DocumentCategories' => array('id' => 2) + ); + $this->Document->setFilterValues($filterValues); + + $expected = array + ( + array('id' => 1, 'title' => 'Testing Doc', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0,), + array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0), + array('id' => 3, 'title' => 'Nonexistant data', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0), + array('id' => 4, 'title' => 'Illegal explosives DIY', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 1), + array('id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0), + array('id' => 6, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0), + array('id' => 7, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0), + ); + + $result = $this->Document->find() + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + } + + /** + * Test afterDataFilter() callback, used to modify the conditions after + * filter conditions have been applied. + * + * @return void + */ + public function testAfterDataFilterCallbackQueryChange() + { + $Document = $this->getTableLocator()->get('Document3', ['className' => Documents3Table::class]); + $this->assertInstanceOf(Documents3Table::class, $Document); + $this->Document = $Document; + $this->Document->itemToUnset = 'FilterDocumentCategories.id'; + + $testOptions = array + ( + 'Documents.title' => array('type' => 'text', 'condition' => 'like'), + 'DocumentCategories.id' => array('type' => 'select'), + 'Documents.is_private' => array('type' => 'checkbox', 'label' => 'Private?') + ); + $this->_reattachBehavior($testOptions); + + + $filterValues = array + ( + 'DocumentCategories' => array('id' => 2) + ); + $this->Document->setFilterValues($filterValues); + + $expected = array + ( + array('id' => 1, 'title' => 'Testing Doc', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0), + array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0), + array('id' => 3, 'title' => 'Nonexistant data', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0), + array('id' => 4, 'title' => 'Illegal explosives DIY', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 1), + array('id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0), + array('id' => 6, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0), + array('id' => 7, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0), + ); + + $result = $this->Document->find('all') + ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) + ->enableHydration(false) + ->toArray(); + $this->assertEquals($expected, $result); + } } From 7f7ece7f8dfa83e916e145f6216044b8df5818b8 Mon Sep 17 00:00:00 2001 From: bancer Date: Mon, 28 Aug 2023 16:00:21 +0200 Subject: [PATCH 27/37] Fix code style errors in ItemsFixture --- tests/Fixture/ItemsFixture.php | 40 +++++++++++++++++----------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/tests/Fixture/ItemsFixture.php b/tests/Fixture/ItemsFixture.php index 5a42918..4e8a831 100644 --- a/tests/Fixture/ItemsFixture.php +++ b/tests/Fixture/ItemsFixture.php @@ -14,37 +14,37 @@ MPL LGPL GPL -*/ + */ class ItemsFixture extends TestFixture { /** * @var mixed[] */ - public $fields = array - ( - 'id' => array('type' => 'integer'), - 'document_id' => array('type' => 'integer', 'null' => false), - 'code' => array('type' => 'string', 'length' => '20', 'null' => false), + public $fields = + [ + 'id' => ['type' => 'integer'], + 'document_id' => ['type' => 'integer', 'null' => false], + 'code' => ['type' => 'string', 'length' => '20', 'null' => false], '_constraints' => [ 'primary' => ['type' => 'primary', 'columns' => ['id']], ], - ); + ]; /** * @var (int|string)[][] */ - public $records = array - ( - array('id' => 1, 'document_id' => 1, 'code' => 'The item #01'), - array('id' => 2, 'document_id' => 1, 'code' => 'The item #02'), - array('id' => 3, 'document_id' => 1, 'code' => 'The item #03'), - array('id' => 4, 'document_id' => 2, 'code' => 'The item #01'), - array('id' => 5, 'document_id' => 2, 'code' => 'The item #02'), - array('id' => 6, 'document_id' => 2, 'code' => 'The item #03'), - array('id' => 7, 'document_id' => 2, 'code' => 'The item #04'), - array('id' => 8, 'document_id' => 3, 'code' => 'The item #01'), - array('id' => 9, 'document_id' => 4, 'code' => 'The item #01'), - array('id' => 10, 'document_id' => 5, 'code' => 'The item #01') - ); + public $records = + [ + ['id' => 1, 'document_id' => 1, 'code' => 'The item #01'], + ['id' => 2, 'document_id' => 1, 'code' => 'The item #02'], + ['id' => 3, 'document_id' => 1, 'code' => 'The item #03'], + ['id' => 4, 'document_id' => 2, 'code' => 'The item #01'], + ['id' => 5, 'document_id' => 2, 'code' => 'The item #02'], + ['id' => 6, 'document_id' => 2, 'code' => 'The item #03'], + ['id' => 7, 'document_id' => 2, 'code' => 'The item #04'], + ['id' => 8, 'document_id' => 3, 'code' => 'The item #01'], + ['id' => 9, 'document_id' => 4, 'code' => 'The item #01'], + ['id' => 10, 'document_id' => 5, 'code' => 'The item #01'], + ]; } From 99c6140e86e2d6cd95c01432d84945321d729133 Mon Sep 17 00:00:00 2001 From: bancer Date: Mon, 28 Aug 2023 16:14:21 +0200 Subject: [PATCH 28/37] Fix code style errors in test files --- tests/Fixture/DocumentCategoriesFixture.php | 30 +- tests/Fixture/DocumentsFixture.php | 42 +- tests/Fixture/MetadataFixture.php | 34 +- .../Component/FilterComponentTest.php | 596 +++++++------- .../MockObjects/DocumentCategoriesTable.php | 4 +- .../MockObjects/DocumentTestsController.php | 2 +- .../TestCase/MockObjects/Documents2Table.php | 1 + .../TestCase/MockObjects/Documents3Table.php | 6 +- tests/TestCase/MockObjects/DocumentsTable.php | 1 + tests/TestCase/MockObjects/ItemsTable.php | 1 + tests/TestCase/MockObjects/MetadataTable.php | 1 + .../Model/Behavior/FilteredBehaviorTest.php | 734 +++++++++--------- 12 files changed, 728 insertions(+), 724 deletions(-) diff --git a/tests/Fixture/DocumentCategoriesFixture.php b/tests/Fixture/DocumentCategoriesFixture.php index 9a223c4..8293c63 100644 --- a/tests/Fixture/DocumentCategoriesFixture.php +++ b/tests/Fixture/DocumentCategoriesFixture.php @@ -14,32 +14,32 @@ MPL LGPL GPL -*/ + */ class DocumentCategoriesFixture extends TestFixture { /** * @var mixed[] */ - public $fields = array - ( - 'id' => array('type' => 'integer'), - 'title' => array('type' => 'string', 'length' => 100, 'null' => false), - 'description' => array('type' => 'string', 'length' => 255), + public $fields = + [ + 'id' => ['type' => 'integer'], + 'title' => ['type' => 'string', 'length' => 100, 'null' => false], + 'description' => ['type' => 'string', 'length' => 255], '_constraints' => [ 'primary' => ['type' => 'primary', 'columns' => ['id']], ], - ); + ]; /** * @var (int|string)[][] */ - public $records = array - ( - array('id' => 1, 'title' => 'Testing Doc', 'description' => 'It\'s a bleeding test doc!'), - array('id' => 2, 'title' => 'Imaginary Spec', 'description' => 'This doc does not exist'), - array('id' => 3, 'title' => 'Nonexistant data', 'description' => 'This doc is probably empty'), - array('id' => 4, 'title' => 'Illegal explosives DIY', 'description' => 'Viva la revolucion!'), - array('id' => 5, 'title' => 'Father Ted', 'description' => 'Feck! Drink! Arse! Girls!') - ); + public $records = + [ + ['id' => 1, 'title' => 'Testing Doc', 'description' => 'It\'s a bleeding test doc!'], + ['id' => 2, 'title' => 'Imaginary Spec', 'description' => 'This doc does not exist'], + ['id' => 3, 'title' => 'Nonexistant data', 'description' => 'This doc is probably empty'], + ['id' => 4, 'title' => 'Illegal explosives DIY', 'description' => 'Viva la revolucion!'], + ['id' => 5, 'title' => 'Father Ted', 'description' => 'Feck! Drink! Arse! Girls!'], + ]; } diff --git a/tests/Fixture/DocumentsFixture.php b/tests/Fixture/DocumentsFixture.php index 6172f95..ef469a0 100644 --- a/tests/Fixture/DocumentsFixture.php +++ b/tests/Fixture/DocumentsFixture.php @@ -14,38 +14,38 @@ MPL LGPL GPL -*/ + */ class DocumentsFixture extends TestFixture { /** * @var mixed[] */ - public $fields = array - ( - 'id' => array('type' => 'integer'), - 'title' => array('type' => 'string', 'length' => '255', 'null' => false), - 'document_category_id' => array('type' => 'integer', 'null' => false), - 'owner_id' => array('type' => 'integer', 'null' => false), - 'is_private' => array('type' => 'integer', 'length' => 1, 'null' => false), - 'created' => array('type' => 'datetime', 'null' => false), - 'updated' => array('type' => 'datetime', 'null' => true), + public $fields = + [ + 'id' => ['type' => 'integer'], + 'title' => ['type' => 'string', 'length' => '255', 'null' => false], + 'document_category_id' => ['type' => 'integer', 'null' => false], + 'owner_id' => ['type' => 'integer', 'null' => false], + 'is_private' => ['type' => 'integer', 'length' => 1, 'null' => false], + 'created' => ['type' => 'datetime', 'null' => false], + 'updated' => ['type' => 'datetime', 'null' => true], '_constraints' => [ 'primary' => ['type' => 'primary', 'columns' => ['id']], ], - ); + ]; /** * @var (int|string)[][] */ - public $records = array - ( - array('id' => 1, 'title' => 'Testing Doc', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-06-28 10:39:23', 'updated' => '2010-06-29 11:22:48'), - array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-03-28 12:19:13', 'updated' => '2010-04-29 11:23:44'), - array('id' => 3, 'title' => 'Nonexistant data', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-04-28 11:12:33', 'updated' => '2010-05-05 15:03:24'), - array('id' => 4, 'title' => 'Illegal explosives DIY', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 1, 'created' => '2010-01-08 05:15:03', 'updated' => '2010-05-22 03:15:24'), - array('id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0, 'created' => '2009-01-13 05:15:03', 'updated' => '2010-12-05 03:24:15'), - array('id' => 6, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0, 'created' => '2009-01-13 05:15:03', 'updated' => '2010-12-05 03:24:15'), - array('id' => 7, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0, 'created' => '2009-01-13 05:15:03', 'updated' => '2010-12-05 03:24:15'), - ); + public $records = + [ + ['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'], + ['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'], + ['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'], + ['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'], + ['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'], + ['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'], + ['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/tests/Fixture/MetadataFixture.php b/tests/Fixture/MetadataFixture.php index b4ee141..a5b2b33 100644 --- a/tests/Fixture/MetadataFixture.php +++ b/tests/Fixture/MetadataFixture.php @@ -14,34 +14,34 @@ MPL LGPL GPL -*/ + */ class MetadataFixture extends TestFixture { /** * @var mixed[] */ - public $fields = array - ( - 'id' => array('type' => 'integer'), - 'document_id' => array('type' => 'integer', 'null' => false), - 'weight' => array('type' => 'integer', 'null' => false), - 'size' => array('type' => 'integer', 'null' => false), - 'permissions' => array('type' => 'string', 'length' => 10, 'null' => false), + public $fields = + [ + 'id' => ['type' => 'integer'], + 'document_id' => ['type' => 'integer', 'null' => false], + 'weight' => ['type' => 'integer', 'null' => false], + 'size' => ['type' => 'integer', 'null' => false], + 'permissions' => ['type' => 'string', 'length' => 10, 'null' => false], '_constraints' => [ 'primary' => ['type' => 'primary', 'columns' => ['id']], ], - ); + ]; /** * @var (int|string)[][] */ - public $records = array - ( - array('id' => 1, 'document_id' => 1, 'weight' => 5, 'size' => 256, 'permissions' => 'rw-r--r--'), - array('id' => 2, 'document_id' => 2, 'weight' => 0, 'size' => 45, 'permissions' => 'rw-------'), - array('id' => 3, 'document_id' => 3, 'weight' => 2, 'size' => 78, 'permissions' => 'rw-rw-r--'), - array('id' => 4, 'document_id' => 4, 'weight' => 1, 'size' => 412, 'permissions' => 'rw-r--r--'), - array('id' => 5, 'document_id' => 5, 'weight' => 4, 'size' => 790, 'permissions' => 'rw-rw-r--'), - ); + public $records = + [ + ['id' => 1, 'document_id' => 1, 'weight' => 5, 'size' => 256, 'permissions' => 'rw-r--r--'], + ['id' => 2, 'document_id' => 2, 'weight' => 0, 'size' => 45, 'permissions' => 'rw-------'], + ['id' => 3, 'document_id' => 3, 'weight' => 2, 'size' => 78, 'permissions' => 'rw-rw-r--'], + ['id' => 4, 'document_id' => 4, 'weight' => 1, 'size' => 412, 'permissions' => 'rw-r--r--'], + ['id' => 5, 'document_id' => 5, 'weight' => 4, 'size' => 790, 'permissions' => 'rw-rw-r--'], + ]; } diff --git a/tests/TestCase/Controller/Component/FilterComponentTest.php b/tests/TestCase/Controller/Component/FilterComponentTest.php index 0c37045..b78a582 100644 --- a/tests/TestCase/Controller/Component/FilterComponentTest.php +++ b/tests/TestCase/Controller/Component/FilterComponentTest.php @@ -1,12 +1,13 @@ LGPL GPL -*/ + */ class FilterComponentTest extends TestCase { /** * @var string[] */ - public $fixtures = array - ( + public $fixtures = + [ 'plugin.Filter.DocumentCategories', 'plugin.Filter.Documents', 'plugin.Filter.Items', 'plugin.Filter.Metadata', - ); + ]; /** * @var \Filter\Test\TestCase\MockObjects\DocumentTestsController @@ -75,31 +76,31 @@ public function testNoFilters() */ public function testNoActionFilters() { - $testSettings = array - ( - 'someotheraction' => array - ( - 'Document' => array - ( - 'Document.title' => array('type' => 'text') - ) - ) - ); + $testSettings = + [ + 'someotheraction' => + [ + 'Document' => + [ + 'Document.title' => ['type' => 'text'], + ], + ], + ]; $this->Controller->filters = $testSettings; $this->Controller->dispatchEvent('Controller.initialize'); $this->assertFalse($this->Controller->Document->hasBehavior('Filtered')); - $testSettings = array - ( - 'index' => array - ( - 'Document' => array - ( - 'Document.title' => array('type' => 'text') - ) - ), - ); + $testSettings = + [ + 'index' => + [ + 'Document' => + [ + 'Document.title' => ['type' => 'text'], + ], + ], + ]; $this->Controller->filters = $testSettings; $this->Controller->dispatchEvent('Controller.initialize'); @@ -113,22 +114,22 @@ public function testNoActionFilters() */ public function testBasicFilters() { - $testSettings = array - ( - 'index' => array - ( - 'Document' => array - ( - 'Document.title' => array('type' => 'text') - ) - ) - ); + $testSettings = + [ + 'index' => + [ + 'Document' => + [ + 'Document.title' => ['type' => 'text'], + ], + ], + ]; $this->Controller->filters = $testSettings; - $expected = array - ( - $this->Controller->getName() => $testSettings - ); + $expected = + [ + $this->Controller->getName() => $testSettings, + ]; $this->Controller->dispatchEvent('Controller.initialize'); $this->assertEquals($expected, $this->Controller->Filter->settings); } @@ -140,16 +141,16 @@ public function testBasicFilters() */ public function testEmptyStartup() { - $testSettings = array - ( - 'index' => array - ( - 'Document' => array - ( - 'Document.title' => array('type' => 'text') - ) - ) - ); + $testSettings = + [ + 'index' => + [ + 'Document' => + [ + 'Document.title' => ['type' => 'text'], + ], + ], + ]; $this->Controller->filters = $testSettings; $this->Controller->dispatchEvent('Controller.initialize'); @@ -162,23 +163,23 @@ public function testEmptyStartup() */ public function testSessionStartupDataFakeNonexistantModel() { - $testSettings = array - ( - 'index' => array - ( - 'FakeNonexistant' => array - ( - 'drink' => array('type' => 'select') - ) - ) - ); + $testSettings = + [ + 'index' => + [ + 'FakeNonexistant' => + [ + 'drink' => ['type' => 'select'], + ], + ], + ]; $this->Controller->filters = $testSettings; $sessionKey = sprintf( 'FilterPlugin.Filters.%s.%s', $this->Controller->getName(), $this->Controller->getRequest()->getParam('action') ); - $filterValues = array(); + $filterValues = []; $this->Controller->getRequest()->getSession()->write($sessionKey, $filterValues); $this->expectException('PHPUnit\Framework\Error\Notice'); $this->Controller->dispatchEvent('Controller.initialize'); @@ -191,16 +192,16 @@ public function testSessionStartupDataFakeNonexistantModel() */ public function testSessionStartupData() { - $testSettings = array - ( - 'index' => array - ( - 'Document' => array - ( - 'Document.title' => array('type' => 'text') - ), - ) - ); + $testSettings = + [ + 'index' => + [ + 'Document' => + [ + 'Document.title' => ['type' => 'text'], + ], + ], + ]; $this->Controller->filters = $testSettings; $sessionKey = sprintf( @@ -209,28 +210,26 @@ public function testSessionStartupData() $this->Controller->getRequest()->getParam('action') ); - $filterValues = array(); + $filterValues = []; $this->Controller->getRequest()->getSession()->write($sessionKey, $filterValues); $this->Controller->dispatchEvent('Controller.initialize'); $this->Controller->dispatchEvent('Controller.startup'); $actualFilterValues = $this->Controller->Document->getFilterValues(); - $this->assertEquals - ( - $filterValues, - $actualFilterValues[$this->Controller->Document->getAlias()] - ); + $this->assertEquals( + $filterValues, + $actualFilterValues[$this->Controller->Document->getAlias()] + ); - $filterValues = array('Document' => array('title' => 'in')); + $filterValues = ['Document' => ['title' => 'in']]; $this->Controller->getRequest()->getSession()->write($sessionKey, $filterValues); $this->Controller->dispatchEvent('Controller.startup'); $actualFilterValues = $this->Controller->Document->getFilterValues(); - $this->assertEquals - ( - $filterValues, - $actualFilterValues[$this->Controller->Document->getAlias()] - ); + $this->assertEquals( + $filterValues, + $actualFilterValues[$this->Controller->Document->getAlias()] + ); $this->Controller->getRequest()->getSession()->delete($sessionKey); } @@ -252,20 +251,20 @@ public function testPostStartupData() ], ]); $this->Controller = new DocumentTestsController($request); - $testSettings = array - ( - 'index' => array - ( - 'Document' => array - ( - 'Document.title' => array('type' => 'text') - ), - ) - ); + $testSettings = + [ + 'index' => + [ + 'Document' => + [ + 'Document.title' => ['type' => 'text'], + ], + ], + ]; $this->Controller->filters = $testSettings; - $filterValues = array('Document' => array('title' => 'in'), 'Filter' => array('filterFormId' => 'Document')); + $filterValues = ['Document' => ['title' => 'in'], 'Filter' => ['filterFormId' => 'Document']]; $this->Controller->request = $this->Controller->getRequest()->withParsedBody($filterValues); $this->Controller->dispatchEvent('Controller.initialize'); @@ -280,11 +279,10 @@ public function testPostStartupData() $this->assertEquals($filterValues, $sessionData); $actualFilterValues = $this->Controller->Document->getFilterValues(); - $this->assertEquals - ( - $filterValues, - $actualFilterValues[$this->Controller->Document->getAlias()] - ); + $this->assertEquals( + $filterValues, + $actualFilterValues[$this->Controller->Document->getAlias()] + ); } /** @@ -294,16 +292,16 @@ public function testPostStartupData() */ public function testBeforeRenderAbort() { - $testSettings = array - ( - 'veryMuchNotIndex' => array - ( - 'Document' => array - ( - 'Document.title' => array('type' => 'text') - ) - ) - ); + $testSettings = + [ + 'veryMuchNotIndex' => + [ + 'Document' => + [ + 'Document.title' => ['type' => 'text'], + ], + ], + ]; $this->Controller->filters = $testSettings; $this->Controller->dispatchEvent('Controller.initialize'); @@ -321,16 +319,16 @@ public function testBeforeRenderAbort() */ public function testNoModelFound() { - $testSettings = array - ( - 'index' => array - ( - 'ThisModelDoesNotExist' => array - ( - 'ThisModelDoesNotExist.title' => array('type' => 'text') - ) - ) - ); + $testSettings = + [ + 'index' => + [ + 'ThisModelDoesNotExist' => + [ + 'ThisModelDoesNotExist.title' => ['type' => 'text'], + ], + ], + ]; $this->Controller->filters = $testSettings; $this->expectException('PHPUnit\Framework\Error\Notice'); $this->Controller->dispatchEvent('Controller.initialize'); @@ -344,49 +342,49 @@ public function testNoModelFound() */ public function testBasicViewInfo() { - $testSettings = array - ( - 'index' => array - ( - 'Document' => array - ( + $testSettings = + [ + 'index' => + [ + 'Document' => + [ 'title', - 'DocumentCategory.id' => array( + 'DocumentCategory.id' => [ 'type' => 'select', 'label' => 'Category', 'className' => DocumentCategoriesTable::class, - ), - ) - ) - ); + ], + ], + ], + ]; $this->Controller->filters = $testSettings; $this->Controller->dispatchEvent('Controller.initialize'); $this->Controller->dispatchEvent('Controller.startup'); $this->Controller->dispatchEvent('Controller.beforeRender'); - $expected = array - ( - array('name' => 'Document.title', 'options' => array('type' => 'text')), - array - ( + $expected = + [ + ['name' => 'Document.title', 'options' => ['type' => 'text']], + + [ 'name' => 'DocumentCategory.id', - 'options' => array - ( + 'options' => + [ 'type' => 'select', - 'options' => array - ( + 'options' => + [ 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']); } @@ -399,60 +397,60 @@ public function testBasicViewInfo() */ public function testAdditionalInputOptions() { - $testSettings = array - ( - 'index' => array - ( - 'Document' => array - ( - 'title' => array('inputOptions' => 'disabled'), - 'DocumentCategory.id' => array - ( + $testSettings = + [ + 'index' => + [ + 'Document' => + [ + 'title' => ['inputOptions' => 'disabled'], + 'DocumentCategory.id' => + [ 'type' => 'select', 'label' => 'Category', - 'inputOptions' => array('class' => 'important'), + 'inputOptions' => ['class' => 'important'], 'className' => DocumentCategoriesTable::class, - ), - ) - ) - ); + ], + ], + ], + ]; $this->Controller->filters = $testSettings; $this->Controller->dispatchEvent('Controller.initialize'); $this->Controller->dispatchEvent('Controller.startup'); $this->Controller->dispatchEvent('Controller.beforeRender'); - $expected = array - ( - array - ( + $expected = + [ + + [ 'name' => 'Document.title', - 'options' => array - ( + 'options' => + [ 'type' => 'text', - 'disabled' - ) - ), - array - ( + 'disabled', + ], + ], + + [ 'name' => 'DocumentCategory.id', - 'options' => array - ( + 'options' => + [ 'type' => 'select', - 'options' => array - ( + 'options' => + [ 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']); } @@ -465,49 +463,49 @@ public function testAdditionalInputOptions() */ public function testCustomSelector() { - $testSettings = array - ( - 'index' => array - ( - 'Document' => array - ( - 'DocumentCategory.id' => array - ( + $testSettings = + [ + 'index' => + [ + 'Document' => + [ + 'DocumentCategory.id' => + [ 'type' => 'select', 'label' => 'Category', 'selector' => 'customSelector', - 'selectOptions' => array( - 'conditions' => array('DocumentCategory.description LIKE' => '%!%'), - ), + 'selectOptions' => [ + 'conditions' => ['DocumentCategory.description LIKE' => '%!%'], + ], 'className' => DocumentCategoriesTable::class, - ), - ) - ) - ); + ], + ], + ], + ]; $this->Controller->filters = $testSettings; $this->Controller->dispatchEvent('Controller.initialize'); $this->Controller->dispatchEvent('Controller.startup'); $this->Controller->dispatchEvent('Controller.beforeRender'); - $expected = array - ( - array - ( + $expected = + [ + + [ 'name' => 'DocumentCategory.id', - 'options' => array - ( + 'options' => + [ 'type' => 'select', - 'options' => array - ( + 'options' => + [ 1 => 'Testing Doc', 5 => 'Father Ted', - ), + ], 'empty' => false, 'label' => 'Category', - ) - ), - ); + ], + ], + ]; $this->assertEquals($expected, $this->Controller->viewVars['viewFilterParams']); } @@ -519,40 +517,40 @@ public function testCustomSelector() */ public function testCheckboxOptions() { - $testSettings = array - ( - 'index' => array - ( - 'Document' => array - ( - 'Document.is_private' => array - ( + $testSettings = + [ + 'index' => + [ + 'Document' => + [ + 'Document.is_private' => + [ 'type' => 'checkbox', 'label' => 'Private?', 'default' => true, - ), - ) - ) - ); + ], + ], + ], + ]; $this->Controller->filters = $testSettings; $this->Controller->dispatchEvent('Controller.initialize'); $this->Controller->dispatchEvent('Controller.startup'); $this->Controller->dispatchEvent('Controller.beforeRender'); - $expected = array - ( - array - ( + $expected = + [ + + [ 'name' => 'Document.is_private', - 'options' => array - ( + 'options' => + [ 'type' => 'checkbox', 'checked' => true, 'label' => 'Private?', - ) - ), - ); + ], + ], + ]; $this->assertEquals($expected, $this->Controller->viewVars['viewFilterParams']); } @@ -564,26 +562,26 @@ public function testCheckboxOptions() */ public function testSelectMultiple() { - $testSettings = array - ( - 'index' => array - ( - 'Document' => array - ( - 'DocumentCategory.id' => array - ( + $testSettings = + [ + 'index' => + [ + 'Document' => + [ + 'DocumentCategory.id' => + [ 'type' => 'select', 'multiple' => true, - ) - ) - ) - ); + ], + ], + ], + ]; $this->Controller->filters = $testSettings; - $expected = array - ( - $this->Controller->getName() => $testSettings - ); + $expected = + [ + $this->Controller->getName() => $testSettings, + ]; $this->Controller->dispatchEvent('Controller.initialize'); $this->assertEquals($expected, $this->Controller->Filter->settings); @@ -596,47 +594,47 @@ public function testSelectMultiple() */ public function testSelectInputFromSameModel() { - $testSettings = array - ( - 'index' => array - ( - 'Document' => array - ( - 'Document.title' => array - ( + $testSettings = + [ + 'index' => + [ + 'Document' => + [ + 'Document.title' => + [ 'type' => 'select', 'className' => DocumentsTable::class, - ), - ) - ) - ); + ], + ], + ], + ]; $this->Controller->filters = $testSettings; $this->Controller->dispatchEvent('Controller.initialize'); $this->Controller->dispatchEvent('Controller.startup'); $this->Controller->dispatchEvent('Controller.beforeRender'); - $expected = array - ( - array - ( + $expected = + [ + + [ 'name' => 'Document.title', - 'options' => array - ( + 'options' => + [ 'type' => 'select', - 'options' => array - ( + 'options' => + [ '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']); } @@ -649,16 +647,16 @@ public function testSelectInputFromSameModel() */ public function testPersistence() { - $testSettings = array - ( - 'index' => array - ( - 'Document' => array - ( - 'Document.title' => array('type' => 'text') - ), - ), - ); + $testSettings = + [ + 'index' => + [ + 'Document' => + [ + 'Document.title' => ['type' => 'text'], + ], + ], + ]; $this->Controller->filters = $testSettings; $this->Controller->components()->unload('Filter'); $this->Controller->loadComponent('Filter.Filter', ['nopersist' => true]); @@ -668,7 +666,7 @@ public function testPersistence() 'SomeOtherController', $this->Controller->getRequest()->getParam('action') ); - $filterValues = array('Document' => array('title' => 'in'), 'Filter' => array('filterFormId' => 'Document')); + $filterValues = ['Document' => ['title' => 'in'], 'Filter' => ['filterFormId' => 'Document']]; $this->Controller->getRequest()->getSession()->write($sessionKey, $filterValues); $sessionKey = sprintf( @@ -676,21 +674,21 @@ public function testPersistence() $this->Controller->getName(), $this->Controller->getRequest()->getParam('action') ); - $filterValues = array('Document' => array('title' => 'in'), 'Filter' => array('filterFormId' => 'Document')); + $filterValues = ['Document' => ['title' => 'in'], 'Filter' => ['filterFormId' => 'Document']]; $this->Controller->getRequest()->getSession()->write($sessionKey, $filterValues); - $this->Controller->Filter->nopersist = array(); + $this->Controller->Filter->nopersist = []; $this->Controller->Filter->nopersist[$this->Controller->getName()] = true; $this->Controller->Filter->nopersist['SomeOtherController'] = true; $this->Controller->dispatchEvent('Controller.initialize'); $this->Controller->dispatchEvent('Controller.startup'); - $expected = array( - $this->Controller->getName() => array( + $expected = [ + $this->Controller->getName() => [ $this->Controller->getRequest()->getParam('action') => $filterValues, - ), - ); + ], + ]; $this->assertEquals($expected, $this->Controller->getRequest()->getSession()->read('FilterPlugin.Filters')); } @@ -702,33 +700,33 @@ public function testPersistence() */ public function testBelongsToFilteringByText() { - $testSettings = array - ( - 'index' => array - ( - 'Document' => array - ( - 'DocumentCategory.title' => array('type' => 'text') - ), - ) - ); + $testSettings = + [ + 'index' => + [ + 'Document' => + [ + 'DocumentCategory.title' => ['type' => 'text'], + ], + ], + ]; $this->Controller->filters = $testSettings; $this->Controller->dispatchEvent('Controller.initialize'); $this->Controller->dispatchEvent('Controller.startup'); $this->Controller->dispatchEvent('Controller.beforeRender'); - $expected = array - ( - array - ( + $expected = + [ + + [ 'name' => 'DocumentCategory.title', - 'options' => array - ( + 'options' => + [ 'type' => 'text', - ) - ), - ); + ], + ], + ]; $this->assertEquals($expected, $this->Controller->viewVars['viewFilterParams']); } diff --git a/tests/TestCase/MockObjects/DocumentCategoriesTable.php b/tests/TestCase/MockObjects/DocumentCategoriesTable.php index cdb3017..0146ef8 100644 --- a/tests/TestCase/MockObjects/DocumentCategoriesTable.php +++ b/tests/TestCase/MockObjects/DocumentCategoriesTable.php @@ -1,4 +1,5 @@ find('list', $options) ->where([ 'DocumentCategory.title LIKE' => '%T%', diff --git a/tests/TestCase/MockObjects/DocumentTestsController.php b/tests/TestCase/MockObjects/DocumentTestsController.php index 0809dce..92e2e3b 100644 --- a/tests/TestCase/MockObjects/DocumentTestsController.php +++ b/tests/TestCase/MockObjects/DocumentTestsController.php @@ -1,4 +1,5 @@ itemToUnset)) - { + if (!is_string($this->itemToUnset)) { return $query; } $query->clause('where')->iterateParts(function ($Comparison) { @@ -48,8 +48,10 @@ public function afterDataFilter($query, $options) if ($field == $this->itemToUnset) { return null; } + return $Comparison; }); + return $query; } } diff --git a/tests/TestCase/MockObjects/DocumentsTable.php b/tests/TestCase/MockObjects/DocumentsTable.php index 15792f0..0d44699 100644 --- a/tests/TestCase/MockObjects/DocumentsTable.php +++ b/tests/TestCase/MockObjects/DocumentsTable.php @@ -1,4 +1,5 @@ LGPL GPL -*/ + */ class FilteredBehaviorTest extends TestCase { /** * @var string[] */ - public $fixtures = array - ( + public $fixtures = + [ 'plugin.Filter.DocumentCategories', 'plugin.Filter.Documents', 'plugin.Filter.Items', 'plugin.Filter.Metadata', - ); + ]; /** * @var \Filter\Test\TestCase\MockObjects\DocumentsTable|\Filter\Test\TestCase\MockObjects\Documents2Table|\Filter\Test\TestCase\MockObjects\Documents3Table @@ -59,7 +60,7 @@ public function tearDown() * @param mixed[] $options Behavior options. * @return void */ - protected function _reattachBehavior($options = array()) + protected function _reattachBehavior($options = []) { if ($this->Document->hasBehavior('Filtered')) { $this->Document->removeBehavior('Filtered'); @@ -85,20 +86,20 @@ public function testBlankAttaching() */ public function testInitSettings() { - $testOptions = array - ( - 'Documents.title' => array('type' => 'text', 'condition' => 'like'), - 'DocumentCategories.id' => array('type' => 'select', 'filterField' => 'document_category_id'), - 'Documents.is_private' => array('type' => 'checkbox', 'label' => 'Private?') - ); + $testOptions = + [ + 'Documents.title' => ['type' => 'text', 'condition' => 'like'], + 'DocumentCategories.id' => ['type' => 'select', 'filterField' => 'document_category_id'], + 'Documents.is_private' => ['type' => 'checkbox', 'label' => 'Private?'], + ]; $this->_reattachBehavior($testOptions); - $expected = array - ( - 'Documents.title' => array('type' => 'text', 'condition' => 'like', 'required' => false, 'selectOptions' => array()), - 'DocumentCategories.id' => array('type' => 'select', 'filterField' => 'document_category_id', 'condition' => 'like', 'required' => false, 'selectOptions' => array()), - 'Documents.is_private' => array('type' => 'checkbox', 'label' => 'Private?', 'condition' => 'like', 'required' => false, 'selectOptions' => array()) - ); + $expected = + [ + 'Documents.title' => ['type' => 'text', 'condition' => 'like', 'required' => false, 'selectOptions' => []], + 'DocumentCategories.id' => ['type' => 'select', 'filterField' => 'document_category_id', 'condition' => 'like', 'required' => false, 'selectOptions' => []], + 'Documents.is_private' => ['type' => 'checkbox', 'label' => 'Private?', 'condition' => 'like', 'required' => false, 'selectOptions' => []], + ]; $Filtered = $this->Document->getBehavior('Filtered'); $this->assertInstanceOf(FilteredBehavior::class, $Filtered); $this->assertEquals($expected, $Filtered->settings[$this->Document->getAlias()]); @@ -111,13 +112,13 @@ public function testInitSettings() */ public function testInitSettingsSingle() { - $testOptions = array('Documents.title'); + $testOptions = ['Documents.title']; $this->_reattachBehavior($testOptions); - $expected = array - ( - 'Documents.title' => array('type' => 'text', 'condition' => 'like', 'required' => false, 'selectOptions' => array()), - ); + $expected = + [ + 'Documents.title' => ['type' => 'text', 'condition' => 'like', 'required' => false, 'selectOptions' => []], + ]; $Filtered = $this->Document->getBehavior('Filtered'); $this->assertInstanceOf(FilteredBehavior::class, $Filtered); $this->assertEquals($expected, $Filtered->settings[$this->Document->getAlias()]); @@ -130,20 +131,20 @@ public function testInitSettingsSingle() */ public function testSetFilterValues() { - $testOptions = array - ( - 'Documents.title' => array('type' => 'text', 'condition' => 'like', 'required' => true), - 'DocumentCategories.id' => array('type' => 'select', 'filterField' => 'document_category_id'), - 'Documents.is_private' => array('type' => 'checkbox', 'label' => 'Private?') - ); + $testOptions = + [ + 'Documents.title' => ['type' => 'text', 'condition' => 'like', 'required' => true], + 'DocumentCategories.id' => ['type' => 'select', 'filterField' => 'document_category_id'], + 'Documents.is_private' => ['type' => 'checkbox', 'label' => 'Private?'], + ]; $this->_reattachBehavior($testOptions); - $filterValues = array - ( - 'Documents' => array('title' => 'in', 'is_private' => 0), - 'DocumentCategories' => array('id' => 1) - ); + $filterValues = + [ + 'Documents' => ['title' => 'in', 'is_private' => 0], + 'DocumentCategories' => ['id' => 1], + ]; $this->Document->setFilterValues($filterValues); $actualFilterValues = $this->Document->getFilterValues(); @@ -157,19 +158,19 @@ public function testSetFilterValues() */ public function testLoadingRequiredFieldValueMissing() { - $testOptions = array - ( - 'Documents.title' => array('type' => 'text', 'condition' => 'like', 'required' => true), - 'DocumentCategories.id' => array('type' => 'select', 'filterField' => 'document_category_id'), - 'Documents.is_private' => array('type' => 'checkbox', 'label' => 'Private?') - ); + $testOptions = + [ + 'Documents.title' => ['type' => 'text', 'condition' => 'like', 'required' => true], + 'DocumentCategories.id' => ['type' => 'select', 'filterField' => 'document_category_id'], + 'Documents.is_private' => ['type' => 'checkbox', 'label' => 'Private?'], + ]; $this->_reattachBehavior($testOptions); - $filterValues = array - ( - 'Documents' => array('is_private' => 0), - 'DocumentCategories' => array('id' => 1) - ); + $filterValues = + [ + 'Documents' => ['is_private' => 0], + 'DocumentCategories' => ['id' => 1], + ]; $this->Document->setFilterValues($filterValues); $this->expectException('PHPUnit\Framework\Error\Notice'); @@ -183,25 +184,25 @@ public function testLoadingRequiredFieldValueMissing() */ public function testFilteringBelongsTo() { - $testOptions = array - ( - 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), - 'DocumentCategories.id' => array('type' => 'select') - ); + $testOptions = + [ + 'title' => ['type' => 'text', 'condition' => 'like', 'required' => true], + 'DocumentCategories.id' => ['type' => 'select'], + ]; $this->_reattachBehavior($testOptions); - $filterValues = array - ( - 'Documents' => array('title' => 'in'), - 'DocumentCategories' => array('id' => 1) - ); + $filterValues = + [ + 'Documents' => ['title' => 'in'], + 'DocumentCategories' => ['id' => 1], + ]; $this->Document->setFilterValues($filterValues); - $expected = array - ( - array('id' => 1, 'title' => 'Testing Doc', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0), - array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0) - ); + $expected = + [ + ['id' => 1, 'title' => 'Testing Doc', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0], + ['id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0], + ]; $result = $this->Document->find() ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) @@ -215,22 +216,22 @@ public function testFilteringBelongsTo() */ public function testFilteringBelongsToTextField() { - $testOptions = array - ( - 'DocumentCategories.title' => array('type' => 'text') - ); + $testOptions = + [ + 'DocumentCategories.title' => ['type' => 'text'], + ]; $this->_reattachBehavior($testOptions); - $filterValues = array - ( - 'DocumentCategories' => array('title' => 'spec') - ); + $filterValues = + [ + 'DocumentCategories' => ['title' => 'spec'], + ]; $this->Document->setFilterValues($filterValues); - $expected = array - ( - array('id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0) - ); + $expected = + [ + ['id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0], + ]; $result = $this->Document->find() ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) @@ -248,25 +249,25 @@ public function testFilteringBelongsToTextField() */ public function testFilteringBelongsToFilterFieldTest() { - $testOptions = array - ( - 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), - 'DocumentCategories.id' => array('type' => 'select', 'filterField' => 'Documents.document_category_id') - ); + $testOptions = + [ + 'title' => ['type' => 'text', 'condition' => 'like', 'required' => true], + 'DocumentCategories.id' => ['type' => 'select', 'filterField' => 'Documents.document_category_id'], + ]; $this->_reattachBehavior($testOptions); - $filterValues = array - ( - 'Documents' => array('title' => 'in'), - 'DocumentCategories' => array('id' => 1) - ); + $filterValues = + [ + 'Documents' => ['title' => 'in'], + 'DocumentCategories' => ['id' => 1], + ]; $this->Document->setFilterValues($filterValues); - $expected = array - ( - array('id' => 1, 'title' => 'Testing Doc', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0), - array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0) - ); + $expected = + [ + ['id' => 1, 'title' => 'Testing Doc', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0], + ['id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0], + ]; $result = $this->Document->find() ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) @@ -283,24 +284,24 @@ public function testFilteringBelongsToFilterFieldTest() */ public function testFilteringBelongsToDifferentConditions() { - $testOptions = array - ( - 'title' => array('type' => 'text', 'condition' => '='), - 'DocumentCategories.id' => array('type' => 'select') - ); + $testOptions = + [ + 'title' => ['type' => 'text', 'condition' => '='], + 'DocumentCategories.id' => ['type' => 'select'], + ]; $this->_reattachBehavior($testOptions); - $filterValues = array - ( - 'Documents' => array('title' => 'Illegal explosives DIY'), - 'DocumentCategories' => array('id' => '') - ); + $filterValues = + [ + 'Documents' => ['title' => 'Illegal explosives DIY'], + 'DocumentCategories' => ['id' => ''], + ]; $this->Document->setFilterValues($filterValues); - $expected = array - ( - array('id' => 4, 'title' => 'Illegal explosives DIY', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 1), - ); + $expected = + [ + ['id' => 4, 'title' => 'Illegal explosives DIY', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 1], + ]; $result = $this->Document->find() ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) @@ -309,26 +310,26 @@ public function testFilteringBelongsToDifferentConditions() ->toArray(); $this->assertEquals($expected, $result); - $testOptions = array - ( - 'id' => array('type' => 'text', 'condition' => '>='), - 'created' => array('type' => 'text', 'condition' => '<=') - ); + $testOptions = + [ + 'id' => ['type' => 'text', 'condition' => '>='], + 'created' => ['type' => 'text', 'condition' => '<='], + ]; $this->_reattachBehavior($testOptions); - $filterValues = array - ( - 'Documents' => array('id' => 3, 'created' => '2010-03-01') - ); + $filterValues = + [ + 'Documents' => ['id' => 3, 'created' => '2010-03-01'], + ]; $this->Document->setFilterValues($filterValues); - $expected = array - ( - array('id' => 4, 'title' => 'Illegal explosives DIY', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 1), - array('id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0), - array('id' => 6, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0), - array('id' => 7, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0), - ); + $expected = + [ + ['id' => 4, 'title' => 'Illegal explosives DIY', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 1], + ['id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0], + ['id' => 6, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0], + ['id' => 7, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0], + ]; $result = $this->Document->find() ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) @@ -346,43 +347,43 @@ public function testFilteringBelongsToDifferentConditions() */ public function testFilteringBelongsToAndHasMany() { - $testOptions = array - ( - 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), - 'DocumentCategories.id' => array('type' => 'select'), - 'Documents.is_private' => array('type' => 'checkbox', 'label' => 'Private?'), - 'Items.code' => array('type' => 'text'), - ); + $testOptions = + [ + 'title' => ['type' => 'text', 'condition' => 'like', 'required' => true], + 'DocumentCategories.id' => ['type' => 'select'], + 'Documents.is_private' => ['type' => 'checkbox', 'label' => 'Private?'], + 'Items.code' => ['type' => 'text'], + ]; $this->_reattachBehavior($testOptions); - $filterValues = array - ( - 'Documents' => array('title' => 'in', 'is_private' => 0), - 'DocumentCategories' => array('id' => 1), - 'Items' => array('code' => '04') - ); + $filterValues = + [ + 'Documents' => ['title' => 'in', 'is_private' => 0], + 'DocumentCategories' => ['id' => 1], + 'Items' => ['code' => '04'], + ]; $this->Document->setFilterValues($filterValues); - $expected = array - ( - array - ( + $expected = + [ + + [ 'id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, - 'document_category' => 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-------'), - 'items' => 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') - ) - ) - ); + 'document_category' => ['id' => 1, 'title' => 'Testing Doc', 'description' => 'It\'s a bleeding test doc!'], + 'metadata' => ['id' => 2, 'document_id' => 2, 'weight' => 0, 'size' => 45, 'permissions' => 'rw-------'], + 'items' => + [ + ['id' => 4, 'document_id' => 2, 'code' => 'The item #01'], + ['id' => 5, 'document_id' => 2, 'code' => 'The item #02'], + ['id' => 6, 'document_id' => 2, 'code' => 'The item #03'], + ['id' => 7, 'document_id' => 2, 'code' => 'The item #04'], + ], + ], + ]; $result = $this->Document->find() ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) @@ -401,19 +402,19 @@ public function testFilteringBelongsToAndHasMany() ->toArray(); $this->assertEquals($expected, $result); - $expected = array - ( - array - ( + $expected = + [ + + [ 'id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, - 'document_category' => 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-------'), - ) - ); + 'document_category' => ['id' => 1, 'title' => 'Testing Doc', 'description' => 'It\'s a bleeding test doc!'], + 'metadata' => ['id' => 2, 'document_id' => 2, 'weight' => 0, 'size' => 45, 'permissions' => 'rw-------'], + ], + ]; $result = $this->Document->find() ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) @@ -446,13 +447,13 @@ public function testFilteringBelongsToAndHasMany() ->toArray(); $this->assertEquals($expected, $result); - $expected = array - ( - array - ( + $expected = + [ + + [ 'id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, - ) - ); + ], + ]; $result = $this->Document->find() ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) @@ -469,26 +470,26 @@ public function testFilteringBelongsToAndHasMany() */ public function testCustomJoinConditions() { - $testOptions = array - ( - 'Metadata.weight' => array('type' => 'text', 'condition' => '>'), - ); + $testOptions = + [ + 'Metadata.weight' => ['type' => 'text', 'condition' => '>'], + ]; $this->_reattachBehavior($testOptions); - $filterValues = array - ( - 'Metadata' => array('weight' => 3), - ); + $filterValues = + [ + 'Metadata' => ['weight' => 3], + ]; $this->Document->setFilterValues($filterValues); - $expected = array - ( - array - ( + $expected = + [ + + [ 'id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0, - 'metadata' => array('id' => 5, 'document_id' => 5, 'weight' => 4, 'size' => 790, 'permissions' => 'rw-rw-r--'), - ) - ); + 'metadata' => ['id' => 5, 'document_id' => 5, 'weight' => 4, 'size' => 790, 'permissions' => 'rw-rw-r--'], + ], + ]; $Metadata = $this->Document->associations()->get('Metadata'); $this->assertInstanceOf(Association::class, $Metadata); $oldConditions = $Metadata->getConditions(); @@ -527,43 +528,43 @@ public function testCustomJoinConditions() */ public function testFilteringBelongsToAndHasManyWithContainable() { - $testOptions = array - ( - 'Documents.title' => array('type' => 'text', 'condition' => 'like', 'required' => true), - 'DocumentCategories.id' => array('type' => 'select'), - 'Documents.is_private' => array('type' => 'checkbox', 'label' => 'Private?'), - 'Items.code' => array('type' => 'text'), - ); + $testOptions = + [ + 'Documents.title' => ['type' => 'text', 'condition' => 'like', 'required' => true], + 'DocumentCategories.id' => ['type' => 'select'], + 'Documents.is_private' => ['type' => 'checkbox', 'label' => 'Private?'], + 'Items.code' => ['type' => 'text'], + ]; $this->_reattachBehavior($testOptions); - $filterValues = array - ( - 'Documents' => array('title' => 'in', 'is_private' => 0), - 'DocumentCategories' => array('id' => 1), - 'Items' => array('code' => '04') - ); + $filterValues = + [ + 'Documents' => ['title' => 'in', 'is_private' => 0], + 'DocumentCategories' => ['id' => 1], + 'Items' => ['code' => '04'], + ]; $this->Document->setFilterValues($filterValues); - $expected = array - ( - array - ( + $expected = + [ + + [ 'id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, - 'document_category' => array('id' => 1, 'title' => 'Testing Doc', 'description' => 'It\'s a bleeding test doc!'), - 'items' => 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') - ) - ) - ); + 'document_category' => ['id' => 1, 'title' => 'Testing Doc', 'description' => 'It\'s a bleeding test doc!'], + 'items' => + [ + ['id' => 4, 'document_id' => 2, 'code' => 'The item #01'], + ['id' => 5, 'document_id' => 2, 'code' => 'The item #02'], + ['id' => 6, 'document_id' => 2, 'code' => 'The item #03'], + ['id' => 7, 'document_id' => 2, 'code' => 'The item #04'], + ], + ], + ]; $result = $this->Document->find() ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) @@ -579,18 +580,18 @@ public function testFilteringBelongsToAndHasManyWithContainable() ->toArray(); $this->assertEquals($expected, $result); - $expected = array - ( - array - ( + $expected = + [ + + [ 'id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, - 'document_category' => array('id' => 1, 'title' => 'Testing Doc', 'description' => 'It\'s a bleeding test doc!'), - ) - ); + 'document_category' => ['id' => 1, 'title' => 'Testing Doc', 'description' => 'It\'s a bleeding test doc!'], + ], + ]; $result = $this->Document->find() ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) @@ -603,17 +604,17 @@ public function testFilteringBelongsToAndHasManyWithContainable() ->toArray(); $this->assertEquals($expected, $result); - $expected = array - ( - array - ( + $expected = + [ + + [ 'id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, - ) - ); + ], + ]; $result = $this->Document->find() ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) @@ -629,28 +630,28 @@ public function testFilteringBelongsToAndHasManyWithContainable() */ public function testHasOneAndHasManyWithTextSearch() { - $testOptions = array - ( - 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), - 'Items.code' => array('type' => 'text'), - 'Metadata.size' => array('type' => 'text', 'condition' => '='), - ); - - $filterValues = array - ( - 'Documents' => array('title' => 'in'), - 'Items' => array('code' => '04'), - 'Metadata' => array('size' => 45), - ); - - $expected = array - ( - array - ( + $testOptions = + [ + 'title' => ['type' => 'text', 'condition' => 'like', 'required' => true], + 'Items.code' => ['type' => 'text'], + 'Metadata.size' => ['type' => 'text', 'condition' => '='], + ]; + + $filterValues = + [ + 'Documents' => ['title' => 'in'], + 'Items' => ['code' => '04'], + 'Metadata' => ['size' => 45], + ]; + + $expected = + [ + + [ 'id' => 2, 'title' => 'Imaginary Spec', - ) - ); + ], + ]; $this->_reattachBehavior($testOptions); $this->Document->setFilterValues($filterValues); @@ -669,39 +670,39 @@ public function testHasOneAndHasManyWithTextSearch() */ public function testHasOneWithContainable() { - $testOptions = array - ( - 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), - 'Items.code' => array('type' => 'text'), - 'Metadata.size' => array('type' => 'text', 'condition' => '='), - ); - - $filterValues = array - ( - 'Documents' => array('title' => 'in'), - 'Items' => array('code' => '04'), - 'Metadata' => array('size' => 45), - ); - - $expected = array - ( - array - ( + $testOptions = + [ + 'title' => ['type' => 'text', 'condition' => 'like', 'required' => true], + 'Items.code' => ['type' => 'text'], + 'Metadata.size' => ['type' => 'text', 'condition' => '='], + ]; + + $filterValues = + [ + 'Documents' => ['title' => 'in'], + 'Items' => ['code' => '04'], + 'Metadata' => ['size' => 45], + ]; + + $expected = + [ + + [ 'id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, - 'metadata' => array('id' => 2, 'document_id' => 2, 'weight' => 0, 'size' => 45, 'permissions' => 'rw-------'), - 'items' => 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') - ) - ) - ); + 'metadata' => ['id' => 2, 'document_id' => 2, 'weight' => 0, 'size' => 45, 'permissions' => 'rw-------'], + 'items' => + [ + ['id' => 4, 'document_id' => 2, 'code' => 'The item #01'], + ['id' => 5, 'document_id' => 2, 'code' => 'The item #02'], + ['id' => 6, 'document_id' => 2, 'code' => 'The item #03'], + ['id' => 7, 'document_id' => 2, 'code' => 'The item #04'], + ], + ], + ]; // containable first, filtered second $this->_reattachBehavior($testOptions); @@ -746,49 +747,49 @@ public function testHasOneWithContainable() */ public function testJoinAlreadyPresent() { - $testOptions = array - ( - 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), - 'Items.code' => array('type' => 'text'), - 'Metadata.size' => array('type' => 'text', 'condition' => '='), - ); - - $filterValues = array - ( - 'Documents' => array('title' => 'in'), - 'Items' => array('code' => '04'), - 'Metadata' => array('size' => 45), - ); - - $expected = array - ( - array - ( + $testOptions = + [ + 'title' => ['type' => 'text', 'condition' => 'like', 'required' => true], + 'Items.code' => ['type' => 'text'], + 'Metadata.size' => ['type' => 'text', 'condition' => '='], + ]; + + $filterValues = + [ + 'Documents' => ['title' => 'in'], + 'Items' => ['code' => '04'], + 'Metadata' => ['size' => 45], + ]; + + $expected = + [ + + [ 'id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, - 'document_category' => 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-------'), - 'items' => 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 - ( + 'document_category' => ['id' => 1, 'title' => 'Testing Doc', 'description' => 'It\'s a bleeding test doc!'], + 'metadata' => ['id' => 2, 'document_id' => 2, 'weight' => 0, 'size' => 45, 'permissions' => 'rw-------'], + 'items' => + [ + ['id' => 4, 'document_id' => 2, 'code' => 'The item #01'], + ['id' => 5, 'document_id' => 2, 'code' => 'The item #02'], + ['id' => 6, 'document_id' => 2, 'code' => 'The item #03'], + ['id' => 7, 'document_id' => 2, 'code' => 'The item #04'], + ], + ], + ]; + + $customJoin = []; + $customJoin[] = + [ 'table' => 'items', 'alias' => 'FilterItems', 'type' => 'INNER', 'conditions' => 'Documents.id = FilterItems.document_id', - ); + ]; $this->_reattachBehavior($testOptions); $this->Document->setFilterValues($filterValues); @@ -818,26 +819,25 @@ public function testJoinAlreadyPresent() */ public function testNofilterFindParam() { - $testOptions = array - ( - 'Documents.title' => array('type' => 'text', 'condition' => 'like'), - 'DocumentCategories.id' => array('type' => 'select'), - 'Documents.is_private' => array('type' => 'checkbox', 'label' => 'Private?', 'default' => 0) - ); + $testOptions = + [ + 'Documents.title' => ['type' => 'text', 'condition' => 'like'], + 'DocumentCategories.id' => ['type' => 'select'], + 'Documents.is_private' => ['type' => 'checkbox', 'label' => 'Private?', 'default' => 0], + ]; $this->_reattachBehavior($testOptions); - - $filterValues = array - ( - 'DocumentCategories' => array('id' => 2), - 'Documents' => array('title' => '') - ); + $filterValues = + [ + 'DocumentCategories' => ['id' => 2], + 'Documents' => ['title' => ''], + ]; $this->Document->setFilterValues($filterValues); - $expected = array - ( - array('id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0) - ); + $expected = + [ + ['id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0], + ]; $result = $this->Document->find('all', ['nofilter' => true]) ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) @@ -864,20 +864,20 @@ public function testExitWhenNoSettings() $Filtered = $this->Document->DocumentCategories->behaviors()->get('Filtered'); $this->assertFalse(isset($Filtered->settings[$this->Document->DocumentCategories->getAlias()])); - $filterValues = array - ( - 'DocumentCategories' => array('id' => 2) - ); + $filterValues = + [ + 'DocumentCategories' => ['id' => 2], + ]; $this->Document->DocumentCategories->setFilterValues($filterValues); - $expected = 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!'), - ); + $expected = + [ + ['id' => 1, 'title' => 'Testing Doc', 'description' => 'It\'s a bleeding test doc!'], + ['id' => 2, 'title' => 'Imaginary Spec', 'description' => 'This doc does not exist'], + ['id' => 3, 'title' => 'Nonexistant data', 'description' => 'This doc is probably empty'], + ['id' => 4, 'title' => 'Illegal explosives DIY', 'description' => 'Viva la revolucion!'], + ['id' => 5, 'title' => 'Father Ted', 'description' => 'Feck! Drink! Arse! Girls!'], + ]; $result = $this->Document->DocumentCategories->find('all', ['nofilter' => 'true']) ->select(['id', 'title', 'description']) @@ -898,31 +898,30 @@ public function testBeforeDataFilterCallbackCancel() $Document = $this->getTableLocator()->get('Document2', ['className' => Documents2Table::class]); $this->assertInstanceOf(Documents2Table::class, $Document); $this->Document = $Document; - $testOptions = array - ( - 'Documents.title' => array('type' => 'text', 'condition' => 'like'), - 'DocumentCategories.id' => array('type' => 'select'), - 'Documents.is_private' => array('type' => 'checkbox', 'label' => 'Private?') - ); + $testOptions = + [ + 'Documents.title' => ['type' => 'text', 'condition' => 'like'], + 'DocumentCategories.id' => ['type' => 'select'], + 'Documents.is_private' => ['type' => 'checkbox', 'label' => 'Private?'], + ]; $this->_reattachBehavior($testOptions); - - $filterValues = array - ( - 'DocumentCategories' => array('id' => 2) - ); + $filterValues = + [ + 'DocumentCategories' => ['id' => 2], + ]; $this->Document->setFilterValues($filterValues); - $expected = array - ( - array('id' => 1, 'title' => 'Testing Doc', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0,), - array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0), - array('id' => 3, 'title' => 'Nonexistant data', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0), - array('id' => 4, 'title' => 'Illegal explosives DIY', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 1), - array('id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0), - array('id' => 6, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0), - array('id' => 7, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0), - ); + $expected = + [ + ['id' => 1, 'title' => 'Testing Doc', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0,], + ['id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0], + ['id' => 3, 'title' => 'Nonexistant data', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0], + ['id' => 4, 'title' => 'Illegal explosives DIY', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 1], + ['id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0], + ['id' => 6, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0], + ['id' => 7, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0], + ]; $result = $this->Document->find() ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) @@ -944,31 +943,30 @@ public function testAfterDataFilterCallbackQueryChange() $this->Document = $Document; $this->Document->itemToUnset = 'FilterDocumentCategories.id'; - $testOptions = array - ( - 'Documents.title' => array('type' => 'text', 'condition' => 'like'), - 'DocumentCategories.id' => array('type' => 'select'), - 'Documents.is_private' => array('type' => 'checkbox', 'label' => 'Private?') - ); + $testOptions = + [ + 'Documents.title' => ['type' => 'text', 'condition' => 'like'], + 'DocumentCategories.id' => ['type' => 'select'], + 'Documents.is_private' => ['type' => 'checkbox', 'label' => 'Private?'], + ]; $this->_reattachBehavior($testOptions); - - $filterValues = array - ( - 'DocumentCategories' => array('id' => 2) - ); + $filterValues = + [ + 'DocumentCategories' => ['id' => 2], + ]; $this->Document->setFilterValues($filterValues); - $expected = array - ( - array('id' => 1, 'title' => 'Testing Doc', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0), - array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0), - array('id' => 3, 'title' => 'Nonexistant data', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0), - array('id' => 4, 'title' => 'Illegal explosives DIY', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 1), - array('id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0), - array('id' => 6, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0), - array('id' => 7, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0), - ); + $expected = + [ + ['id' => 1, 'title' => 'Testing Doc', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0], + ['id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0], + ['id' => 3, 'title' => 'Nonexistant data', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0], + ['id' => 4, 'title' => 'Illegal explosives DIY', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 1], + ['id' => 5, 'title' => 'Father Ted', 'document_category_id' => 2, 'owner_id' => 2, 'is_private' => 0], + ['id' => 6, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0], + ['id' => 7, 'title' => 'Duplicate title', 'document_category_id' => 5, 'owner_id' => 3, 'is_private' => 0], + ]; $result = $this->Document->find('all') ->select(['id', 'title', 'document_category_id', 'owner_id', 'is_private']) From 62837b09e43648b3fbc9c980320a0cc4675f5282 Mon Sep 17 00:00:00 2001 From: bancer Date: Mon, 28 Aug 2023 16:24:09 +0200 Subject: [PATCH 29/37] Fix code style errors in FilteredBehavior --- src/Model/Behavior/FilteredBehavior.php | 272 +++++++++++------------- 1 file changed, 124 insertions(+), 148 deletions(-) diff --git a/src/Model/Behavior/FilteredBehavior.php b/src/Model/Behavior/FilteredBehavior.php index b525a74..1ee5105 100644 --- a/src/Model/Behavior/FilteredBehavior.php +++ b/src/Model/Behavior/FilteredBehavior.php @@ -1,4 +1,5 @@ LGPL GPL -*/ + */ class FilteredBehavior extends Behavior { /** * Keeps current values after filter form post. * - * @var mixed[] + * @var array */ - protected $_filterValues = array(); + protected $_filterValues = []; /** * 2.x compartible settings (supports having dots in the keys, f.ex. 'Model.id'). * - * @var mixed[] + * @var array */ public $settings = []; /** * {@inheritDoc} * - * @param mixed[] $settings The configuration settings provided to this behavior. + * @param array $settings The configuration settings provided to this behavior. * @return void */ public function initialize(array $settings) { - foreach ($settings as $key => $value) - { - if (!is_array($value)) - { + foreach ($settings as $key => $value) { + if (!is_array($value)) { $key = $value; - $value = array(); + $value = []; } - $this->settings[$this->getTable()->getAlias()][$key] = array_merge - ( - array - ( + $this->settings[$this->getTable()->getAlias()][$key] = array_merge( + [ 'type' => 'text', 'condition' => 'like', 'required' => false, - 'selectOptions' => array() - ), - $value - ); + 'selectOptions' => [], + ], + $value + ); } - $this->_filterValues[$this->getTable()->getAlias()] = array(); + $this->_filterValues[$this->getTable()->getAlias()] = []; } /** @@ -83,38 +80,32 @@ public function initialize(array $settings) */ public function beforeFind(Event $event, Query $Query, ArrayObject $options) { - if (isset($Query->getOptions()['nofilter']) && $Query->getOptions()['nofilter'] === true) - { + if (isset($Query->getOptions()['nofilter']) && $Query->getOptions()['nofilter'] === true) { return; } $Table = $this->getTable(); $alias = $Table->getAlias(); - if (method_exists($Table, 'beforeDataFilter')) - { + if (method_exists($Table, 'beforeDataFilter')) { $callbackOptions['values'] = $this->_filterValues[$alias]; $callbackOptions['settings'] = $this->settings[$alias]; - if (!$Table->beforeDataFilter($Query, $callbackOptions)) - { + if (!$Table->beforeDataFilter($Query, $callbackOptions)) { return; } } - if (!isset($this->settings[$alias])) - { + if (!isset($this->settings[$alias])) { return; } $settings = $this->settings[$alias]; $values = $this->_filterValues[$alias]; - foreach ($settings as $field => $options) - { + foreach ($settings as $field => $options) { $this->addFieldToFilter($Table, $Query, $values, $field, $options); } - if (method_exists($Table, 'afterDataFilter')) - { + if (method_exists($Table, 'afterDataFilter')) { $callbackOptions['values'] = $this->_filterValues[$alias]; $callbackOptions['settings'] = $this->settings[$alias]; @@ -127,9 +118,9 @@ public function beforeFind(Event $event, Query $Query, ArrayObject $options) * * @param \Cake\ORM\Table $Table Model table object. * @param \Cake\ORM\Query $Query Query object. - * @param mixed[] $values Filter values. + * @param array $values Filter values. * @param string $field Field name. - * @param mixed[] $fieldOptions Field options. + * @param array $fieldOptions Field options. * @return void */ protected function addFieldToFilter(Table $Table, Query $Query, $values, $field, $fieldOptions) @@ -137,25 +128,28 @@ protected function addFieldToFilter(Table $Table, Query $Query, $values, $field, $configurationModelName = $Table->getAlias(); $configurationFieldName = $field; - if (strpos($field, '.') !== false) - { - list($configurationModelName, $configurationFieldName) = explode('.', $field); + if (strpos($field, '.') !== false) { + [$configurationModelName, $configurationFieldName] = explode('.', $field); } - if (!isset($values[$configurationModelName][$configurationFieldName]) && isset($fieldOptions['default'])) - { + if (!isset($values[$configurationModelName][$configurationFieldName]) && isset($fieldOptions['default'])) { $values[$configurationModelName][$configurationFieldName] = $fieldOptions['default']; } - if ($fieldOptions['required'] && !isset($values[$configurationModelName][$configurationFieldName])) - { + if ($fieldOptions['required'] && !isset($values[$configurationModelName][$configurationFieldName])) { // TODO: implement a bit of a user friendly handling of this scenario.. trigger_error(sprintf('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)) - { + if ( + !isset($values[$configurationModelName][$configurationFieldName]) || + ( + empty($values[$configurationModelName][$configurationFieldName]) && + $values[$configurationModelName][$configurationFieldName] != 0 + ) + ) { // no value to filter with, just skip this field return; } @@ -167,56 +161,46 @@ protected function addFieldToFilter(Table $Table, Query $Query, $values, $field, $linkModelName = null; $relationType = null; - if ($configurationModelName != $Table->getAlias()) - { + if ($configurationModelName != $Table->getAlias()) { if ($Table->hasAssociation($configurationModelName)) { $relationType = $Table->getAssociation($configurationModelName)->type(); if ($relationType == Association::MANY_TO_MANY) { $linkModelName = $Table->{$configurationModelName}->junction()->getAlias(); } - $filterModelName = 'Filter'.$configurationModelName; + $filterModelName = 'Filter' . $configurationModelName; } } - if (isset($fieldOptions['filterField'])) - { - if (strpos($fieldOptions['filterField'], '.') !== false) - { - list($filterModelName, $filterFieldName) = explode('.', $fieldOptions['filterField']); + if (isset($fieldOptions['filterField'])) { + if (strpos($fieldOptions['filterField'], '.') !== false) { + [$filterModelName, $filterFieldName] = explode('.', $fieldOptions['filterField']); - if ($filterModelName != $Table->getAlias()) - { - $filterModelName = 'Filter'.$filterModelName; + if ($filterModelName != $Table->getAlias()) { + $filterModelName = 'Filter' . $filterModelName; } - } - else - { + } else { $filterModelName = $Table->getAlias(); $filterFieldName = $fieldOptions['filterField']; } } $realFilterField = sprintf('%s.%s', $filterModelName, $filterFieldName); - if ($Table->hasAssociation($configurationModelName)) - { + if ($Table->hasAssociation($configurationModelName)) { $relatedModel = $Table->{$configurationModelName}->getTarget(); - if (!$this->__isAlreadyJoined($Query, $relatedModel)) - { + if (!$this->__isAlreadyJoined($Query, $relatedModel)) { $joinStatements = $this->buildFilterJoin($Table, $relatedModel, $linkModelName); - foreach ($joinStatements as $joinStatement) - { + foreach ($joinStatements as $joinStatement) { $Query->join($joinStatement); } } } - $this->buildFilterConditions - ( - $Query, - $realFilterField, - $fieldOptions, - $values[$configurationModelName][$configurationFieldName] - ); + $this->buildFilterConditions( + $Query, + $realFilterField, + $fieldOptions, + $values[$configurationModelName][$configurationFieldName] + ); } /** @@ -224,13 +208,14 @@ protected function addFieldToFilter(Table $Table, Query $Query, $values, $field, * * @param \Cake\ORM\Query $Query Query object. * @param \Cake\ORM\Table $Table Related model. - * @return boolean + * @return bool */ private function __isAlreadyJoined(Query $Query, Table $Table) { $relatedModelAlias = 'Filter' . $Table->getAlias(); $containedAliases = array_keys($Query->getContain()); $joinAliases = $this->__extractJoinAliases($Query); + return in_array($relatedModelAlias, $containedAliases) || in_array($relatedModelAlias, $joinAliases); } @@ -238,7 +223,7 @@ private function __isAlreadyJoined(Query $Query, Table $Table) * Extract the JOIN clause aliases from the given query object. * * @param \Cake\ORM\Query $Query Query object. - * @return string[] + * @return array */ private function __extractJoinAliases(Query $Query) { @@ -249,6 +234,7 @@ private function __extractJoinAliases(Query $Query) $aliases[] = $join['alias']; } } + return $aliases; } @@ -258,11 +244,11 @@ private function __extractJoinAliases(Query $Query) * @param \Cake\ORM\Table $Table Model table object. * @param \Cake\ORM\Table $RelatedTable Related model table object. * @param string $linkModelName Linked model name (alias) in MANY_TO_MANY association. - * @return mixed[] Cake join array. + * @return array Cake join array. */ protected function buildFilterJoin(Table $Table, Table $RelatedTable, $linkModelName) { - $conditions = array(); + $conditions = []; $alias = $Table->getAlias(); $primaryKey = $Table->getPrimaryKey(); $relatedTableAlias = $RelatedTable->getAlias(); @@ -275,11 +261,10 @@ protected function buildFilterJoin(Table $Table, Table $RelatedTable, $linkModel if (!$Table->hasAssociation($relatedTableAlias)) { return []; } - $relatedModelAlias = 'Filter'.$relatedTableAlias; + $relatedModelAlias = 'Filter' . $relatedTableAlias; $association = $Table->getAssociation($relatedTableAlias); $linkModelAlias = null; - if (!empty($linkModelName) && ($association instanceof BelongsToMany)) - { + if (!empty($linkModelName) && ($association instanceof BelongsToMany)) { $linkModelAlias = $association->junction()->getAlias(); } $relationType = $association->type(); @@ -287,61 +272,59 @@ protected function buildFilterJoin(Table $Table, Table $RelatedTable, $linkModel $associationConditions = $association->getConditions(); $associationPrimaryKey = $RelatedTable->getPrimaryKey(); $linkConditions = []; - if (!empty($foreignKey) && is_string($foreignKey)) - { - if ($relationType == Association::MANY_TO_ONE && is_string($associationPrimaryKey)) - { - $conditions[] = sprintf - ( - '%s.%s = %s.%s', - $alias, $foreignKey, - $relatedModelAlias, $associationPrimaryKey - ); - } - else if ( + if (!empty($foreignKey) && is_string($foreignKey)) { + if ($relationType == Association::MANY_TO_ONE && is_string($associationPrimaryKey)) { + $conditions[] = sprintf( + '%s.%s = %s.%s', + $alias, + $foreignKey, + $relatedModelAlias, + $associationPrimaryKey + ); + } elseif ( in_array($relationType, [Association::ONE_TO_MANY, Association::ONE_TO_ONE]) && is_string($primaryKey) ) { - $conditions[] = sprintf - ( - '%s.%s = %s.%s', - $alias, $primaryKey, - $relatedModelAlias, $foreignKey - ); - } - else if ( + $conditions[] = sprintf( + '%s.%s = %s.%s', + $alias, + $primaryKey, + $relatedModelAlias, + $foreignKey + ); + } elseif ( $relationType == Association::MANY_TO_MANY && is_string($primaryKey) && is_string($associationPrimaryKey) ) { $associationForeignKey = $RelatedTable->getAssociation($alias)->getForeignKey(); if (is_string($associationForeignKey)) { - $conditions[] = sprintf - ( + $conditions[] = sprintf( '%s.%s = %s.%s', - $linkModelAlias, $associationForeignKey, - $relatedModelAlias, $associationPrimaryKey + $linkModelAlias, + $associationForeignKey, + $relatedModelAlias, + $associationPrimaryKey ); } - $linkConditions[] = sprintf - ( + $linkConditions[] = sprintf( '%s.%s = %s.%s', - $alias, $primaryKey, - $linkModelAlias, $foreignKey + $alias, + $primaryKey, + $linkModelAlias, + $foreignKey ); } } // merge any custom conditions from the relation, but change // the alias to our $relatedModelAlias - if (!empty($associationConditions)) - { + if (!empty($associationConditions)) { $customConditions = $associationConditions; - if (!is_array($associationConditions)) - { - $customConditions = array($customConditions); + if (!is_array($associationConditions)) { + $customConditions = [$customConditions]; } $formatAlias = sprintf('#(? $RelatedTable->getTable(), 'alias' => $relatedModelAlias, 'type' => 'LEFT', 'conditions' => $conditions, - ) - ); + ], + ]; - if (!empty($linkModelName) && ($association instanceof BelongsToMany)) - { - $return = array - ( - array - ( + if (!empty($linkModelName) && ($association instanceof BelongsToMany)) { + $return = + [ + + [ 'table' => $association->junction()->getTable(), 'alias' => $linkModelAlias, 'type' => 'LEFT', 'conditions' => $linkConditions, - ), - array - ( + ], + + [ 'table' => $RelatedTable->getTable(), 'alias' => $relatedModelAlias, 'type' => 'LEFT', 'conditions' => $conditions, - ) - ); + ], + ]; } + return $return; } @@ -391,14 +374,14 @@ protected function buildFilterJoin(Table $Table, Table $RelatedTable, $linkModel * * @param \Cake\ORM\Query $Query Cake query array. * @param string $field Filter field. - * @param mixed[] $options Configuration options for this field. + * @param array $options Configuration options for this field. * @param mixed $value Field value. * @return void */ protected function buildFilterConditions(Query $Query, $field, $options, $value) { - $conditionFieldFormats = array - ( + $conditionFieldFormats = + [ 'like' => '%s like', 'ilike' => '%s ilike', 'contains' => '%s like', @@ -407,9 +390,9 @@ protected function buildFilterConditions(Query $Query, $field, $options, $value) 'equal' => '%s', 'equals' => '%s', '=' => '%s', - ); - $conditionValueFormats = array - ( + ]; + $conditionValueFormats = + [ 'like' => '%%%s%%', 'ilike' => '%%%s%%', 'contains' => '%%%s%%', @@ -418,13 +401,11 @@ protected function buildFilterConditions(Query $Query, $field, $options, $value) 'equal' => '%s', 'equals' => '%s', '=' => '%s', - ); + ]; - switch ($options['type']) - { + switch ($options['type']) { case 'select': - if (is_string($value) && strlen(trim(strval($value))) == 0) - { + if (is_string($value) && strlen(trim(strval($value))) == 0) { break; } if (is_array($value)) { @@ -441,15 +422,13 @@ protected function buildFilterConditions(Query $Query, $field, $options, $value) } break; default: - if (strlen(trim(strval($value))) == 0) - { + if (strlen(trim(strval($value))) == 0) { break; } $condition = $options['condition']; - switch ($condition) - { + switch ($condition) { case 'like': case 'ilike': case 'contains': @@ -463,9 +442,7 @@ protected function buildFilterConditions(Query $Query, $field, $options, $value) $Query->andWhere([$formattedField => $formattedValue]); break; default: - { - $Query->andWhere([$field.' '.$condition => $value]); - } + $Query->andWhere([$field . ' ' . $condition => $value]); break; } @@ -476,10 +453,10 @@ protected function buildFilterConditions(Query $Query, $field, $options, $value) /** * Sets filter values. * - * @param mixed[] $values Filter values. + * @param array $values Filter values. * @return void */ - public function setFilterValues($values = array()) + public function setFilterValues($values = []) { $alias = $this->getTable()->getAlias(); $this->_filterValues[$alias] = array_merge($this->_filterValues[$alias], (array)$values); @@ -488,11 +465,10 @@ public function setFilterValues($values = array()) /** * Gets filter values. * - * @return mixed[] + * @return array */ public function getFilterValues() { return $this->_filterValues; } - } From 17f57b1f392704307c69cb46436981db836edd6b Mon Sep 17 00:00:00 2001 From: bancer Date: Mon, 28 Aug 2023 16:32:50 +0200 Subject: [PATCH 30/37] Fix code style errors in FilterHelper --- src/Model/Behavior/FilteredBehavior.php | 6 +- src/View/Helper/FilterHelper.php | 95 +++++++++---------- .../Model/Behavior/FilteredBehaviorTest.php | 2 +- tests/bootstrap.php | 3 - 4 files changed, 46 insertions(+), 60 deletions(-) diff --git a/src/Model/Behavior/FilteredBehavior.php b/src/Model/Behavior/FilteredBehavior.php index 1ee5105..d3ce53f 100644 --- a/src/Model/Behavior/FilteredBehavior.php +++ b/src/Model/Behavior/FilteredBehavior.php @@ -1,6 +1,4 @@ getAlias()) { $filterModelName = 'Filter' . $filterModelName; diff --git a/src/View/Helper/FilterHelper.php b/src/View/Helper/FilterHelper.php index 8246ab4..962f388 100644 --- a/src/View/Helper/FilterHelper.php +++ b/src/View/Helper/FilterHelper.php @@ -14,88 +14,80 @@ MPL LGPL GPL -*/ + */ class FilterHelper extends Helper { /** * @param string $modelName - * @param mixed[] $options + * @param array $options * @return string */ public function filterForm($modelName, $options) { $view =& $this->_View; - $output = $view->element - ( - 'filter_form_begin', - array - ( + $output = $view->element( + 'filter_form_begin', + [ '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_end', - array('plugin' => 'Filter'), - array('plugin' => 'Filter') - ); + 'options' => $options, + ], + ['plugin' => 'Filter'] + ); + + $output .= $view->element( + 'filter_form_fields', + ['plugin' => 'Filter'], + ['plugin' => 'Filter'] + ); + + $output .= $view->element( + 'filter_form_end', + ['plugin' => 'Filter'], + ['plugin' => 'Filter'] + ); return $output; } /** * @param string $modelName - * @param mixed[] $options + * @param array $options * @return string */ public function beginForm($modelName, $options) { $view =& $this->_View; - $output = $view->element - ( - 'filter_form_begin', - array - ( + $output = $view->element( + 'filter_form_begin', + [ 'plugin' => 'Filter', 'modelName' => $modelName, - 'options' => $options - ), - array('plugin' => 'Filter') - ); + 'options' => $options, + ], + ['plugin' => 'Filter'] + ); return $output; } /** - * @param string[] $fields + * @param array $fields * @return string */ - public function inputFields($fields = array()) + public function inputFields($fields = []) { $view =& $this->_View; - $output = $view->element - ( - 'filter_form_fields', - array - ( + $output = $view->element( + 'filter_form_fields', + [ 'plugin' => 'Filter', - 'includeFields' => $fields - ), - array('plugin' => 'Filter') - ); + 'includeFields' => $fields, + ], + ['plugin' => 'Filter'] + ); return $output; } @@ -106,12 +98,11 @@ public function inputFields($fields = array()) public function endForm() { $view = $this->_View; - $output = $view->element - ( - 'filter_form_end', - array(), - array('plugin' => 'Filter') - ); + $output = $view->element( + 'filter_form_end', + [], + ['plugin' => 'Filter'] + ); return $output; } diff --git a/tests/TestCase/Model/Behavior/FilteredBehaviorTest.php b/tests/TestCase/Model/Behavior/FilteredBehaviorTest.php index 4dc767b..147885e 100644 --- a/tests/TestCase/Model/Behavior/FilteredBehaviorTest.php +++ b/tests/TestCase/Model/Behavior/FilteredBehaviorTest.php @@ -914,7 +914,7 @@ public function testBeforeDataFilterCallbackCancel() $expected = [ - ['id' => 1, 'title' => 'Testing Doc', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0,], + ['id' => 1, 'title' => 'Testing Doc', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0], ['id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0], ['id' => 3, 'title' => 'Nonexistant data', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0], ['id' => 4, 'title' => 'Illegal explosives DIY', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 1], diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 7b6f124..3d134f6 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,7 +1,4 @@ Date: Mon, 28 Aug 2023 16:40:38 +0200 Subject: [PATCH 31/37] Fix code style errors in FilterComponent --- src/Controller/Component/FilterComponent.php | 272 ++++++++----------- src/View/Helper/FilterHelper.php | 10 +- 2 files changed, 111 insertions(+), 171 deletions(-) diff --git a/src/Controller/Component/FilterComponent.php b/src/Controller/Component/FilterComponent.php index d58b0fc..45e2f38 100644 --- a/src/Controller/Component/FilterComponent.php +++ b/src/Controller/Component/FilterComponent.php @@ -16,44 +16,44 @@ MPL LGPL GPL -*/ + */ /** - * @property RequestHandlerComponent $RequestHandler - * @property SessionComponent $Session + * @property \Filter\Controller\Component\RequestHandlerComponent $RequestHandler + * @property \Filter\Controller\Component\SessionComponent $Session */ class FilterComponent extends Component { /** - * @var string[] + * @var array */ - public $components = array('Session'); + public $components = ['Session']; /** - * @var mixed[] + * @var array */ - public $settings = array(); + public $settings = []; /** - * @var mixed[] + * @var array */ - public $nopersist = array(); + public $nopersist = []; /** - * @var mixed[] + * @var array */ - public $formData = array(); + public $formData = []; /** - * @var mixed[] + * @var array */ - protected $_request_settings = array(); + protected $_request_settings = []; /** * {@inheritDoc} * * @param \Cake\Controller\ComponentRegistry $registry A ComponentRegistry this component can use to lazy load its components - * @param mixed[] $config Array of configuration settings. + * @param array $config Array of configuration settings. */ public function __construct(ComponentRegistry $registry, array $config = []) { @@ -70,8 +70,7 @@ public function __construct(ComponentRegistry $registry, array $config = []) public function beforeFilter(Event $event) { $controller = $this->getController(); - if (!isset($controller->filters)) - { + if (!isset($controller->filters)) { return; } @@ -80,17 +79,14 @@ public function beforeFilter(Event $event) $this->settings[$controllerName] = $controller->filters; $action = $controller->getRequest()->getParam('action'); - if (!isset($this->settings[$controllerName][$action])) - { + if (!isset($this->settings[$controllerName][$action])) { return; } $settings = $this->settings[$controllerName][$action]; - foreach ($settings as $model => $filter) - { - if (!isset($controller->{$model})) - { + foreach ($settings as $model => $filter) { + if (!isset($controller->{$model})) { trigger_error(sprintf('Filter model not found: %s', $model)); continue; } @@ -110,57 +106,45 @@ public function startup(Event $event) $controller = $this->getController(); $controllerName = $controller->getName(); $action = $controller->getRequest()->getParam('action'); - if (!isset($this->settings[$controllerName][$action])) - { + if (!isset($this->settings[$controllerName][$action])) { return; } $settings = $this->settings[$controllerName][$action]; - if (!in_array('Filter.Filter', $controller->viewBuilder()->getHelpers())) - { + if (!in_array('Filter.Filter', $controller->viewBuilder()->getHelpers())) { $controller->viewBuilder()->setHelpers(['Filter.Filter']); } $sessionKey = sprintf('FilterPlugin.Filters.%s.%s', $controllerName, $action); $Session = $controller->getRequest()->getSession(); $filterFormId = $controller->request->getQuery('filterFormId'); - if ($controller->request->is('get') && !empty($filterFormId)) - { - /** @var mixed[] $requestData */ + if ($controller->request->is('get') && !empty($filterFormId)) { + /** @var array $requestData */ $requestData = $controller->request->getQuery('data', []); $this->formData = $requestData; - } - elseif (!$controller->request->is('post') || $controller->request->getData('Filter.filterFormId') === null) - { - $persistedData = array(); + } elseif (!$controller->request->is('post') || $controller->request->getData('Filter.filterFormId') === null) { + $persistedData = []; - if ($Session->check($sessionKey)) - { + if ($Session->check($sessionKey)) { $persistedData = $Session->read($sessionKey); } - if (empty($persistedData)) - { + if (empty($persistedData)) { return; } $this->formData = $persistedData; - } - else - { - /** @var mixed[] $requestData */ + } else { + /** @var array $requestData */ $requestData = $controller->request->getData(); $this->formData = $requestData; - if ($Session->started()) - { + if ($Session->started()) { $Session->write($sessionKey, $this->formData); } } - foreach ($settings as $model => $options) - { - if (!isset($controller->{$model})) - { + foreach ($settings as $model => $options) { + if (!isset($controller->{$model})) { trigger_error(__('Filter model not found: %s', $model)); continue; } @@ -180,41 +164,34 @@ public function beforeRender(Event $event) $controller = $this->getController(); $controllerName = $controller->getName(); $action = $controller->getRequest()->getParam('action'); - if (!isset($this->settings[$controllerName][$action])) - { + if (!isset($this->settings[$controllerName][$action])) { return; } $models = $this->settings[$controllerName][$action]; - $viewFilterParams = array(); + $viewFilterParams = []; - foreach ($models as $model => $fields) - { - if (!isset($controller->$model)) - { + 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)) - { + foreach ($fields as $field => $settings) { + if (!is_array($settings)) { $field = $settings; - $settings = array(); + $settings = []; } - if (!isset($settings['required'])) - { + if (!isset($settings['required'])) { $settings['required'] = false; } - if (!isset($settings['type'])) - { + if (!isset($settings['type'])) { $settings['type'] = 'text'; } - $options = array(); + $options = []; $fieldName = $field; $fieldModel = $model; @@ -222,45 +199,37 @@ public function beforeRender(Event $event) if (isset($settings['className'])) { $className = $settings['className']; } - if (strpos($field, '.') !== false) - { - list($fieldModel, $fieldName) = explode('.', $field); + if (strpos($field, '.') !== false) { + [$fieldModel, $fieldName] = explode('.', $field); } - if (!empty($this->formData)) - { - if (isset($this->formData[$fieldModel][$fieldName])) - { + if (!empty($this->formData)) { + if (isset($this->formData[$fieldModel][$fieldName])) { $options['value'] = $this->formData[$fieldModel][$fieldName]; - if ($options['value']) - { + if ($options['value']) { $options['class'] = 'filter-active'; } } } - if (isset($settings['inputOptions'])) - { - if (!is_array($settings['inputOptions'])) - { - $settings['inputOptions'] = array($settings['inputOptions']); + if (isset($settings['inputOptions'])) { + if (!is_array($settings['inputOptions'])) { + $settings['inputOptions'] = [$settings['inputOptions']]; } $options = array_merge($options, $settings['inputOptions']); } - if (isset($settings['label'])) - { + if (isset($settings['label'])) { $options['label'] = $settings['label']; } - switch ($settings['type']) - { + switch ($settings['type']) { case 'select': $options['type'] = 'select'; - $selectOptions = array(); + $selectOptions = []; $TableLocator = $this->getController()->getTableLocator(); if ($TableLocator->exists($fieldModel)) { $workingModel = $TableLocator->get($fieldModel); @@ -274,59 +243,49 @@ public function beforeRender(Event $event) } } - if (isset($settings['selectOptions'])) - { + if (isset($settings['selectOptions'])) { $selectOptions = $settings['selectOptions']; } - if (isset($settings['selector'])) - { - if (!method_exists($workingModel, $settings['selector'])) - { - trigger_error - ( - sprintf( - 'Selector method "%s" not found in model "%s" for field "%s"!', - $settings['selector'], - $fieldModel, - $fieldName - ) - ); + if (isset($settings['selector'])) { + if (!method_exists($workingModel, $settings['selector'])) { + trigger_error( + sprintf( + '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) - { + } else { + if ($fieldModel == $model) { $listOptions = array_merge( $selectOptions, [ 'nofilter' => true, 'keyField' => $fieldName, 'valueField' => $fieldName, - 'fields' => array($fieldName, $fieldName), + 'fields' => [$fieldName, $fieldName], ] ); - } - else - { - $listOptions = array_merge($selectOptions, array('nofilter' => true)); + } else { + $listOptions = array_merge($selectOptions, ['nofilter' => true]); } $options['options'] = $workingModel->find('list', $listOptions) ->toArray(); } - if (!$settings['required']) - { + if (!$settings['required']) { $options['empty'] = ''; } - if (isset($settings['multiple'])) - { + if (isset($settings['multiple'])) { $options['multiple'] = $settings['multiple']; } @@ -335,13 +294,10 @@ public function beforeRender(Event $event) case 'checkbox': $options['type'] = 'checkbox'; - if (isset($options['value'])) - { + if (isset($options['value'])) { $options['checked'] = !!$options['value']; unset($options['value']); - } - else if (isset($settings['default'])) - { + } elseif (isset($settings['default'])) { $options['checked'] = !!$settings['default']; } break; @@ -352,18 +308,19 @@ public function beforeRender(Event $event) } // if no value has been set, show the default one - if (!isset($options['value']) && + if ( + !isset($options['value']) && isset($settings['default']) && - $options['type'] != 'checkbox') - { + $options['type'] != 'checkbox' + ) { $options['value'] = $settings['default']; } - $viewFilterParams[] = array - ( + $viewFilterParams[] = + [ 'name' => sprintf('%s.%s', $fieldModel, $fieldName), - 'options' => $options - ); + 'options' => $options, + ]; } } @@ -372,14 +329,13 @@ public function beforeRender(Event $event) array_search($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') - { + foreach ($viewFilterParams as $viewFilterParam) { + if ( + !empty($viewFilterParam['options']['class']) && + $viewFilterParam['options']['class'] == 'filter-active' + ) { $titleValue = $viewFilterParam['options']['value']; - if ($viewFilterParam['options']['type'] == 'select') - { + if ($viewFilterParam['options']['type'] == 'select') { $titleValue = $viewFilterParam['options']['options'][$titleValue]; } $title .= ' - ' . $titleValue; @@ -391,7 +347,7 @@ public function beforeRender(Event $event) } /** - * @param mixed[] $settings + * @param array $settings * @return void */ private function __updatePersistence($settings) @@ -399,61 +355,45 @@ private function __updatePersistence($settings) $controller = $this->getController(); $controllerName = $controller->getName(); $Session = $controller->getRequest()->getSession(); - if ($Session->check('FilterPlugin.NoPersist')) - { + if ($Session->check('FilterPlugin.NoPersist')) { $this->nopersist = $Session->read('FilterPlugin.NoPersist'); } - if (isset($settings['nopersist'])) - { + if (isset($settings['nopersist'])) { $this->nopersist[$controllerName] = $settings['nopersist']; - if ($Session->started()) - { + if ($Session->started()) { $Session->write('FilterPlugin.NoPersist', $this->nopersist); } - } - else if (isset($this->nopersist[$controllerName])) - { + } elseif (isset($this->nopersist[$controllerName])) { unset($this->nopersist[$controllerName]); - if ($Session->started()) - { + if ($Session->started()) { $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($this->nopersist)) { + foreach ($this->nopersist as $nopersistController => $actions) { + if (is_string($actions)) { + $actions = [$actions]; + } elseif ($actions === true) { + $actions = []; } - if (empty($actions) && $controllerName != $nopersistController) - { - if ($Session->check(sprintf('FilterPlugin.Filters.%s', $nopersistController))) - { + if (empty($actions) && $controllerName != $nopersistController) { + if ($Session->check(sprintf('FilterPlugin.Filters.%s', $nopersistController))) { $Session->delete(sprintf('FilterPlugin.Filters.%s', $nopersistController)); continue; } } $action = $controller->getRequest()->getParam('action'); - foreach ($actions as $noPersistAction) - { - if ($controllerName == $nopersistController && $noPersistAction == $action) - { + foreach ($actions as $noPersistAction) { + if ($controllerName == $nopersistController && $noPersistAction == $action) { continue; } - - if ($Session->check(sprintf('FilterPlugin.Filters.%s.%s', $nopersistController, $noPersistAction))) - { - $Session->delete(sprintf('FilterPlugin.Filters.%s.%s', $nopersistController, $noPersistAction)); + $sessionKey = sprintf('FilterPlugin.Filters.%s.%s', $nopersistController, $noPersistAction); + if ($Session->check($sessionKey)) { + $Session->delete($sessionKey); } } } diff --git a/src/View/Helper/FilterHelper.php b/src/View/Helper/FilterHelper.php index 962f388..cab1dec 100644 --- a/src/View/Helper/FilterHelper.php +++ b/src/View/Helper/FilterHelper.php @@ -19,8 +19,8 @@ class FilterHelper extends Helper { /** - * @param string $modelName - * @param array $options + * @param string $modelName Model name. + * @param array $options Options. * @return string */ public function filterForm($modelName, $options) @@ -53,8 +53,8 @@ public function filterForm($modelName, $options) } /** - * @param string $modelName - * @param array $options + * @param string $modelName Model name. + * @param array $options Options. * @return string */ public function beginForm($modelName, $options) @@ -74,7 +74,7 @@ public function beginForm($modelName, $options) } /** - * @param array $fields + * @param array $fields Fileds to include. * @return string */ public function inputFields($fields = []) From 35876c7cd3a2f4ad5f779349c54e45d0123f76cb Mon Sep 17 00:00:00 2001 From: bancer Date: Mon, 28 Aug 2023 16:46:08 +0200 Subject: [PATCH 32/37] Fix code style errors --- src/Controller/Component/FilterComponent.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Controller/Component/FilterComponent.php b/src/Controller/Component/FilterComponent.php index 45e2f38..d09f43d 100644 --- a/src/Controller/Component/FilterComponent.php +++ b/src/Controller/Component/FilterComponent.php @@ -200,7 +200,7 @@ public function beforeRender(Event $event) $className = $settings['className']; } if (strpos($field, '.') !== false) { - [$fieldModel, $fieldName] = explode('.', $field); + list($fieldModel, $fieldName) = explode('.', $field); } if (!empty($this->formData)) { @@ -295,10 +295,10 @@ public function beforeRender(Event $event) $options['type'] = 'checkbox'; if (isset($options['value'])) { - $options['checked'] = !!$options['value']; + $options['checked'] = (bool)$options['value']; unset($options['value']); } elseif (isset($settings['default'])) { - $options['checked'] = !!$settings['default']; + $options['checked'] = (bool)$settings['default']; } break; @@ -347,7 +347,7 @@ public function beforeRender(Event $event) } /** - * @param array $settings + * @param array $settings Settings. * @return void */ private function __updatePersistence($settings) From 82b43a7e4166709982ebf9b91efef420683d488e Mon Sep 17 00:00:00 2001 From: bancer Date: Mon, 28 Aug 2023 16:53:48 +0200 Subject: [PATCH 33/37] Fix code style errors --- src/Model/Behavior/FilteredBehavior.php | 1 - tests/TestCase/Controller/Component/FilterComponentTest.php | 1 - tests/TestCase/MockObjects/DocumentCategoriesTable.php | 1 - tests/TestCase/MockObjects/DocumentTestsController.php | 2 -- tests/TestCase/MockObjects/Documents2Table.php | 1 - tests/TestCase/MockObjects/Documents3Table.php | 1 - tests/TestCase/MockObjects/DocumentsTable.php | 1 - tests/TestCase/MockObjects/ItemsTable.php | 1 - tests/TestCase/MockObjects/MetadataTable.php | 1 - tests/TestCase/Model/Behavior/FilteredBehaviorTest.php | 1 - 10 files changed, 11 deletions(-) diff --git a/src/Model/Behavior/FilteredBehavior.php b/src/Model/Behavior/FilteredBehavior.php index d3ce53f..ed3a639 100644 --- a/src/Model/Behavior/FilteredBehavior.php +++ b/src/Model/Behavior/FilteredBehavior.php @@ -135,7 +135,6 @@ protected function addFieldToFilter(Table $Table, Query $Query, $values, $field, } if ($fieldOptions['required'] && !isset($values[$configurationModelName][$configurationFieldName])) { - // TODO: implement a bit of a user friendly handling of this scenario.. trigger_error(sprintf('No value present for required field "%s" and default value not present', $field)); return; diff --git a/tests/TestCase/Controller/Component/FilterComponentTest.php b/tests/TestCase/Controller/Component/FilterComponentTest.php index b78a582..17a165b 100644 --- a/tests/TestCase/Controller/Component/FilterComponentTest.php +++ b/tests/TestCase/Controller/Component/FilterComponentTest.php @@ -1,5 +1,4 @@ Date: Mon, 28 Aug 2023 17:08:16 +0200 Subject: [PATCH 34/37] Add ctp files to code style checks --- phpstan.neon | 3 +++ 1 file changed, 3 insertions(+) diff --git a/phpstan.neon b/phpstan.neon index 218fba5..ac708a9 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -6,6 +6,9 @@ parameters: - phpstan-bootstrap.php scanDirectories: - ./ + fileExtensions: + - php + - ctp ignoreErrors: # False positive as __() function can accept more than 2 parameters. - '/Cannot call method enableHydration\(\) on array\|Cake\\ORM\\Query./' From 64e672879b07c3c1553be183200a0e378d9b9624 Mon Sep 17 00:00:00 2001 From: bancer Date: Mon, 28 Aug 2023 17:08:54 +0200 Subject: [PATCH 35/37] Fix code style errors --- .github/workflows/pull-request.yml | 2 +- src/View/Elements/filter_form_begin.ctp | 16 ++++++++++------ src/View/Elements/filter_form_end.ctp | 2 ++ src/View/Elements/filter_form_fields.ctp | 7 ++++++- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 31b9b7d..ea044b9 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -77,4 +77,4 @@ jobs: composer require cakephp/cakephp-codesniffer vendor/bin/phpcs --config-set installed_paths /home/runner/work/cakephp-filter-plugin/cakephp-filter-plugin/vendor/cakephp/cakephp-codesniffer - name: Run PHPCS - run: vendor/bin/phpcs --colors --parallel=16 -p --standard=CakePHP src/ tests/ + run: vendor/bin/phpcs --colors --parallel=16 -p --standard=CakePHP --extensions=php,ctp src/ tests/ diff --git a/src/View/Elements/filter_form_begin.ctp b/src/View/Elements/filter_form_begin.ctp index a2e3cd4..d83548d 100644 --- a/src/View/Elements/filter_form_begin.ctp +++ b/src/View/Elements/filter_form_begin.ctp @@ -9,21 +9,25 @@ MPL LGPL GPL -*/ + + @var mixed[] $options + @var string $modelName + @var \Cake\View\View $this + */ + ?>
Form->create( false, array( 'url' => array( - 'plugin' => $this->request->params['plugin'], - 'controller' => $this->request->params['controller'], - 'action' => $this->request->params['action'], + 'plugin' => $this->getRequest()->getParam('plugin'), + 'controller' => $this->getRequest()->getParam('controller'), + 'action' => $this->getRequest()->getParam('action'), ), 'id' => $modelName.'Filter', ) + $options ); ?> - Form->inputDefaults(array('required' => false)); ?>
- Form->input('Filter.filterFormId', array('type' => 'hidden', 'value' => $modelName)); ?> + Form->control('Filter.filterFormId', array('type' => 'hidden', 'value' => $modelName)); ?> diff --git a/src/View/Elements/filter_form_end.ctp b/src/View/Elements/filter_form_end.ctp index 85c8f9d..688a496 100644 --- a/src/View/Elements/filter_form_end.ctp +++ b/src/View/Elements/filter_form_end.ctp @@ -9,6 +9,8 @@ MPL LGPL GPL + + @var \Cake\View\View $this */ ?>
diff --git a/src/View/Elements/filter_form_fields.ctp b/src/View/Elements/filter_form_fields.ctp index 62daa80..7910531 100644 --- a/src/View/Elements/filter_form_fields.ctp +++ b/src/View/Elements/filter_form_fields.ctp @@ -9,6 +9,8 @@ MPL LGPL GPL + + @var \Cake\View\View $this */ if (isset($viewFilterParams)) @@ -21,7 +23,10 @@ if (isset($viewFilterParams)) 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($field['required'])) { + $field['required'] = false; + } + echo $this->Form->control($field['name'], $field['options']); } } } From 9e42499315e5416c895232be713fa2081cb05c6b Mon Sep 17 00:00:00 2001 From: bancer Date: Tue, 29 Aug 2023 09:01:38 +0200 Subject: [PATCH 36/37] Fix code style errors --- src/View/Elements/filter_form_begin.ctp | 21 ++++++++++----------- src/View/Elements/filter_form_end.ctp | 2 +- src/View/Elements/filter_form_fields.ctp | 11 ++++------- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/View/Elements/filter_form_begin.ctp b/src/View/Elements/filter_form_begin.ctp index d83548d..83bf4f2 100644 --- a/src/View/Elements/filter_form_begin.ctp +++ b/src/View/Elements/filter_form_begin.ctp @@ -10,7 +10,7 @@ LGPL GPL - @var mixed[] $options + @var array $options @var string $modelName @var \Cake\View\View $this */ @@ -19,20 +19,19 @@
Form->create( false, - array( - 'url' => array( + [ + 'url' => [ 'plugin' => $this->getRequest()->getParam('plugin'), - 'controller' => $this->getRequest()->getParam('controller'), - 'action' => $this->getRequest()->getParam('action'), - ), - 'id' => $modelName.'Filter', - ) + $options + 'controller' => $this->getRequest()->getParam('controller'), + 'action' => $this->getRequest()->getParam('action'), + ], + 'id' => $modelName . 'Filter', + ] + $options ); ?>
- Form->control('Filter.filterFormId', array('type' => 'hidden', 'value' => $modelName)); ?> + Form->control('Filter.filterFormId', ['type' => 'hidden', 'value' => $modelName]); ?> diff --git a/src/View/Elements/filter_form_end.ctp b/src/View/Elements/filter_form_end.ctp index 688a496..acb0762 100644 --- a/src/View/Elements/filter_form_end.ctp +++ b/src/View/Elements/filter_form_end.ctp @@ -11,7 +11,7 @@ GPL @var \Cake\View\View $this -*/ + */ ?>
Form->submit(__('Submit')); ?> diff --git a/src/View/Elements/filter_form_fields.ctp b/src/View/Elements/filter_form_fields.ctp index 7910531..19cd8fa 100644 --- a/src/View/Elements/filter_form_fields.ctp +++ b/src/View/Elements/filter_form_fields.ctp @@ -11,14 +11,11 @@ GPL @var \Cake\View\View $this -*/ + */ -if (isset($viewFilterParams)) -{ - foreach ($viewFilterParams as $field) - { - if(empty($includeFields) || in_array($field['name'], $includeFields)) - { +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]); From 4e247411049b4c7288184ae13c476a95f1f8e47f Mon Sep 17 00:00:00 2001 From: bancer Date: Tue, 29 Aug 2023 09:10:48 +0200 Subject: [PATCH 37/37] Update readme file --- README.markdown | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.markdown b/README.markdown index ea82238..b5aefe6 100644 --- a/README.markdown +++ b/README.markdown @@ -9,7 +9,9 @@ session, but this can be turned off if undesirable. It also features callback methods for further search refinement where necessary. -**IMPORTANT**: These instructions are for CakePHP 2.0. If you're using CakePHP 1.3.x +**IMPORTANT**: These instructions are for CakePHP 3.0. +If you are using CakePHP 2.x then go to https://github.com/lecterror/cakephp-filter-plugin/tree/2.x. +If you're using CakePHP 1.3.x 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. @@ -23,7 +25,7 @@ First, obtain the plugin. If you're using Git, run this while in your app folder git submodule update Or visit and download the -plugin manually to your `app/Plugin/Filter/` folder. +plugin manually to your `plugins/Filter/` folder. To use the plugin, you need to tell it which model to filter and which fields to use. For a quick tutorial, visit