diff --git a/README.md b/README.md index 3623ba6..580444f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Build Status](https://img.shields.io/github/workflow/status/FriendsOfCake/crud-view/CI/master?style=flat-square)](https://github.com/FriendsOfCake/crud-view/actions?query=workflow%3ACI+branch%3Amaster) +[![Build Status](https://img.shields.io/github/actions/workflow/status/FriendsOfCake/crud-view/ci.yml?branch=master&style=flat-square)](https://github.com/FriendsOfCake/crud-view/actions?query=workflow%3ACI+branch%3Amaster) [![Total Downloads](https://img.shields.io/packagist/dt/FriendsOfCake/crud-view.svg?style=flat-square)](https://packagist.org/packages/FriendsOfCake/crud-view) [![Latest Stable Version](https://img.shields.io/packagist/v/friendsofcake/crud-view.svg?style=flat-square)](https://packagist.org/packages/FriendsOfCake/crud-view) [![Documentation Status](https://readthedocs.org/projects/crud-view/badge/?version=latest&style=flat-square)](https://readthedocs.org/projects/crud-view/?badge=latest) diff --git a/docs/requirements.txt b/docs/requirements.txt index e9f5e9f..8b7a7d8 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,7 +1,7 @@ alabaster==0.7.12 Babel==2.9.1 cakephp-theme==1.1.3 -certifi==2022.12.7 +certifi==2023.7.22 chardet==3.0.4 docutils==0.15.2 idna==2.8 @@ -9,10 +9,10 @@ imagesize==1.1.0 Jinja2==2.11.3 MarkupSafe==1.1.1 packaging==19.2 -Pygments==2.7.4 +Pygments==2.15.0 pyparsing==2.4.5 pytz==2019.3 -requests==2.22.0 +requests==2.31.0 six==1.13.0 snowballstemmer==2.0.0 Sphinx==2.2.1 @@ -24,4 +24,4 @@ sphinxcontrib-jsmath==1.0.1 sphinxcontrib-phpdomain==0.6.2 sphinxcontrib-qthelp==1.0.2 sphinxcontrib-serializinghtml==1.1.3 -urllib3==1.26.5 +urllib3==1.26.17 diff --git a/src/Listener/ViewListener.php b/src/Listener/ViewListener.php index e7dce5c..7a2d784 100644 --- a/src/Listener/ViewListener.php +++ b/src/Listener/ViewListener.php @@ -75,7 +75,7 @@ public function beforePaginate(EventInterface $event): void } if (!$event->getSubject()->query->getContain()) { - $event->getSubject()->query->contain($this->_getRelatedModels(['manyToOne', 'oneToOne'])); + $event->getSubject()->query->contain($this->_getRelatedModels(['belongsTo', 'hasOne'])); } } diff --git a/src/Listener/ViewSearchListener.php b/src/Listener/ViewSearchListener.php index 2a09892..9ccaf1b 100644 --- a/src/Listener/ViewSearchListener.php +++ b/src/Listener/ViewSearchListener.php @@ -124,6 +124,11 @@ public function fields(): array $input['type'] = 'select'; } + /** @psalm-suppress PossiblyUndefinedArrayOffset */ + if ($input['type'] === 'select') { + $input += ['empty' => true]; + } + if (!empty($input['options'])) { $input['empty'] ??= $this->getPlaceholder($field); if (empty($input['class']) && !$config['select2']) { @@ -139,6 +144,7 @@ public function fields(): array $input['class'] = 'autocomplete'; } + /** @psalm-suppress PossiblyUndefinedArrayOffset */ if ( !empty($input['class']) && strpos($input['class'], 'autocomplete') !== false @@ -166,17 +172,20 @@ public function fields(): array } $urlArgs = []; - - $fieldKeys = $input['fields'] ?? ['id' => $field, 'value' => $field]; - if (is_array($fieldKeys)) { - foreach ($fieldKeys as $key => $val) { - $urlArgs[$key] = $val; + if (!isset($input['data-url'])) { + $urlArgs = []; + + $fieldKeys = $input['fields'] ?? ['id' => $field, 'value' => $field]; + if (is_array($fieldKeys)) { + foreach ($fieldKeys as $key => $val) { + $urlArgs[$key] = $val; + } } + + $input['data-url'] = Router::url(['action' => 'lookup', '_ext' => 'json', '?' => $urlArgs]); } unset($input['fields']); - $url = array_merge(['action' => 'lookup', '_ext' => 'json'], ['?' => $urlArgs]); - $input['data-url'] = Router::url($url); $fields[$field] = $input; } diff --git a/src/View/Helper/CrudViewHelper.php b/src/View/Helper/CrudViewHelper.php index 261302c..5a17535 100644 --- a/src/View/Helper/CrudViewHelper.php +++ b/src/View/Helper/CrudViewHelper.php @@ -3,8 +3,8 @@ namespace CrudView\View\Helper; -use Cake\Database\Schema\TableSchemaInterface; use Cake\Datasource\EntityInterface; +use Cake\Datasource\SchemaInterface; use Cake\Utility\Inflector; use Cake\Utility\Text; use Cake\View\Helper; @@ -381,9 +381,9 @@ public function currentModel(): string /** * Get model schema. * - * @return \Cake\Database\Schema\TableSchemaInterface + * @return \Cake\Datasource\SchemaInterface */ - public function schema(): TableSchemaInterface + public function schema(): SchemaInterface { return $this->getViewVar('modelSchema'); } diff --git a/src/View/Widget/DateTimeWidget.php b/src/View/Widget/DateTimeWidget.php index 151892e..fb6143e 100644 --- a/src/View/Widget/DateTimeWidget.php +++ b/src/View/Widget/DateTimeWidget.php @@ -111,6 +111,7 @@ public function render(array $data, ContextInterface $context): string $clearIcon = $this->clearIcon; $toggleIcon = $this->calendarIcon; + /** @psalm-suppress PossiblyUndefinedArrayOffset */ if (isset($datetimePicker['iconClass'])) { $toggleIcon = $datetimePicker['iconClass']; unset($datetimePicker['iconClass']); diff --git a/tests/Fixture/BlogsFixture.php b/tests/Fixture/BlogsFixture.php new file mode 100644 index 0000000..fa6da5e --- /dev/null +++ b/tests/Fixture/BlogsFixture.php @@ -0,0 +1,26 @@ + ['type' => 'integer'], + 'is_active' => ['type' => 'boolean', 'default' => true, 'null' => false], + 'name' => ['type' => 'string', 'length' => 255, 'null' => false], + 'body' => ['type' => 'text', 'null' => false], + 'user_id' => ['type' => 'integer'], + '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]], + ]; + + public $records = [ + ['name' => '1st post', 'body' => '1st post body', 'user_id' => 1], + ['name' => '2nd post', 'body' => '2nd post body', 'user_id' => 1], + ['name' => '3rd post', 'body' => '3rd post body', 'user_id' => 1], + ['name' => '4th post', 'body' => '4th post body', 'user_id' => 1], + ['name' => '5th post', 'body' => '5th post body', 'user_id' => 1], + ]; +} diff --git a/tests/TestCase/Listener/ViewSearchListenerTest.php b/tests/TestCase/Listener/ViewSearchListenerTest.php new file mode 100644 index 0000000..8b3c905 --- /dev/null +++ b/tests/TestCase/Listener/ViewSearchListenerTest.php @@ -0,0 +1,105 @@ +setRouteClass(DashedRoute::class); + $routesBuilder->connect('/{controller}/{action}/*', []) + ->setExtensions(['json']); + + $request = new ServerRequest([ + 'url' => '/blogs/index', + 'params' => ['controller' => 'Blogs', 'action' => 'index', 'plugin' => null, '_ext' => null], + ]); + + $this->controller = new Controller($request, null, 'Blogs'); + + $this->listener = new ViewSearchListener($this->controller); + + Router::setRequest($request); + } + + public function testFields() + { + $this->listener->setConfig(['fields' => [ + 'name', + 'is_active', + 'user_id', + 'custom_select' => ['empty' => false, 'type' => 'select'], + ]]); + + $fields = $this->listener->fields(); + $expected = [ + 'name' => [ + 'required' => false, + 'type' => 'select', + 'value' => null, + 'class' => 'autocomplete', + 'data-url' => '/blogs/lookup.json?id=name&value=name', + 'data-input-type' => 'text', + 'data-tags' => 'true', + 'data-allow-clear' => 'true', + 'data-placeholder' => '', + ], + 'is_active' => [ + 'required' => false, + 'type' => 'select', + 'value' => null, + 'empty' => true, + 'options' => ['No', 'Yes'], + ], + 'user_id' => [ + 'required' => false, + 'type' => 'select', + 'empty' => true, + 'value' => null, + 'class' => 'autocomplete', + 'data-url' => '/blogs/lookup.json?id=user_id&value=user_id', + ], + 'custom_select' => [ + 'required' => false, + 'type' => 'select', + 'empty' => false, + 'value' => null, + 'class' => 'autocomplete', + 'data-url' => '/blogs/lookup.json?id=custom_select&value=custom_select', + ], + ]; + $this->assertEquals($expected, $fields); + + $this->listener->setConfig([ + 'fields' => ['name' => ['data-url' => '/custom']], + ], null, true); + + $fields = $this->listener->fields(); + $expected['name']['data-url'] = '/custom'; + $this->assertEquals($expected, $fields); + } +}