From f1e543ab58d043e9e3dae6038705c5ec52a85f51 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sat, 8 Feb 2020 21:46:46 +0100 Subject: [PATCH 001/264] Add 3.0.x-dev branch --- README.md | 2 ++ composer.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 694692c..bf05412 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,5 @@ EcommitCrudBundle ================== [![SensioLabsInsight](https://insight.sensiolabs.com/projects/ee0677db-2ad9-4248-9b61-ab00020317b5/big.png)](https://insight.sensiolabs.com/projects/ee0677db-2ad9-4248-9b61-ab00020317b5) + +**WARNING: This branch is under development. Not use in production. You can use stable versions or 2.5 branch.** diff --git a/composer.json b/composer.json index 66d9b73..afb3e68 100644 --- a/composer.json +++ b/composer.json @@ -36,7 +36,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.5.x-dev" + "dev-master": "3.0.x-dev" } } } From b735b8e5a329c2ee5890010be1b655cc5b96a28c Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sat, 25 Apr 2020 19:02:33 +0200 Subject: [PATCH 002/264] Remove depreciations --- Controller/CrudControllerTrait.php | 31 +------------------ Crud/Crud.php | 16 ---------- DoctrineExtension/Paginate.php | 21 +------------ Form/Filter/FieldFilterEntity.php | 13 +------- ...ieldFilterJqueryAutocompleteEntityAjax.php | 13 +------- Form/Filter/FieldFilterSelect2EntityAjax.php | 13 +------- .../FieldFilterTokenInputEntitiesAjax.php | 13 +------- Helper/CrudHelper.php | 2 +- Paginator/ArrayPaginator.php | 28 ----------------- Paginator/DoctrineDBALPaginator.php | 14 --------- 10 files changed, 7 insertions(+), 157 deletions(-) diff --git a/Controller/CrudControllerTrait.php b/Controller/CrudControllerTrait.php index 9e1caab..dd519fe 100644 --- a/Controller/CrudControllerTrait.php +++ b/Controller/CrudControllerTrait.php @@ -80,36 +80,7 @@ protected function afterBuildQuery() * @param string $action Action * @return string */ - protected function getTemplateName($action) - { - trigger_error('The "getTemplateName" must be overrided. This method will soon be abstract.', E_USER_DEPRECATED); - - return $this->getPathView($action); - } - - /** - * Returns the path of the template given - * - * @param string $name Template name - * @return string - * @deprecated Deprecated since version 2.4. Override getTemplateName method instead. - */ - protected function getPathView($name) - { - trigger_error('The "getPathView" method is deprecated since version 2.4. Override "getTemplateName" method instead.', E_USER_DEPRECATED); - - if (preg_match('/^(?P\w+)\\\(?P\w+)\\\Controller\\\(?P\w+)Controller$/', get_class($this), $matches)) { - return sprintf('%s%s:%s:%s.html.twig', $matches['vendor'], $matches['bundle'], $matches['controller'], $name); - } elseif (preg_match('/^(?P\w+)\\\(?P\w+)\\\Controller\\\(?P\w+)\\\(?P\w+)Controller$/', get_class($this), $matches)) { - return sprintf('%s%s:%s/%s:%s.html.twig', $matches['vendor'], $matches['bundle'], $matches['dir'], $matches['controller'], $name); - } elseif (preg_match('/^AppBundle\\\Controller\\\(?P\w+)Controller$/', get_class($this), $matches)) { - return sprintf('AppBundle:%s:%s.html.twig', $matches['controller'], $name); - } elseif (preg_match('/^AppBundle\\\Controller\\\(?P\w+)\\\(?P\w+)Controller$/', get_class($this), $matches)) { - return sprintf('AppBundle:%s/%s:%s.html.twig', $matches['dir'], $matches['controller'], $name); - } - - new \Exception('getPathView: Bad structure'); - } + abstract protected function getTemplateName($action); public function autoAjaxListAction() { diff --git a/Crud/Crud.php b/Crud/Crud.php index ea1320f..0dd2238 100644 --- a/Crud/Crud.php +++ b/Crud/Crud.php @@ -51,7 +51,6 @@ class Crud protected $defaultFormSearcherData = null; protected $queryBuilder = null; - protected $useDbal = false; //Deprecated. Not used protected $persistentSettings = false; protected $updateDatabase = false; protected $paginator = null; @@ -367,21 +366,6 @@ public function setBuildPaginator($value) return $this; } - /* - * Use (or not) DBAL - * - * @param bool $value - * @deprecated Deprecated since version 2.3. - */ - public function setUseDbal($value) - { - trigger_error('setUseDbal is deprecated since 2.3 version.', E_USER_DEPRECATED); - - $this->useDbal = $value; - - return $this; - } - /* * Use (or not) persistent settings * diff --git a/DoctrineExtension/Paginate.php b/DoctrineExtension/Paginate.php index 0b40e58..8e45236 100644 --- a/DoctrineExtension/Paginate.php +++ b/DoctrineExtension/Paginate.php @@ -12,7 +12,6 @@ namespace Ecommit\CrudBundle\DoctrineExtension; use Doctrine\ORM\NativeQuery; -use Doctrine\ORM\Query; use Doctrine\ORM\Query\Parameter; use Doctrine\ORM\Query\ResultSetMapping; use Doctrine\ORM\Tools\Pagination\Paginator; @@ -26,25 +25,7 @@ class Paginate { /** - * Returns total results (SQL function "count") - * - * @param Query $query - * @param bool $simplifiedRequest Use simplified request (not subrequest and not order by) or not - * @return int - * @deprecated Deprecated since version 2.4. Use countQueryBuilder or Doctrine\ORM\Tools\Pagination\Paginator::count method instead. - */ - static public function count(Query $query, $simplifiedRequest = true) - { - trigger_error('Paginate::count is deprecated since 2.4 version. Use countQueryBuilder or Doctrine\ORM\Tools\Pagination\Paginator::count method instead.', E_USER_DEPRECATED); - - $doctrinePaginator = new Paginator($query); - $doctrinePaginator->setUseOutputWalkers(!$simplifiedRequest); - - return $doctrinePaginator->count(); - } - - /** - * @param \Doctrine\ORM\QueryBuilde|\Doctrine\DBAL\Query\QueryBuilder $queryBuilder + * @param \Doctrine\ORM\QueryBuilder|\Doctrine\DBAL\Query\QueryBuilder $queryBuilder * @param array $options * @return int * @throws \Exception diff --git a/Form/Filter/FieldFilterEntity.php b/Form/Filter/FieldFilterEntity.php index 09cf17f..d7e34e5 100644 --- a/Form/Filter/FieldFilterEntity.php +++ b/Form/Filter/FieldFilterEntity.php @@ -14,7 +14,6 @@ use Doctrine\Common\Persistence\ManagerRegistry; use Ecommit\JavascriptBundle\Form\Type\EntityNormalizerTrait; use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader; -use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\PropertyAccess\PropertyAccess; @@ -36,17 +35,7 @@ protected function configureOptions(OptionsResolver $resolver) $resolver->setDefaults( array( - 'property' => null, // deprecated since 2.2, use "choice_label" - 'choice_label' => function (Options $options) { - // BC with the "property" option - if ($options['property']) { - trigger_error('The "property" option is deprecated since version 2.2. Use "choice_label" instead.', E_USER_DEPRECATED); - - return $options['property']; - } - - return null; - }, + 'choice_label' => null, 'em' => null, 'query_builder' => null, 'identifier' => null, diff --git a/Form/Filter/FieldFilterJqueryAutocompleteEntityAjax.php b/Form/Filter/FieldFilterJqueryAutocompleteEntityAjax.php index 860f14c..144b5aa 100644 --- a/Form/Filter/FieldFilterJqueryAutocompleteEntityAjax.php +++ b/Form/Filter/FieldFilterJqueryAutocompleteEntityAjax.php @@ -13,7 +13,6 @@ use Ecommit\CrudBundle\Form\Searcher\AbstractFormSearcher; use Ecommit\JavascriptBundle\Form\Type\JqueryAutocompleteEntityAjaxType; use Symfony\Component\Form\FormBuilder; -use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Validator\Constraints as Assert; @@ -28,17 +27,7 @@ protected function configureOptions(OptionsResolver $resolver) array( 'em' => null, 'query_builder' => null, - 'property' => null, // deprecated since 2.2, use "choice_label" - 'choice_label' => function (Options $options) { - // BC with the "property" option - if ($options['property']) { - trigger_error('The "property" option is deprecated since version 2.2. Use "choice_label" instead.', E_USER_DEPRECATED); - - return $options['property']; - } - - return null; - }, + 'choice_label' => null, 'identifier' => null, 'url' => null, //Required in FormType if route_name is empty 'route_name' => null, diff --git a/Form/Filter/FieldFilterSelect2EntityAjax.php b/Form/Filter/FieldFilterSelect2EntityAjax.php index 0b3af32..06c0b5e 100644 --- a/Form/Filter/FieldFilterSelect2EntityAjax.php +++ b/Form/Filter/FieldFilterSelect2EntityAjax.php @@ -12,7 +12,6 @@ use Ecommit\JavascriptBundle\Form\Type\Select2\Select2EntityAjaxType; use Symfony\Component\Form\FormBuilder; -use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; class FieldFilterSelect2EntityAjax extends FieldFilterChoice @@ -26,17 +25,7 @@ protected function configureOptions(OptionsResolver $resolver) array( 'em' => null, 'query_builder' => null, - 'property' => null, // deprecated since 2.2, use "choice_label" - 'choice_label' => function (Options $options) { - // BC with the "property" option - if ($options['property']) { - trigger_error('The "property" option is deprecated since version 2.2. Use "choice_label" instead.', E_USER_DEPRECATED); - - return $options['property']; - } - - return null; - }, + 'choice_label' => null, 'identifier' => null, 'url' => null, //Required in FormType if route_name is empty 'route_name' => null, diff --git a/Form/Filter/FieldFilterTokenInputEntitiesAjax.php b/Form/Filter/FieldFilterTokenInputEntitiesAjax.php index 271cd9a..7364b18 100644 --- a/Form/Filter/FieldFilterTokenInputEntitiesAjax.php +++ b/Form/Filter/FieldFilterTokenInputEntitiesAjax.php @@ -14,7 +14,6 @@ use Ecommit\JavascriptBundle\Form\Type\TokenInputEntitiesAjaxType; use Ecommit\UtilBundle\Util\Util; use Symfony\Component\Form\FormBuilder; -use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Validator\Constraints as Assert; @@ -29,17 +28,7 @@ protected function configureOptions(OptionsResolver $resolver) array( 'em' => null, 'query_builder' => null, - 'property' => null, // deprecated since 2.2, use "choice_label" - 'choice_label' => function (Options $options) { - // BC with the "property" option - if ($options['property']) { - trigger_error('The "property" option is deprecated since version 2.2. Use "choice_label" instead.', E_USER_DEPRECATED); - - return $options['property']; - } - - return null; - }, + 'choice_label' => null, 'identifier' => null, 'url' => null, //Required in FormType if route_name is empty 'route_name' => null, diff --git a/Helper/CrudHelper.php b/Helper/CrudHelper.php index a34a95c..32d22c9 100644 --- a/Helper/CrudHelper.php +++ b/Helper/CrudHelper.php @@ -673,7 +673,7 @@ public function remoteModal($modalId, $url, $options, $ajaxOptions) * Returns modal form tag * * @param string $modalId Modal id - * @param FormView|string $form The form or the url. Url is deprecated since 2.2 version + * @param FormView|string $form The form or the url. * @param array $ajaxOptions Ajax options * @param array $htmlOptions Html options * @return string diff --git a/Paginator/ArrayPaginator.php b/Paginator/ArrayPaginator.php index 42843b0..00fa575 100644 --- a/Paginator/ArrayPaginator.php +++ b/Paginator/ArrayPaginator.php @@ -47,34 +47,6 @@ public function init() } } - /** - * Set an array of results - * - * @param array|ArrayIterator $results - * @return ArrayPaginator - * @deprecated Deprecated since version 2.2. Use setData method instead. - */ - public function setResults($results) - { - trigger_error('setResults is deprecated since 2.2 version. Use setData instead', E_USER_DEPRECATED); - - return $this->setData($results); - } - - /** - * Set an array of results without slice - * - * @param array|ArrayIterator $results - * @param Int $manualCountResults - * @deprecated Deprecated since version 2.2. Use setDataWithoutSlice method instead. - */ - public function setResultsWithoutSlice($results, $manualCountResults) - { - trigger_error('setResultsWithoutSlice is deprecated since 2.2 version. Use setDataWithoutSlice instead', E_USER_DEPRECATED); - - return $this->setDataWithoutSlice($results, $manualCountResults); - } - /** * Set an array of results * diff --git a/Paginator/DoctrineDBALPaginator.php b/Paginator/DoctrineDBALPaginator.php index 3b07fa9..698e287 100644 --- a/Paginator/DoctrineDBALPaginator.php +++ b/Paginator/DoctrineDBALPaginator.php @@ -37,20 +37,6 @@ public function initPaginator() } } - /** - * Sets the QueryBuilder - * - * @param mixed $query - * @return DoctrineDBALPaginator - * @deprecated Deprecated since version 2.2. Use setQueryBuilder method instead. - */ - public function setDbalQueryBuilder($query) - { - trigger_error('setDbalQueryBuilder is deprecated since 2.2 version. Use setQueryBuilder instead', E_USER_DEPRECATED); - - return $this->setQueryBuilder($query); - } - /** * {@inheritDoc} */ From 253705870711df90e8ecedffc2cfe74bf26a61f7 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sun, 26 Apr 2020 14:21:29 +0200 Subject: [PATCH 003/264] Remove AbstractCrudLegacyController. AbstractController extends now Symfony\Bundle\FrameworkBundle\Controller\AbstractController (before extended Symfony\Component\DependencyInjection\ContainerAwareTrait) --- Controller/AbstractCrudController.php | 21 ++++++---------- Controller/AbstractCrudLegacyController.php | 27 --------------------- 2 files changed, 7 insertions(+), 41 deletions(-) delete mode 100644 Controller/AbstractCrudLegacyController.php diff --git a/Controller/AbstractCrudController.php b/Controller/AbstractCrudController.php index 71eb8b9..5118a90 100644 --- a/Controller/AbstractCrudController.php +++ b/Controller/AbstractCrudController.php @@ -11,25 +11,18 @@ namespace Ecommit\CrudBundle\Controller; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\ControllerTrait; -use Symfony\Component\DependencyInjection\ContainerAwareInterface; -use Symfony\Component\DependencyInjection\ContainerAwareTrait; -abstract class AbstractCrudController implements ContainerAwareInterface +abstract class AbstractCrudController extends AbstractController { - use ContainerAwareTrait; - use ControllerTrait; use CrudControllerTrait; - /** - * Gets a container configuration parameter by its name. - * - * @return mixed - * - * @final - */ - protected function getParameter(string $name) + public static function getSubscribedServices() { - return $this->container->getParameter($name); + return array_merge( + parent::getSubscribedServices(), + self::getCrudRequiredServices() + ); } } diff --git a/Controller/AbstractCrudLegacyController.php b/Controller/AbstractCrudLegacyController.php deleted file mode 100644 index 757c813..0000000 --- a/Controller/AbstractCrudLegacyController.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Ecommit\CrudBundle\Controller; - -use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; - -abstract class AbstractCrudLegacyController extends AbstractController -{ - use CrudControllerTrait; - - public static function getSubscribedServices() - { - return array_merge( - parent::getSubscribedServices(), - self::getCrudRequiredServices() - ); - } -} From 70671b671c89cba24368ec16fe01c1bb65be3b53 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sat, 2 May 2020 19:38:18 +0200 Subject: [PATCH 004/264] Add functional tests --- .editorconfig | 9 + .gitignore | 5 + .travis.yml | 18 + Tests/App/Controller/UserController.php | 64 ++++ Tests/App/DataFixtures/TestUserFixtures.php | 46 +++ Tests/App/Entity/TestUser.php | 89 +++++ Tests/App/Form/Searcher/UserSearcher.php | 33 ++ Tests/App/Kernel.php | 63 ++++ Tests/App/config/bootstrap.php | 10 + Tests/App/config/doctrine.yaml | 22 ++ Tests/App/config/framework.yaml | 5 + Tests/App/config/routes.yaml | 11 + Tests/App/config/security.yaml | 5 + Tests/App/config/services.yaml | 14 + Tests/App/public/index.php | 27 ++ Tests/App/templates/layout.html.twig | 11 + Tests/App/templates/user/index.html.twig | 11 + Tests/App/templates/user/list.html.twig | 29 ++ Tests/App/templates/user/search.html.twig | 12 + .../Controller/TestCrudControllerTest.php | 327 ++++++++++++++++++ Tests/bootstrap.php | 33 ++ composer.json | 50 ++- phpunit.xml.dist | 26 ++ 23 files changed, 901 insertions(+), 19 deletions(-) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 Tests/App/Controller/UserController.php create mode 100644 Tests/App/DataFixtures/TestUserFixtures.php create mode 100644 Tests/App/Entity/TestUser.php create mode 100644 Tests/App/Form/Searcher/UserSearcher.php create mode 100644 Tests/App/Kernel.php create mode 100644 Tests/App/config/bootstrap.php create mode 100644 Tests/App/config/doctrine.yaml create mode 100644 Tests/App/config/framework.yaml create mode 100644 Tests/App/config/routes.yaml create mode 100644 Tests/App/config/security.yaml create mode 100644 Tests/App/config/services.yaml create mode 100644 Tests/App/public/index.php create mode 100644 Tests/App/templates/layout.html.twig create mode 100644 Tests/App/templates/user/index.html.twig create mode 100644 Tests/App/templates/user/list.html.twig create mode 100644 Tests/App/templates/user/search.html.twig create mode 100644 Tests/Functional/Controller/TestCrudControllerTest.php create mode 100644 Tests/bootstrap.php create mode 100644 phpunit.xml.dist diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0e740ce --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +; top-most EditorConfig file +root = true + +; Unix-style newlines +[*] +end_of_line = LF +insert_final_newline = true +indent_style = space +indent_size = 4 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d7c9146 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.php_cs.cache +.phpunit.result.cache +composer.lock +Tests/App/var/ +vendor/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..af268da --- /dev/null +++ b/.travis.yml @@ -0,0 +1,18 @@ +language: php +php: + - 7.2 + - 7.3 + - 7.4 + +env: + - DEPENDENCIES="high" + - DEPENDENCIES="low" + +addons: + firefox: latest + +install: + - if [ "$DEPENDENCIES" = "low" ]; then composer update --prefer-lowest --prefer-stable; else composer install; fi; + +script: + - php vendor/phpunit/phpunit/phpunit diff --git a/Tests/App/Controller/UserController.php b/Tests/App/Controller/UserController.php new file mode 100644 index 0000000..62096ac --- /dev/null +++ b/Tests/App/Controller/UserController.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Ecommit\CrudBundle\Tests\App\Controller; + +use Ecommit\CrudBundle\Controller\AbstractCrudController; +use Ecommit\CrudBundle\Crud\Crud; +use Ecommit\CrudBundle\Tests\App\Entity\TestUser; +use Ecommit\CrudBundle\Tests\App\Form\Searcher\UserSearcher; + +class UserController extends AbstractCrudController +{ + protected function configCrud() + { + $em = $this->getDoctrine()->getManager(); + + $queryBuilder = $em->getRepository(TestUser::class) + ->createQueryBuilder('u') + ->select('u'); + + $crud = $this->createCrud('user'); + $crud->addColumn('username', 'u.username', 'username', ['default_displayed' => false]) + ->addColumn('firstName', 'u.firstName', 'first_name') + ->addColumn('lastName', 'u.lastName', 'last_name') + ->setQueryBuilder($queryBuilder) + ->setAvailableResultsPerPage([5, 5, 10, 50], 5) + ->setDefaultSort('firstName', Crud::ASC) + ->createSearcherForm(new UserSearcher()) + ->setRoute('user_ajax_list') + ->setSearchRoute('user_ajax_search') + ->setPersistentSettings(true) + ->init(); + + return $crud; + } + + protected function getTemplateName($action) + { + return sprintf('user/%s.html.twig', $action); + } + + public function listAction() + { + return $this->autoListAction(); + } + + public function ajaxListAction() + { + return $this->autoAjaxListAction(); + } + + public function ajaxSearchAction() + { + return $this->autoAjaxSearchAction(); + } +} diff --git a/Tests/App/DataFixtures/TestUserFixtures.php b/Tests/App/DataFixtures/TestUserFixtures.php new file mode 100644 index 0000000..c5f9e08 --- /dev/null +++ b/Tests/App/DataFixtures/TestUserFixtures.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Ecommit\CrudBundle\Tests\App\DataFixtures; + +use Doctrine\Bundle\FixturesBundle\Fixture; +use Doctrine\Persistence\ObjectManager; +use Ecommit\CrudBundle\Tests\App\Entity\TestUser; + +class TestUserFixtures extends Fixture +{ + public function load(ObjectManager $manager) + { + $dataSet = [ + ['Eve', 'Reste'], + ['Henri', 'Poste'], + ['Henri', 'Plait'], + ['Jean', 'Serrien'], + ['Clément', 'Tine'], + ['Aude', 'Javel'], + ['Yvon', 'Embavé'], + ['Judie', 'Cieux'], + ['Paul', 'Ochon'], + ['Sarah', 'Pelle'], + ['Thierry', 'Gollo'], + ]; + + foreach ($dataSet as $data) { + $user = new TestUser(); + $user->setUsername($data[0].$data[1]) + ->setFirstName($data[0]) + ->setLastName($data[1]); + $manager->persist($user); + } + + $manager->flush(); + } +} diff --git a/Tests/App/Entity/TestUser.php b/Tests/App/Entity/TestUser.php new file mode 100644 index 0000000..9770a7f --- /dev/null +++ b/Tests/App/Entity/TestUser.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Ecommit\CrudBundle\Tests\App\Entity; + +use Doctrine\ORM\Mapping as ORM; +use Ecommit\CrudBundle\Entity\UserCrudInterface; + +/** + * @ORM\Entity + * @ORM\Table(name="user") + */ +class TestUser implements UserCrudInterface +{ + /** + * @ORM\Id + * @ORM\Column(type="integer") + * @ORM\GeneratedValue(strategy="AUTO") + */ + protected $userId; + + /** + * @ORM\Column(type="string", length=180, unique=true) + */ + protected $username; + + /** + * @ORM\Column(type="string", length=30) + */ + protected $firstName; + + /** + * @ORM\Column(type="string", length=30) + */ + protected $lastName; + + /* + * Getters / Setters (auto-generated) + */ + + public function getUserId(): ?int + { + return $this->userId; + } + + public function setUsername(?string $username): self + { + $this->username = $username; + + return $this; + } + + public function getUsername(): ?string + { + return $this->username; + } + + public function setFirstName(?string $firstName): self + { + $this->firstName = $firstName; + + return $this; + } + + public function getFirstName(): ?string + { + return $this->firstName; + } + + public function setLastName(?string $lastName): self + { + $this->lastName = $lastName; + + return $this; + } + + public function getLastName(): ?string + { + return $this->lastName; + } +} diff --git a/Tests/App/Form/Searcher/UserSearcher.php b/Tests/App/Form/Searcher/UserSearcher.php new file mode 100644 index 0000000..03c2cfe --- /dev/null +++ b/Tests/App/Form/Searcher/UserSearcher.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Ecommit\CrudBundle\Tests\App\Form\Searcher; + +use Ecommit\CrudBundle\Form\Filter as Filter; +use Ecommit\CrudBundle\Form\Searcher\AbstractFormSearcher; + +class UserSearcher extends AbstractFormSearcher +{ + public $username; + + public $firstName; + + public $lastName; + + public function configureFieldsFilter() + { + return [ + new Filter\FieldFilterText('username', 'username', []), + new Filter\FieldFilterText('firstName', 'firstName', []), + new Filter\FieldFilterText('lastName', 'lastName', []), + ]; + } +} diff --git a/Tests/App/Kernel.php b/Tests/App/Kernel.php new file mode 100644 index 0000000..2f8a99e --- /dev/null +++ b/Tests/App/Kernel.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Ecommit\CrudBundle\Tests\App; + +use Doctrine\Bundle\DoctrineBundle\DoctrineBundle; +use Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle; +use Ecommit\CrudBundle\EcommitCrudBundle; +use Ecommit\JavascriptBundle\EcommitJavascriptBundle; +use Ecommit\UtilBundle\EcommitUtilBundle; +use Symfony\Bundle\FrameworkBundle\FrameworkBundle; +use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; +use Symfony\Bundle\SecurityBundle\SecurityBundle; +use Symfony\Bundle\TwigBundle\TwigBundle; +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\HttpKernel\Kernel as BaseKernel; +use Symfony\Component\Routing\RouteCollectionBuilder; + +class Kernel extends BaseKernel +{ + use MicroKernelTrait; + + protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void + { + $loader->load($this->getProjectDir().'/config/framework.yaml'); + $loader->load($this->getProjectDir().'/config/doctrine.yaml'); + $loader->load($this->getProjectDir().'/config/security.yaml'); + $loader->load($this->getProjectDir().'/config/services.yaml'); + } + + public function registerBundles() + { + return [ + new TwigBundle(), + new DoctrineBundle(), + new DoctrineFixturesBundle(), + new FrameworkBundle(), + new SecurityBundle(), + new EcommitCrudBundle(), + new EcommitJavascriptBundle(), + new EcommitUtilBundle(), + ]; + } + + protected function configureRoutes(RouteCollectionBuilder $routes): void + { + $routes->import($this->getProjectDir().'/config/routes.yaml'); + } + + public function getProjectDir() + { + return __DIR__; + } +} diff --git a/Tests/App/config/bootstrap.php b/Tests/App/config/bootstrap.php new file mode 100644 index 0000000..442c378 --- /dev/null +++ b/Tests/App/config/bootstrap.php @@ -0,0 +1,10 @@ +handle($request); +$response->send(); +$kernel->terminate($request, $response); diff --git a/Tests/App/templates/layout.html.twig b/Tests/App/templates/layout.html.twig new file mode 100644 index 0000000..439fd1a --- /dev/null +++ b/Tests/App/templates/layout.html.twig @@ -0,0 +1,11 @@ + + + {% block title %}{% endblock %} + + {% block stylesheets %}{% endblock %} + {% block javascripts %}{% endblock %} + + + {% block content %}{% endblock %} + + diff --git a/Tests/App/templates/user/index.html.twig b/Tests/App/templates/user/index.html.twig new file mode 100644 index 0000000..cfafad4 --- /dev/null +++ b/Tests/App/templates/user/index.html.twig @@ -0,0 +1,11 @@ +{% extends 'layout.html.twig' %} + +{% block content %} + + +
+ {% include 'user/list.html.twig' %} +
+{% endblock %} diff --git a/Tests/App/templates/user/list.html.twig b/Tests/App/templates/user/list.html.twig new file mode 100644 index 0000000..bc94ab9 --- /dev/null +++ b/Tests/App/templates/user/list.html.twig @@ -0,0 +1,29 @@ +
+ + + + {{ crud_th('username', crud) }} + {{ crud_th('firstName', crud) }} + {{ crud_th('lastName', crud) }} + + + + {% for user in crud.paginator %} + + {{ crud_td('username', crud, user.username|lower) }} + {{ crud_td('firstName', crud, user.firstName|capitalize) }} + {{ crud_td('lastName', crud, user.lastName|capitalize) }} + + {% endfor %} + +
+
+ +
+ {% trans with {'%first%': crud.paginator.firstIndice, '%last%' : crud.paginator.lastIndice} %}Results %first%-%last%{% endtrans %} - + {% trans with {'%page%': crud.paginator.page, '%lastPage%' : crud.paginator.lastPage} %}Page %page%/%lastPage%{% endtrans %} +
+ +{{ crud_paginator_links(crud) }} + +{{ crud_display_settings(crud, {modal: false}) }} diff --git a/Tests/App/templates/user/search.html.twig b/Tests/App/templates/user/search.html.twig new file mode 100644 index 0000000..a706b8e --- /dev/null +++ b/Tests/App/templates/user/search.html.twig @@ -0,0 +1,12 @@ +{{ crud_search_form(crud, {}, {'class': 'form-inline'}) }} + {{ form_row(crud.searcherform.username) }} + {{ form_row(crud.searcherform.firstName) }} + {{ form_row(crud.searcherform.lastName) }} + +
+ + {{ crud_search_reset(crud) }} +
+ diff --git a/Tests/Functional/Controller/TestCrudControllerTest.php b/Tests/Functional/Controller/TestCrudControllerTest.php new file mode 100644 index 0000000..968cbb0 --- /dev/null +++ b/Tests/Functional/Controller/TestCrudControllerTest.php @@ -0,0 +1,327 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Ecommit\CrudBundle\Tests\Functional\Controller; + +use Ecommit\CrudBundle\Crud\Crud; +use Symfony\Component\Panther\Client; +use Symfony\Component\Panther\DomCrawler\Crawler; +use Symfony\Component\Panther\PantherTestCase; + +class CrudControllerTest extends PantherTestCase +{ + public static function setUpBeforeClass(): void + { + parent::setUpBeforeClass(); + + static::$defaultOptions['browser'] = static::FIREFOX; + } + + public function testList(): Client + { + $client = static::createPantherClient(); + $client->request('GET', '/user/'); + + $this->assertSame([5, 2], $this->countRowsAndColumns($client->getCrawler())); + $this->assertSame([1, 3], $this->getPagination($client->getCrawler())); + $this->assertSame(['first_name', Crud::ASC], $this->getSort($client->getCrawler())); + $this->assertSame('AudeJavel', $this->getFirstUsername($client->getCrawler())); + + return $client; + } + + /** + * @depends testList + */ + public function testChangeSortSense(Client $client): Client + { + $link = $client->getCrawler()->filterXPath('//table[@class="result"]/thead/tr/th/a[text()="first_name"]'); + $link->click(); + $this->waitForAjax($client); + + $this->assertSame([5, 2], $this->countRowsAndColumns($client->getCrawler())); + $this->assertSame([1, 3], $this->getPagination($client->getCrawler())); + $this->assertSame(['first_name', Crud::DESC], $this->getSort($client->getCrawler())); + $this->assertSame('YvonEmbavé', $this->getFirstUsername($client->getCrawler())); + + return $client; + } + + /** + * @depends testChangeSortSense + */ + public function testChangeSortColumn(Client $client): Client + { + $link = $client->getCrawler()->filterXPath('//table[@class="result"]/thead/tr/th/a[text()="last_name"]'); + $link->click(); + $this->waitForAjax($client); + + $this->assertSame([5, 2], $this->countRowsAndColumns($client->getCrawler())); + $this->assertSame([1, 3], $this->getPagination($client->getCrawler())); + $this->assertSame(['last_name', Crud::DESC], $this->getSort($client->getCrawler())); + $this->assertSame('ClémentTine', $this->getFirstUsername($client->getCrawler())); + + return $client; + } + + /** + * @depends testChangeSortColumn + */ + public function testChangeDisplayedColumns(Client $client): Client + { + $button = $client->getCrawler()->filterXPath('//button[contains(.,"Display Settings")]'); + $button->first()->click(); + + $client->getCrawler()->filterXPath('//form[@name="crud_display_settings_user"]/descendant::input[@value="username"]')->click(); + $client->getCrawler()->filterXPath('//form[@name="crud_display_settings_user"]/descendant::button[@type="submit"]')->click(); + $this->waitForAjax($client); + + $this->assertSame([5, 3], $this->countRowsAndColumns($client->getCrawler())); + $this->assertSame([1, 3], $this->getPagination($client->getCrawler())); + $this->assertSame(['last_name', Crud::DESC], $this->getSort($client->getCrawler())); + $this->assertSame('ClémentTine', $this->getFirstUsername($client->getCrawler())); + + return $client; + } + + /** + * @depends testChangeDisplayedColumns + */ + public function testChangePerPage(Client $client): Client + { + $button = $client->getCrawler()->filterXPath('//button[contains(., "Display Settings")]'); + $button->first()->click(); + + $form = $client->getCrawler()->filterXPath('//form[@name="crud_display_settings_user"]/descendant::button[@type="submit"]')->form(); + $form['crud_display_settings_user[resultsPerPage]'] = 10; + $client->submit($form); + $this->waitForAjax($client); + + $this->assertSame([10, 3], $this->countRowsAndColumns($client->getCrawler())); + $this->assertSame([1, 2], $this->getPagination($client->getCrawler())); + $this->assertSame(['last_name', Crud::DESC], $this->getSort($client->getCrawler())); + $this->assertSame('ClémentTine', $this->getFirstUsername($client->getCrawler())); + + return $client; + } + + /** + * @depends testChangePerPage + */ + public function testPersistentValuesAfterChangeSortAndSettings(Client $client): Client + { + $client->request('GET', '/user/'); + + $this->assertSame([10, 3], $this->countRowsAndColumns($client->getCrawler())); + $this->assertSame([1, 2], $this->getPagination($client->getCrawler())); + $this->assertSame(['last_name', Crud::DESC], $this->getSort($client->getCrawler())); + $this->assertSame('ClémentTine', $this->getFirstUsername($client->getCrawler())); + + return $client; + } + + /** + * @depends testPersistentValuesAfterChangeSortAndSettings + */ + public function testChangePage(Client $client): Client + { + $page = $client->getCrawler()->filterXPath('//ul[@class="pagination"]/li/a[text()="2"]'); + $page->first()->click(); + $this->waitForAjax($client); + + $this->assertSame([1, 3], $this->countRowsAndColumns($client->getCrawler())); + $this->assertSame([2, 2], $this->getPagination($client->getCrawler())); + $this->assertSame(['last_name', Crud::DESC], $this->getSort($client->getCrawler())); + $this->assertSame('JudieCieux', $this->getFirstUsername($client->getCrawler())); + + return $client; + } + + /** + * @depends testChangePage + */ + public function testPersistentValuesAfterChangePage(Client $client): Client + { + $client->request('GET', '/user/'); + + $this->assertSame([1, 3], $this->countRowsAndColumns($client->getCrawler())); + $this->assertSame([2, 2], $this->getPagination($client->getCrawler())); + $this->assertSame(['last_name', Crud::DESC], $this->getSort($client->getCrawler())); + $this->assertSame('JudieCieux', $this->getFirstUsername($client->getCrawler())); + + return $client; + } + + /** + * @depends testPersistentValuesAfterChangePage + */ + public function testSearch(Client $client): Client + { + $form = $client->getCrawler()->filterXPath('//div[@id="crud_search"]/descendant::button[@type="submit" and contains(text(), "Search")]')->form(); + $form['crud_search_user[firstName]'] = 'Henri'; + $client->submit($form); + $this->waitForAjax($client); + + $this->assertSame([2, 3], $this->countRowsAndColumns($client->getCrawler())); + $this->assertSame([1, 1], $this->getPagination($client->getCrawler())); + $this->assertSame(['last_name', Crud::DESC], $this->getSort($client->getCrawler())); + $this->assertSame('HenriPoste', $this->getFirstUsername($client->getCrawler())); + + return $client; + } + + /** + * @depends testSearch + */ + public function testPersistentValuesAfterSearch(Client $client): Client + { + $client->request('GET', '/user/'); + + $this->assertSame([2, 3], $this->countRowsAndColumns($client->getCrawler())); + $this->assertSame([1, 1], $this->getPagination($client->getCrawler())); + $this->assertSame(['last_name', Crud::DESC], $this->getSort($client->getCrawler())); + $this->assertSame('HenriPoste', $this->getFirstUsername($client->getCrawler())); + + return $client; + } + + /** + * @depends testPersistentValuesAfterSearch + */ + public function testResetSearch(Client $client): Client + { + $button = $client->getCrawler()->filterXPath('//div[@id="crud_search"]/descendant::button[contains(text(), "Reset")]'); + $button->first()->click(); + $this->waitForAjax($client); + + $this->assertSame([10, 3], $this->countRowsAndColumns($client->getCrawler())); + $this->assertSame([1, 2], $this->getPagination($client->getCrawler())); + $this->assertSame(['last_name', Crud::DESC], $this->getSort($client->getCrawler())); + $this->assertSame('ClémentTine', $this->getFirstUsername($client->getCrawler())); + + return $client; + } + + /** + * @depends testResetSearch + */ + public function testPersistentValuesAfterResetSearch(Client $client): Client + { + $client->request('GET', '/user/'); + + $this->assertSame([10, 3], $this->countRowsAndColumns($client->getCrawler())); + $this->assertSame([1, 2], $this->getPagination($client->getCrawler())); + $this->assertSame(['last_name', Crud::DESC], $this->getSort($client->getCrawler())); + $this->assertSame('ClémentTine', $this->getFirstUsername($client->getCrawler())); + + return $client; + } + + /** + * @depends testPersistentValuesAfterResetSearch + */ + public function testResetSettings(Client $client): Client + { + $button = $client->getCrawler()->filterXPath('//button[contains(., "Display Settings")]'); + $button->first()->click(); + + $button = $client->getCrawler()->filterXPath('//form[@name="crud_display_settings_user"]/descendant::button[contains(text(), "Reset display settings")]'); + $button->first()->click(); + $this->waitForAjax($client); + + $this->assertSame([5, 2], $this->countRowsAndColumns($client->getCrawler())); + $this->assertSame([1, 3], $this->getPagination($client->getCrawler())); + $this->assertSame(['first_name', Crud::ASC], $this->getSort($client->getCrawler())); + $this->assertSame('AudeJavel', $this->getFirstUsername($client->getCrawler())); + + return $client; + } + + /** + * @depends testResetSettings + */ + public function testPersistentValuesAfterResetSettings(Client $client): Client + { + $client->request('GET', '/user/'); + + $this->assertSame([5, 2], $this->countRowsAndColumns($client->getCrawler())); + $this->assertSame([1, 3], $this->getPagination($client->getCrawler())); + $this->assertSame(['first_name', Crud::ASC], $this->getSort($client->getCrawler())); + $this->assertSame('AudeJavel', $this->getFirstUsername($client->getCrawler())); + + return $client; + } + + protected function countRowsAndColumns(Crawler $crawler): array + { + $rows = $crawler->filterXPath('//table[@class="result"]/tbody/tr'); + $countRows = \count($rows); + $columns = $rows->first()->filterXPath('td'); + $countColumns = \count($columns); + + return [$countRows, $countColumns]; + } + + protected function getPagination(Crawler $crawler): array + { + $infos = $crawler->filterXPath('//div[@class="info-pagination"]')->text(); + + preg_match('/^Results \d+\-\d+ \- Page (\d+)\/(\d+)$/', $infos, $groups); + + $page = (int) $groups[1]; + $countPages = (int) $groups[2]; + + return [$page, $countPages]; + } + + protected function getSort(Crawler $crawler): array + { + $imageSort = $crawler->filterXPath('//table[@class="result"]/thead/tr/th/img'); + if (0 === count($imageSort)) { + return []; + } + $imageSort = $imageSort->first(); + + switch ($imageSort->getAttribute('alt')) { + case '^': + $sense = Crud::DESC; + break; + case 'V': + $sense = Crud::ASC; + break; + default: + throw new \Exception('Bad sense'); + } + + $column = $imageSort->filterXPath('ancestor::th')->last(); + + return [$column->text(), $sense]; + } + + protected function getFirstUsername(Crawler $crawler): ?string + { + $rows = $crawler->filterXPath('//table[@class="result"]/tbody/tr'); + if (0 === \count($rows)) { + return null; + } + + return $rows->first()->getAttribute('data-username'); + } + + protected function waitForAjax(Client $client, int $timeout = 5): void + { + $driver = $client->getWebDriver(); + + $driver->wait($timeout, 500)->until(static function ($driver) { + return !$driver->executeScript('return (typeof jQuery !== "undefined" && jQuery.active);'); + }); + } +} diff --git a/Tests/bootstrap.php b/Tests/bootstrap.php new file mode 100644 index 0000000..a5029b4 --- /dev/null +++ b/Tests/bootstrap.php @@ -0,0 +1,33 @@ +boot(); + + $application = new \Symfony\Bundle\FrameworkBundle\Console\Application($kernel); + $application->setAutoExit(false); + + $application->run(new \Symfony\Component\Console\Input\ArrayInput([ + 'command' => 'doctrine:database:drop', + '--force' => true, + ])); + + $application->run(new \Symfony\Component\Console\Input\ArrayInput([ + 'command' => 'doctrine:database:create', + ])); + + $application->run(new \Symfony\Component\Console\Input\ArrayInput([ + 'command' => 'doctrine:schema:update', + '--force' => true, + ])); + + $application->run(new \Symfony\Component\Console\Input\ArrayInput([ + 'command' => 'doctrine:fixtures:load', + '--no-interaction' => true, + ])); +} + +bootstrap(); diff --git a/composer.json b/composer.json index afb3e68..98da978 100644 --- a/composer.json +++ b/composer.json @@ -10,30 +10,42 @@ } ], "require": { + "php": "^7.2", "doctrine/common": "*", - "doctrine/doctrine-bundle": "^1.12|^2.0", - "doctrine/orm": "~2.3", - "doctrine/persistence": "*", - "ecommit/javascript-bundle": "2.5.*@dev", - "ecommit/util-bundle": "2.5.*@dev", - "symfony/config": "^4.2|^5.0", - "symfony/dependency-injection": "^4.2|^5.0", - "symfony/doctrine-bridge": "^4.2|^5.0", - "symfony/form": "^4.2|^5.0", - "symfony/framework-bundle": "^4.2|^5.0", - "symfony/http-foundation": "^4.2|^5.0", - "symfony/http-kernel": "^4.2|^5.0", - "symfony/options-resolver": "^4.2|^5.0", - "symfony/property-access": "^4.2|^5.0", - "symfony/security-bundle": "^4.2|^5.0", - "symfony/translation": "^4.2|^5.0", - "symfony/twig-bundle": "^4.2|^5.0", - "symfony/validator": "^4.2|^5.0", - "twig/extensions": "^1.0" + "doctrine/doctrine-bundle": "^1.12.3|^2.0.3", + "doctrine/orm": "^2.6.3", + "doctrine/persistence": "^1.3", + "ecommit/javascript-bundle": "^2.5.1", + "ecommit/util-bundle": "^2.5.1", + "symfony/asset": "^4.4|^5.0", + "symfony/config": "^4.4|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/doctrine-bridge": "^4.4|^5.0", + "symfony/form": "^4.4|^5.0", + "symfony/framework-bundle": "^4.4|^5.0", + "symfony/http-foundation": "^4.4|^5.0", + "symfony/http-kernel": "^4.4|^5.0", + "symfony/options-resolver": "^4.4|^5.0", + "symfony/property-access": "^4.4|^5.0", + "symfony/routing": "^4.4|^5.0", + "symfony/security-bundle": "^4.4|^5.0", + "symfony/translation": "^4.4|^5.0", + "symfony/twig-bundle": "^4.4|^5.0", + "symfony/validator": "^4.4|^5.0", + "twig/extensions": "^1.0", + "twig/twig": "^2.12.0|^3.0" + }, + "require-dev": { + "doctrine/doctrine-fixtures-bundle": "^3.3", + "phpunit/phpunit": "^8.4", + "symfony/panther": "^0.7.1" }, "autoload": { "psr-4": { "Ecommit\\CrudBundle\\": "" } }, + "config": { + "sort-packages": true + }, "extra": { "branch-alias": { "dev-master": "3.0.x-dev" diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..b3cde74 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + Tests + + + From 99f7ede3ce360208f1ca2367e053368f10405d23 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Tue, 5 May 2020 13:05:16 +0200 Subject: [PATCH 005/264] Move all files to src and tests --- .gitignore | 4 ++-- composer.json | 5 ++++- phpunit.xml.dist | 6 +++--- .../Controller}/AbstractCrudController.php | 0 .../Controller}/CrudControllerTrait.php | 0 {Crud => src/Crud}/Crud.php | 0 {Crud => src/Crud}/CrudColumn.php | 0 {Crud => src/Crud}/CrudFactory.php | 0 {Crud => src/Crud}/CrudSession.php | 0 {Crud => src/Crud}/QueryBuilderInterface.php | 0 .../Crud}/QueryBuilderParameterInterface.php | 0 {Crud => src/Crud}/Rest/RestQueryBuilder.php | 0 .../Crud}/Rest/RestQueryBuilderParameter.php | 0 .../DependencyInjection}/Configuration.php | 0 .../DependencyInjection}/EcommitCrudExtension.php | 0 .../DoctrineExtension}/Paginate.php | 0 .../DoctrineExtension}/QueryBuilderFilter.php | 0 EcommitCrudBundle.php => src/EcommitCrudBundle.php | 0 {Entity => src/Entity}/UserCrudInterface.php | 0 {Entity => src/Entity}/UserCrudSettings.php | 0 .../EventListener}/MappingEntities.php | 0 {Form => src/Form}/Filter/AbstractFieldFilter.php | 0 {Form => src/Form}/Filter/FieldFilterBoolean.php | 0 {Form => src/Form}/Filter/FieldFilterChoice.php | 0 {Form => src/Form}/Filter/FieldFilterDate.php | 0 .../Form}/Filter/FieldFilterDoctrineInterface.php | 0 {Form => src/Form}/Filter/FieldFilterEmpty.php | 0 {Form => src/Form}/Filter/FieldFilterEntity.php | 0 {Form => src/Form}/Filter/FieldFilterInteger.php | 0 .../FieldFilterJqueryAutocompleteEntityAjax.php | 0 {Form => src/Form}/Filter/FieldFilterNumber.php | 0 .../Form}/Filter/FieldFilterSelect2Choice.php | 0 .../Form}/Filter/FieldFilterSelect2Country.php | 0 .../Form}/Filter/FieldFilterSelect2Entity.php | 0 .../Form}/Filter/FieldFilterSelect2EntityAjax.php | 0 {Form => src/Form}/Filter/FieldFilterText.php | 0 .../Filter/FieldFilterTokenInputEntitiesAjax.php | 0 .../Form}/Searcher/AbstractFormSearcher.php | 0 {Form => src/Form}/Type/DisplaySettingsType.php | 0 {Form => src/Form}/Type/FormSearchType.php | 0 {Helper => src/Helper}/CrudHelper.php | 0 .../Paginator}/AbstractDoctrinePaginator.php | 0 {Paginator => src/Paginator}/AbstractPaginator.php | 0 {Paginator => src/Paginator}/ArrayPaginator.php | 0 .../Paginator}/DoctrineDBALPaginator.php | 0 .../Paginator}/DoctrineORMPaginator.php | 0 {Resources => src/Resources}/config/services.xml | 0 {Resources => src/Resources}/doc/config.sample.yml | 0 .../Resources}/public/images/i16/LICENSE | 0 .../Resources}/public/images/i16/list.png | Bin .../public/images/i16/resultset_first.png | Bin .../Resources}/public/images/i16/resultset_last.png | Bin .../Resources}/public/images/i16/resultset_next.png | Bin .../public/images/i16/resultset_previous.png | Bin .../Resources}/public/images/i16/sort_decrease.png | Bin .../Resources}/public/images/i16/sort_incr.png | Bin .../Resources}/public/js/scrollToFirstMessage.js | 0 .../Resources}/translations/messages.en.yml | 0 .../Resources}/translations/messages.fr.yml | 0 .../Resources}/views/Crud/double_search.html.twig | 0 .../views/Crud/form_settings_modal.html.twig | 0 .../Crud/form_settings_modal_bootstrap.html.twig | 0 .../views/Crud/form_settings_nomodal.html.twig | 0 .../Crud/form_settings_nomodal_bootstrap.html.twig | 0 .../Form/bootstrap_3_horizontal_layout.html.twig | 0 .../views/Form/bootstrap_3_layout.html.twig | 0 .../Resources}/views/Form/div_layout.html.twig | 0 {Twig => src/Twig}/CrudExtension.php | 0 {Tests => tests}/App/Controller/UserController.php | 0 .../App/DataFixtures/TestUserFixtures.php | 0 {Tests => tests}/App/Entity/TestUser.php | 0 {Tests => tests}/App/Form/Searcher/UserSearcher.php | 0 {Tests => tests}/App/Kernel.php | 0 {Tests => tests}/App/config/bootstrap.php | 0 {Tests => tests}/App/config/doctrine.yaml | 0 {Tests => tests}/App/config/framework.yaml | 0 {Tests => tests}/App/config/routes.yaml | 0 {Tests => tests}/App/config/security.yaml | 0 {Tests => tests}/App/config/services.yaml | 0 {Tests => tests}/App/public/index.php | 0 {Tests => tests}/App/templates/layout.html.twig | 0 {Tests => tests}/App/templates/user/index.html.twig | 0 {Tests => tests}/App/templates/user/list.html.twig | 0 .../App/templates/user/search.html.twig | 0 .../Controller/TestCrudControllerTest.php | 0 {Tests => tests}/bootstrap.php | 0 86 files changed, 9 insertions(+), 6 deletions(-) rename {Controller => src/Controller}/AbstractCrudController.php (100%) rename {Controller => src/Controller}/CrudControllerTrait.php (100%) rename {Crud => src/Crud}/Crud.php (100%) rename {Crud => src/Crud}/CrudColumn.php (100%) rename {Crud => src/Crud}/CrudFactory.php (100%) rename {Crud => src/Crud}/CrudSession.php (100%) rename {Crud => src/Crud}/QueryBuilderInterface.php (100%) rename {Crud => src/Crud}/QueryBuilderParameterInterface.php (100%) rename {Crud => src/Crud}/Rest/RestQueryBuilder.php (100%) rename {Crud => src/Crud}/Rest/RestQueryBuilderParameter.php (100%) rename {DependencyInjection => src/DependencyInjection}/Configuration.php (100%) rename {DependencyInjection => src/DependencyInjection}/EcommitCrudExtension.php (100%) rename {DoctrineExtension => src/DoctrineExtension}/Paginate.php (100%) rename {DoctrineExtension => src/DoctrineExtension}/QueryBuilderFilter.php (100%) rename EcommitCrudBundle.php => src/EcommitCrudBundle.php (100%) rename {Entity => src/Entity}/UserCrudInterface.php (100%) rename {Entity => src/Entity}/UserCrudSettings.php (100%) rename {EventListener => src/EventListener}/MappingEntities.php (100%) rename {Form => src/Form}/Filter/AbstractFieldFilter.php (100%) rename {Form => src/Form}/Filter/FieldFilterBoolean.php (100%) rename {Form => src/Form}/Filter/FieldFilterChoice.php (100%) rename {Form => src/Form}/Filter/FieldFilterDate.php (100%) rename {Form => src/Form}/Filter/FieldFilterDoctrineInterface.php (100%) rename {Form => src/Form}/Filter/FieldFilterEmpty.php (100%) rename {Form => src/Form}/Filter/FieldFilterEntity.php (100%) rename {Form => src/Form}/Filter/FieldFilterInteger.php (100%) rename {Form => src/Form}/Filter/FieldFilterJqueryAutocompleteEntityAjax.php (100%) rename {Form => src/Form}/Filter/FieldFilterNumber.php (100%) rename {Form => src/Form}/Filter/FieldFilterSelect2Choice.php (100%) rename {Form => src/Form}/Filter/FieldFilterSelect2Country.php (100%) rename {Form => src/Form}/Filter/FieldFilterSelect2Entity.php (100%) rename {Form => src/Form}/Filter/FieldFilterSelect2EntityAjax.php (100%) rename {Form => src/Form}/Filter/FieldFilterText.php (100%) rename {Form => src/Form}/Filter/FieldFilterTokenInputEntitiesAjax.php (100%) rename {Form => src/Form}/Searcher/AbstractFormSearcher.php (100%) rename {Form => src/Form}/Type/DisplaySettingsType.php (100%) rename {Form => src/Form}/Type/FormSearchType.php (100%) rename {Helper => src/Helper}/CrudHelper.php (100%) rename {Paginator => src/Paginator}/AbstractDoctrinePaginator.php (100%) rename {Paginator => src/Paginator}/AbstractPaginator.php (100%) rename {Paginator => src/Paginator}/ArrayPaginator.php (100%) rename {Paginator => src/Paginator}/DoctrineDBALPaginator.php (100%) rename {Paginator => src/Paginator}/DoctrineORMPaginator.php (100%) rename {Resources => src/Resources}/config/services.xml (100%) rename {Resources => src/Resources}/doc/config.sample.yml (100%) rename {Resources => src/Resources}/public/images/i16/LICENSE (100%) rename {Resources => src/Resources}/public/images/i16/list.png (100%) rename {Resources => src/Resources}/public/images/i16/resultset_first.png (100%) rename {Resources => src/Resources}/public/images/i16/resultset_last.png (100%) rename {Resources => src/Resources}/public/images/i16/resultset_next.png (100%) rename {Resources => src/Resources}/public/images/i16/resultset_previous.png (100%) rename {Resources => src/Resources}/public/images/i16/sort_decrease.png (100%) rename {Resources => src/Resources}/public/images/i16/sort_incr.png (100%) rename {Resources => src/Resources}/public/js/scrollToFirstMessage.js (100%) rename {Resources => src/Resources}/translations/messages.en.yml (100%) rename {Resources => src/Resources}/translations/messages.fr.yml (100%) rename {Resources => src/Resources}/views/Crud/double_search.html.twig (100%) rename {Resources => src/Resources}/views/Crud/form_settings_modal.html.twig (100%) rename {Resources => src/Resources}/views/Crud/form_settings_modal_bootstrap.html.twig (100%) rename {Resources => src/Resources}/views/Crud/form_settings_nomodal.html.twig (100%) rename {Resources => src/Resources}/views/Crud/form_settings_nomodal_bootstrap.html.twig (100%) rename {Resources => src/Resources}/views/Form/bootstrap_3_horizontal_layout.html.twig (100%) rename {Resources => src/Resources}/views/Form/bootstrap_3_layout.html.twig (100%) rename {Resources => src/Resources}/views/Form/div_layout.html.twig (100%) rename {Twig => src/Twig}/CrudExtension.php (100%) rename {Tests => tests}/App/Controller/UserController.php (100%) rename {Tests => tests}/App/DataFixtures/TestUserFixtures.php (100%) rename {Tests => tests}/App/Entity/TestUser.php (100%) rename {Tests => tests}/App/Form/Searcher/UserSearcher.php (100%) rename {Tests => tests}/App/Kernel.php (100%) rename {Tests => tests}/App/config/bootstrap.php (100%) rename {Tests => tests}/App/config/doctrine.yaml (100%) rename {Tests => tests}/App/config/framework.yaml (100%) rename {Tests => tests}/App/config/routes.yaml (100%) rename {Tests => tests}/App/config/security.yaml (100%) rename {Tests => tests}/App/config/services.yaml (100%) rename {Tests => tests}/App/public/index.php (100%) rename {Tests => tests}/App/templates/layout.html.twig (100%) rename {Tests => tests}/App/templates/user/index.html.twig (100%) rename {Tests => tests}/App/templates/user/list.html.twig (100%) rename {Tests => tests}/App/templates/user/search.html.twig (100%) rename {Tests => tests}/Functional/Controller/TestCrudControllerTest.php (100%) rename {Tests => tests}/bootstrap.php (100%) diff --git a/.gitignore b/.gitignore index d7c9146..aedc937 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ .php_cs.cache .phpunit.result.cache composer.lock -Tests/App/var/ -vendor/ +/tests/App/var/ +/vendor/ diff --git a/composer.json b/composer.json index 98da978..b98f876 100644 --- a/composer.json +++ b/composer.json @@ -41,7 +41,10 @@ "symfony/panther": "^0.7.1" }, "autoload": { - "psr-4": { "Ecommit\\CrudBundle\\": "" } + "psr-4": { "Ecommit\\CrudBundle\\": "src/" } + }, + "autoload-dev": { + "psr-4": { "Ecommit\\CrudBundle\\Tests\\": "tests/" } }, "config": { "sort-packages": true diff --git a/phpunit.xml.dist b/phpunit.xml.dist index b3cde74..513eb68 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -6,12 +6,12 @@ backupGlobals="false" colors="true" beStrictAboutTestsThatDoNotTestAnything="false" - bootstrap="Tests/bootstrap.php" + bootstrap="tests/bootstrap.php" > - + @@ -20,7 +20,7 @@ - Tests + tests diff --git a/Controller/AbstractCrudController.php b/src/Controller/AbstractCrudController.php similarity index 100% rename from Controller/AbstractCrudController.php rename to src/Controller/AbstractCrudController.php diff --git a/Controller/CrudControllerTrait.php b/src/Controller/CrudControllerTrait.php similarity index 100% rename from Controller/CrudControllerTrait.php rename to src/Controller/CrudControllerTrait.php diff --git a/Crud/Crud.php b/src/Crud/Crud.php similarity index 100% rename from Crud/Crud.php rename to src/Crud/Crud.php diff --git a/Crud/CrudColumn.php b/src/Crud/CrudColumn.php similarity index 100% rename from Crud/CrudColumn.php rename to src/Crud/CrudColumn.php diff --git a/Crud/CrudFactory.php b/src/Crud/CrudFactory.php similarity index 100% rename from Crud/CrudFactory.php rename to src/Crud/CrudFactory.php diff --git a/Crud/CrudSession.php b/src/Crud/CrudSession.php similarity index 100% rename from Crud/CrudSession.php rename to src/Crud/CrudSession.php diff --git a/Crud/QueryBuilderInterface.php b/src/Crud/QueryBuilderInterface.php similarity index 100% rename from Crud/QueryBuilderInterface.php rename to src/Crud/QueryBuilderInterface.php diff --git a/Crud/QueryBuilderParameterInterface.php b/src/Crud/QueryBuilderParameterInterface.php similarity index 100% rename from Crud/QueryBuilderParameterInterface.php rename to src/Crud/QueryBuilderParameterInterface.php diff --git a/Crud/Rest/RestQueryBuilder.php b/src/Crud/Rest/RestQueryBuilder.php similarity index 100% rename from Crud/Rest/RestQueryBuilder.php rename to src/Crud/Rest/RestQueryBuilder.php diff --git a/Crud/Rest/RestQueryBuilderParameter.php b/src/Crud/Rest/RestQueryBuilderParameter.php similarity index 100% rename from Crud/Rest/RestQueryBuilderParameter.php rename to src/Crud/Rest/RestQueryBuilderParameter.php diff --git a/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php similarity index 100% rename from DependencyInjection/Configuration.php rename to src/DependencyInjection/Configuration.php diff --git a/DependencyInjection/EcommitCrudExtension.php b/src/DependencyInjection/EcommitCrudExtension.php similarity index 100% rename from DependencyInjection/EcommitCrudExtension.php rename to src/DependencyInjection/EcommitCrudExtension.php diff --git a/DoctrineExtension/Paginate.php b/src/DoctrineExtension/Paginate.php similarity index 100% rename from DoctrineExtension/Paginate.php rename to src/DoctrineExtension/Paginate.php diff --git a/DoctrineExtension/QueryBuilderFilter.php b/src/DoctrineExtension/QueryBuilderFilter.php similarity index 100% rename from DoctrineExtension/QueryBuilderFilter.php rename to src/DoctrineExtension/QueryBuilderFilter.php diff --git a/EcommitCrudBundle.php b/src/EcommitCrudBundle.php similarity index 100% rename from EcommitCrudBundle.php rename to src/EcommitCrudBundle.php diff --git a/Entity/UserCrudInterface.php b/src/Entity/UserCrudInterface.php similarity index 100% rename from Entity/UserCrudInterface.php rename to src/Entity/UserCrudInterface.php diff --git a/Entity/UserCrudSettings.php b/src/Entity/UserCrudSettings.php similarity index 100% rename from Entity/UserCrudSettings.php rename to src/Entity/UserCrudSettings.php diff --git a/EventListener/MappingEntities.php b/src/EventListener/MappingEntities.php similarity index 100% rename from EventListener/MappingEntities.php rename to src/EventListener/MappingEntities.php diff --git a/Form/Filter/AbstractFieldFilter.php b/src/Form/Filter/AbstractFieldFilter.php similarity index 100% rename from Form/Filter/AbstractFieldFilter.php rename to src/Form/Filter/AbstractFieldFilter.php diff --git a/Form/Filter/FieldFilterBoolean.php b/src/Form/Filter/FieldFilterBoolean.php similarity index 100% rename from Form/Filter/FieldFilterBoolean.php rename to src/Form/Filter/FieldFilterBoolean.php diff --git a/Form/Filter/FieldFilterChoice.php b/src/Form/Filter/FieldFilterChoice.php similarity index 100% rename from Form/Filter/FieldFilterChoice.php rename to src/Form/Filter/FieldFilterChoice.php diff --git a/Form/Filter/FieldFilterDate.php b/src/Form/Filter/FieldFilterDate.php similarity index 100% rename from Form/Filter/FieldFilterDate.php rename to src/Form/Filter/FieldFilterDate.php diff --git a/Form/Filter/FieldFilterDoctrineInterface.php b/src/Form/Filter/FieldFilterDoctrineInterface.php similarity index 100% rename from Form/Filter/FieldFilterDoctrineInterface.php rename to src/Form/Filter/FieldFilterDoctrineInterface.php diff --git a/Form/Filter/FieldFilterEmpty.php b/src/Form/Filter/FieldFilterEmpty.php similarity index 100% rename from Form/Filter/FieldFilterEmpty.php rename to src/Form/Filter/FieldFilterEmpty.php diff --git a/Form/Filter/FieldFilterEntity.php b/src/Form/Filter/FieldFilterEntity.php similarity index 100% rename from Form/Filter/FieldFilterEntity.php rename to src/Form/Filter/FieldFilterEntity.php diff --git a/Form/Filter/FieldFilterInteger.php b/src/Form/Filter/FieldFilterInteger.php similarity index 100% rename from Form/Filter/FieldFilterInteger.php rename to src/Form/Filter/FieldFilterInteger.php diff --git a/Form/Filter/FieldFilterJqueryAutocompleteEntityAjax.php b/src/Form/Filter/FieldFilterJqueryAutocompleteEntityAjax.php similarity index 100% rename from Form/Filter/FieldFilterJqueryAutocompleteEntityAjax.php rename to src/Form/Filter/FieldFilterJqueryAutocompleteEntityAjax.php diff --git a/Form/Filter/FieldFilterNumber.php b/src/Form/Filter/FieldFilterNumber.php similarity index 100% rename from Form/Filter/FieldFilterNumber.php rename to src/Form/Filter/FieldFilterNumber.php diff --git a/Form/Filter/FieldFilterSelect2Choice.php b/src/Form/Filter/FieldFilterSelect2Choice.php similarity index 100% rename from Form/Filter/FieldFilterSelect2Choice.php rename to src/Form/Filter/FieldFilterSelect2Choice.php diff --git a/Form/Filter/FieldFilterSelect2Country.php b/src/Form/Filter/FieldFilterSelect2Country.php similarity index 100% rename from Form/Filter/FieldFilterSelect2Country.php rename to src/Form/Filter/FieldFilterSelect2Country.php diff --git a/Form/Filter/FieldFilterSelect2Entity.php b/src/Form/Filter/FieldFilterSelect2Entity.php similarity index 100% rename from Form/Filter/FieldFilterSelect2Entity.php rename to src/Form/Filter/FieldFilterSelect2Entity.php diff --git a/Form/Filter/FieldFilterSelect2EntityAjax.php b/src/Form/Filter/FieldFilterSelect2EntityAjax.php similarity index 100% rename from Form/Filter/FieldFilterSelect2EntityAjax.php rename to src/Form/Filter/FieldFilterSelect2EntityAjax.php diff --git a/Form/Filter/FieldFilterText.php b/src/Form/Filter/FieldFilterText.php similarity index 100% rename from Form/Filter/FieldFilterText.php rename to src/Form/Filter/FieldFilterText.php diff --git a/Form/Filter/FieldFilterTokenInputEntitiesAjax.php b/src/Form/Filter/FieldFilterTokenInputEntitiesAjax.php similarity index 100% rename from Form/Filter/FieldFilterTokenInputEntitiesAjax.php rename to src/Form/Filter/FieldFilterTokenInputEntitiesAjax.php diff --git a/Form/Searcher/AbstractFormSearcher.php b/src/Form/Searcher/AbstractFormSearcher.php similarity index 100% rename from Form/Searcher/AbstractFormSearcher.php rename to src/Form/Searcher/AbstractFormSearcher.php diff --git a/Form/Type/DisplaySettingsType.php b/src/Form/Type/DisplaySettingsType.php similarity index 100% rename from Form/Type/DisplaySettingsType.php rename to src/Form/Type/DisplaySettingsType.php diff --git a/Form/Type/FormSearchType.php b/src/Form/Type/FormSearchType.php similarity index 100% rename from Form/Type/FormSearchType.php rename to src/Form/Type/FormSearchType.php diff --git a/Helper/CrudHelper.php b/src/Helper/CrudHelper.php similarity index 100% rename from Helper/CrudHelper.php rename to src/Helper/CrudHelper.php diff --git a/Paginator/AbstractDoctrinePaginator.php b/src/Paginator/AbstractDoctrinePaginator.php similarity index 100% rename from Paginator/AbstractDoctrinePaginator.php rename to src/Paginator/AbstractDoctrinePaginator.php diff --git a/Paginator/AbstractPaginator.php b/src/Paginator/AbstractPaginator.php similarity index 100% rename from Paginator/AbstractPaginator.php rename to src/Paginator/AbstractPaginator.php diff --git a/Paginator/ArrayPaginator.php b/src/Paginator/ArrayPaginator.php similarity index 100% rename from Paginator/ArrayPaginator.php rename to src/Paginator/ArrayPaginator.php diff --git a/Paginator/DoctrineDBALPaginator.php b/src/Paginator/DoctrineDBALPaginator.php similarity index 100% rename from Paginator/DoctrineDBALPaginator.php rename to src/Paginator/DoctrineDBALPaginator.php diff --git a/Paginator/DoctrineORMPaginator.php b/src/Paginator/DoctrineORMPaginator.php similarity index 100% rename from Paginator/DoctrineORMPaginator.php rename to src/Paginator/DoctrineORMPaginator.php diff --git a/Resources/config/services.xml b/src/Resources/config/services.xml similarity index 100% rename from Resources/config/services.xml rename to src/Resources/config/services.xml diff --git a/Resources/doc/config.sample.yml b/src/Resources/doc/config.sample.yml similarity index 100% rename from Resources/doc/config.sample.yml rename to src/Resources/doc/config.sample.yml diff --git a/Resources/public/images/i16/LICENSE b/src/Resources/public/images/i16/LICENSE similarity index 100% rename from Resources/public/images/i16/LICENSE rename to src/Resources/public/images/i16/LICENSE diff --git a/Resources/public/images/i16/list.png b/src/Resources/public/images/i16/list.png similarity index 100% rename from Resources/public/images/i16/list.png rename to src/Resources/public/images/i16/list.png diff --git a/Resources/public/images/i16/resultset_first.png b/src/Resources/public/images/i16/resultset_first.png similarity index 100% rename from Resources/public/images/i16/resultset_first.png rename to src/Resources/public/images/i16/resultset_first.png diff --git a/Resources/public/images/i16/resultset_last.png b/src/Resources/public/images/i16/resultset_last.png similarity index 100% rename from Resources/public/images/i16/resultset_last.png rename to src/Resources/public/images/i16/resultset_last.png diff --git a/Resources/public/images/i16/resultset_next.png b/src/Resources/public/images/i16/resultset_next.png similarity index 100% rename from Resources/public/images/i16/resultset_next.png rename to src/Resources/public/images/i16/resultset_next.png diff --git a/Resources/public/images/i16/resultset_previous.png b/src/Resources/public/images/i16/resultset_previous.png similarity index 100% rename from Resources/public/images/i16/resultset_previous.png rename to src/Resources/public/images/i16/resultset_previous.png diff --git a/Resources/public/images/i16/sort_decrease.png b/src/Resources/public/images/i16/sort_decrease.png similarity index 100% rename from Resources/public/images/i16/sort_decrease.png rename to src/Resources/public/images/i16/sort_decrease.png diff --git a/Resources/public/images/i16/sort_incr.png b/src/Resources/public/images/i16/sort_incr.png similarity index 100% rename from Resources/public/images/i16/sort_incr.png rename to src/Resources/public/images/i16/sort_incr.png diff --git a/Resources/public/js/scrollToFirstMessage.js b/src/Resources/public/js/scrollToFirstMessage.js similarity index 100% rename from Resources/public/js/scrollToFirstMessage.js rename to src/Resources/public/js/scrollToFirstMessage.js diff --git a/Resources/translations/messages.en.yml b/src/Resources/translations/messages.en.yml similarity index 100% rename from Resources/translations/messages.en.yml rename to src/Resources/translations/messages.en.yml diff --git a/Resources/translations/messages.fr.yml b/src/Resources/translations/messages.fr.yml similarity index 100% rename from Resources/translations/messages.fr.yml rename to src/Resources/translations/messages.fr.yml diff --git a/Resources/views/Crud/double_search.html.twig b/src/Resources/views/Crud/double_search.html.twig similarity index 100% rename from Resources/views/Crud/double_search.html.twig rename to src/Resources/views/Crud/double_search.html.twig diff --git a/Resources/views/Crud/form_settings_modal.html.twig b/src/Resources/views/Crud/form_settings_modal.html.twig similarity index 100% rename from Resources/views/Crud/form_settings_modal.html.twig rename to src/Resources/views/Crud/form_settings_modal.html.twig diff --git a/Resources/views/Crud/form_settings_modal_bootstrap.html.twig b/src/Resources/views/Crud/form_settings_modal_bootstrap.html.twig similarity index 100% rename from Resources/views/Crud/form_settings_modal_bootstrap.html.twig rename to src/Resources/views/Crud/form_settings_modal_bootstrap.html.twig diff --git a/Resources/views/Crud/form_settings_nomodal.html.twig b/src/Resources/views/Crud/form_settings_nomodal.html.twig similarity index 100% rename from Resources/views/Crud/form_settings_nomodal.html.twig rename to src/Resources/views/Crud/form_settings_nomodal.html.twig diff --git a/Resources/views/Crud/form_settings_nomodal_bootstrap.html.twig b/src/Resources/views/Crud/form_settings_nomodal_bootstrap.html.twig similarity index 100% rename from Resources/views/Crud/form_settings_nomodal_bootstrap.html.twig rename to src/Resources/views/Crud/form_settings_nomodal_bootstrap.html.twig diff --git a/Resources/views/Form/bootstrap_3_horizontal_layout.html.twig b/src/Resources/views/Form/bootstrap_3_horizontal_layout.html.twig similarity index 100% rename from Resources/views/Form/bootstrap_3_horizontal_layout.html.twig rename to src/Resources/views/Form/bootstrap_3_horizontal_layout.html.twig diff --git a/Resources/views/Form/bootstrap_3_layout.html.twig b/src/Resources/views/Form/bootstrap_3_layout.html.twig similarity index 100% rename from Resources/views/Form/bootstrap_3_layout.html.twig rename to src/Resources/views/Form/bootstrap_3_layout.html.twig diff --git a/Resources/views/Form/div_layout.html.twig b/src/Resources/views/Form/div_layout.html.twig similarity index 100% rename from Resources/views/Form/div_layout.html.twig rename to src/Resources/views/Form/div_layout.html.twig diff --git a/Twig/CrudExtension.php b/src/Twig/CrudExtension.php similarity index 100% rename from Twig/CrudExtension.php rename to src/Twig/CrudExtension.php diff --git a/Tests/App/Controller/UserController.php b/tests/App/Controller/UserController.php similarity index 100% rename from Tests/App/Controller/UserController.php rename to tests/App/Controller/UserController.php diff --git a/Tests/App/DataFixtures/TestUserFixtures.php b/tests/App/DataFixtures/TestUserFixtures.php similarity index 100% rename from Tests/App/DataFixtures/TestUserFixtures.php rename to tests/App/DataFixtures/TestUserFixtures.php diff --git a/Tests/App/Entity/TestUser.php b/tests/App/Entity/TestUser.php similarity index 100% rename from Tests/App/Entity/TestUser.php rename to tests/App/Entity/TestUser.php diff --git a/Tests/App/Form/Searcher/UserSearcher.php b/tests/App/Form/Searcher/UserSearcher.php similarity index 100% rename from Tests/App/Form/Searcher/UserSearcher.php rename to tests/App/Form/Searcher/UserSearcher.php diff --git a/Tests/App/Kernel.php b/tests/App/Kernel.php similarity index 100% rename from Tests/App/Kernel.php rename to tests/App/Kernel.php diff --git a/Tests/App/config/bootstrap.php b/tests/App/config/bootstrap.php similarity index 100% rename from Tests/App/config/bootstrap.php rename to tests/App/config/bootstrap.php diff --git a/Tests/App/config/doctrine.yaml b/tests/App/config/doctrine.yaml similarity index 100% rename from Tests/App/config/doctrine.yaml rename to tests/App/config/doctrine.yaml diff --git a/Tests/App/config/framework.yaml b/tests/App/config/framework.yaml similarity index 100% rename from Tests/App/config/framework.yaml rename to tests/App/config/framework.yaml diff --git a/Tests/App/config/routes.yaml b/tests/App/config/routes.yaml similarity index 100% rename from Tests/App/config/routes.yaml rename to tests/App/config/routes.yaml diff --git a/Tests/App/config/security.yaml b/tests/App/config/security.yaml similarity index 100% rename from Tests/App/config/security.yaml rename to tests/App/config/security.yaml diff --git a/Tests/App/config/services.yaml b/tests/App/config/services.yaml similarity index 100% rename from Tests/App/config/services.yaml rename to tests/App/config/services.yaml diff --git a/Tests/App/public/index.php b/tests/App/public/index.php similarity index 100% rename from Tests/App/public/index.php rename to tests/App/public/index.php diff --git a/Tests/App/templates/layout.html.twig b/tests/App/templates/layout.html.twig similarity index 100% rename from Tests/App/templates/layout.html.twig rename to tests/App/templates/layout.html.twig diff --git a/Tests/App/templates/user/index.html.twig b/tests/App/templates/user/index.html.twig similarity index 100% rename from Tests/App/templates/user/index.html.twig rename to tests/App/templates/user/index.html.twig diff --git a/Tests/App/templates/user/list.html.twig b/tests/App/templates/user/list.html.twig similarity index 100% rename from Tests/App/templates/user/list.html.twig rename to tests/App/templates/user/list.html.twig diff --git a/Tests/App/templates/user/search.html.twig b/tests/App/templates/user/search.html.twig similarity index 100% rename from Tests/App/templates/user/search.html.twig rename to tests/App/templates/user/search.html.twig diff --git a/Tests/Functional/Controller/TestCrudControllerTest.php b/tests/Functional/Controller/TestCrudControllerTest.php similarity index 100% rename from Tests/Functional/Controller/TestCrudControllerTest.php rename to tests/Functional/Controller/TestCrudControllerTest.php diff --git a/Tests/bootstrap.php b/tests/bootstrap.php similarity index 100% rename from Tests/bootstrap.php rename to tests/bootstrap.php From e31674ba4f98b63d90ebd5f9cd585fd6c507a6c5 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Tue, 5 May 2020 13:10:48 +0200 Subject: [PATCH 006/264] Insight badge replaced by Travis badge --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index bf05412..47ddce5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ -EcommitCrudBundle -================== +# EcommitCrudBundle + + +[![Build Status](https://travis-ci.org/e-commit/EcommitCrudBundle.svg?branch=master)](https://travis-ci.org/e-commit/EcommitCrudBundle) -[![SensioLabsInsight](https://insight.sensiolabs.com/projects/ee0677db-2ad9-4248-9b61-ab00020317b5/big.png)](https://insight.sensiolabs.com/projects/ee0677db-2ad9-4248-9b61-ab00020317b5) **WARNING: This branch is under development. Not use in production. You can use stable versions or 2.5 branch.** From 40c3019a3ec59c9af09867fb76151be143245116 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Tue, 5 May 2020 14:34:49 +0200 Subject: [PATCH 007/264] Fix CS --- .php_cs.dist | 42 ++ .travis.yml | 1 + composer.json | 1 + src/Controller/AbstractCrudController.php | 3 +- src/Controller/CrudControllerTrait.php | 29 +- src/Crud/Crud.php | 369 +++++++++--------- src/Crud/CrudColumn.php | 22 +- src/Crud/CrudFactory.php | 6 +- src/Crud/CrudSession.php | 16 +- src/Crud/QueryBuilderInterface.php | 8 +- src/Crud/QueryBuilderParameterInterface.php | 5 +- src/Crud/Rest/RestQueryBuilder.php | 40 +- src/Crud/Rest/RestQueryBuilderParameter.php | 5 +- src/DependencyInjection/Configuration.php | 9 +- .../EcommitCrudExtension.php | 15 +- src/DoctrineExtension/Paginate.php | 63 +-- src/DoctrineExtension/QueryBuilderFilter.php | 147 ++++--- src/EcommitCrudBundle.php | 2 + src/Entity/UserCrudInterface.php | 2 + src/Entity/UserCrudSettings.php | 79 ++-- src/EventListener/MappingEntities.php | 19 +- src/Form/Filter/AbstractFieldFilter.php | 61 ++- src/Form/Filter/FieldFilterBoolean.php | 38 +- src/Form/Filter/FieldFilterChoice.php | 51 +-- src/Form/Filter/FieldFilterDate.php | 88 +++-- .../Filter/FieldFilterDoctrineInterface.php | 3 + src/Form/Filter/FieldFilterEmpty.php | 10 +- src/Form/Filter/FieldFilterEntity.php | 27 +- src/Form/Filter/FieldFilterInteger.php | 32 +- ...ieldFilterJqueryAutocompleteEntityAjax.php | 37 +- src/Form/Filter/FieldFilterNumber.php | 4 +- src/Form/Filter/FieldFilterSelect2Choice.php | 9 +- src/Form/Filter/FieldFilterSelect2Country.php | 9 +- src/Form/Filter/FieldFilterSelect2Entity.php | 7 +- .../Filter/FieldFilterSelect2EntityAjax.php | 23 +- src/Form/Filter/FieldFilterText.php | 30 +- .../FieldFilterTokenInputEntitiesAjax.php | 48 +-- src/Form/Searcher/AbstractFormSearcher.php | 50 +-- src/Form/Type/DisplaySettingsType.php | 30 +- src/Form/Type/FormSearchType.php | 22 +- src/Helper/CrudHelper.php | 304 ++++++++------- src/Paginator/AbstractDoctrinePaginator.php | 30 +- src/Paginator/AbstractPaginator.php | 59 +-- src/Paginator/ArrayPaginator.php | 22 +- src/Paginator/DoctrineDBALPaginator.php | 14 +- src/Paginator/DoctrineORMPaginator.php | 30 +- src/Twig/CrudExtension.php | 117 +++--- tests/App/Controller/UserController.php | 2 + tests/App/DataFixtures/TestUserFixtures.php | 4 +- tests/App/Entity/TestUser.php | 2 + tests/App/Form/Searcher/UserSearcher.php | 2 + tests/App/Kernel.php | 13 +- tests/App/config/bootstrap.php | 11 +- tests/App/public/index.php | 11 + .../Controller/TestCrudControllerTest.php | 6 +- tests/bootstrap.php | 11 + 56 files changed, 1158 insertions(+), 942 deletions(-) create mode 100644 .php_cs.dist diff --git a/.php_cs.dist b/.php_cs.dist new file mode 100644 index 0000000..b07196c --- /dev/null +++ b/.php_cs.dist @@ -0,0 +1,42 @@ + + +For the full copyright and license information, please view the LICENSE +file that was distributed with this source code. +COMMENT; + +$finder = PhpCsFixer\Finder::create() + ->in(__DIR__) + ->exclude('tests/App/var') +; + +return PhpCsFixer\Config::create() + ->setRiskyAllowed(true) + ->setRules([ + '@Symfony' => true, + '@Symfony:risky' => true, + '@DoctrineAnnotation' => true, + '@PHP56Migration' => true, + '@PHP56Migration:risky' => true, + '@PHP71Migration' => true, + '@PHP71Migration:risky' => true, + '@PHP73Migration' => true, + 'array_syntax' => ['syntax' => 'short'], + 'fopen_flags' => true, + 'header_comment' => ['header' => $fileHeaderComment, 'separate' => 'both'], + 'linebreak_after_opening_tag' => true, + 'mb_str_functions' => true, + 'no_php4_constructor' => true, + 'no_unreachable_default_argument_value' => true, + 'no_useless_else' => true, + 'no_useless_return' => true, + 'ordered_imports' => true, + 'phpdoc_order' => true, + 'protected_to_private' => false, + ]) + ->setFinder($finder) +; diff --git a/.travis.yml b/.travis.yml index af268da..d43d51d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,3 +16,4 @@ install: script: - php vendor/phpunit/phpunit/phpunit + - php vendor/friendsofphp/php-cs-fixer/php-cs-fixer fix --diff --dry-run -v diff --git a/composer.json b/composer.json index b98f876..e7a45d9 100644 --- a/composer.json +++ b/composer.json @@ -37,6 +37,7 @@ }, "require-dev": { "doctrine/doctrine-fixtures-bundle": "^3.3", + "friendsofphp/php-cs-fixer": "^2.16.1", "phpunit/phpunit": "^8.4", "symfony/panther": "^0.7.1" }, diff --git a/src/Controller/AbstractCrudController.php b/src/Controller/AbstractCrudController.php index 5118a90..c437d26 100644 --- a/src/Controller/AbstractCrudController.php +++ b/src/Controller/AbstractCrudController.php @@ -1,5 +1,7 @@ prepareList(); - return $this->renderCrud($this->getTemplateName('index'), \array_merge($data, array('crud' => $this->cm))); + return $this->renderCrud($this->getTemplateName('index'), array_merge($data, ['crud' => $this->cm])); } protected function prepareList() @@ -51,33 +54,33 @@ protected function prepareList() } /** - * Configures and returns the CRUD + * Configures and returns the CRUD. * * @return Crud */ abstract protected function configCrud(); /** - * * @return array */ protected function addDataAfterBuildQuery() { - return array(); + return []; } - protected function beforeBuildQuery() + protected function beforeBuildQuery(): void { } - protected function afterBuildQuery() + protected function afterBuildQuery(): void { } /** - * Returns template for action + * Returns template for action. * * @param string $action Action + * * @return string */ abstract protected function getTemplateName($action); @@ -90,7 +93,7 @@ public function autoAjaxListAction() } $data = $this->prepareList(); - return $this->renderCrud($this->getTemplateName('list'), \array_merge($data, array('crud' => $this->cm))); + return $this->renderCrud($this->getTemplateName('list'), array_merge($data, ['crud' => $this->cm])); } public function autoAjaxSearchAction() @@ -102,18 +105,18 @@ public function autoAjaxSearchAction() $data = $this->processSearch(); $renderSearch = $this->renderCrudView( $this->getTemplateName('search'), - \array_merge($data, array('crud' => $this->cm)) + array_merge($data, ['crud' => $this->cm]) ); - $renderList = $this->renderCrudView($this->getTemplateName('list'), \array_merge($data, array('crud' => $this->cm))); + $renderList = $this->renderCrudView($this->getTemplateName('list'), array_merge($data, ['crud' => $this->cm])); return $this->renderCrud( '@EcommitCrud/Crud/double_search.html.twig', - array( + [ 'id_search' => $this->cm->getDivIdSearch(), 'id_list' => $this->cm->getDivIdList(), 'render_search' => $renderSearch, - 'render_list' => $renderList - ) + 'render_list' => $renderList, + ] ); } diff --git a/src/Crud/Crud.php b/src/Crud/Crud.php index 0dd2238..9825778 100644 --- a/src/Crud/Crud.php +++ b/src/Crud/Crud.php @@ -1,5 +1,7 @@ sessionName = $sessionName; @@ -132,32 +135,33 @@ public function __construct( } /** - * Add a column inside the crud + * Add a column inside the crud. + * + * @param string $id Column id (used everywhere inside the crud) + * @param string $alias Column SQL alias + * @param string $label Column label (used in the header table) + * @param array $options Options: + * * sortable: If the column is sortable (Default: true) + * * default_displayed: If the column is displayed, by default (Default: true) + * * alias_search: Column SQL alias, used during searchs. If null, $alias is used. + * * alias_sort: Column(s) SQL alias (string or array of strings), used during sorting. If null, $alias is used. * - * @param string $id Column id (used everywhere inside the crud) - * @param string $alias Column SQL alias - * @param string $label Column label (used in the header table) - * @param array $options Options: - * * sortable: If the column is sortable (Default: true) - * * default_displayed: If the column is displayed, by default (Default: true) - * * alias_search: Column SQL alias, used during searchs. If null, $alias is used. - * * alias_sort: Column(s) SQL alias (string or array of strings), used during sorting. If null, $alias is used. * @return Crud */ - public function addColumn($id, $alias, $label, $options = array()) + public function addColumn($id, $alias, $label, $options = []) { - if (\strlen($id) > 30) { + if (\mb_strlen($id) > 30) { throw new \Exception('Column id is too long'); } $resolver = new OptionsResolver(); $resolver->setDefaults( - array( + [ 'sortable' => true, 'default_displayed' => true, 'alias_search' => null, 'alias_sort' => null, - ) + ] ); $resolver->setAllowedTypes('sortable', 'bool'); $resolver->setAllowedTypes('default_displayed', 'bool'); @@ -178,10 +182,11 @@ public function addColumn($id, $alias, $label, $options = array()) } /** - * Add a virtual column inside the crud + * Add a virtual column inside the crud. + * + * @param string $id Column id (used everywhere inside the crud) + * @param string $aliasSearch column SQL alias, used during searchs * - * @param string $id Column id (used everywhere inside the crud) - * @param string $aliasSearch Column SQL alias, used during searchs. * @return Crud */ public function addVirtualColumn($id, $aliasSearch) @@ -193,7 +198,7 @@ public function addVirtualColumn($id, $aliasSearch) } /** - * Gets the query builder + * Gets the query builder. * * @return QueryBuilder */ @@ -203,9 +208,10 @@ public function getQueryBuilder() } /** - * Sets the query builder + * Sets the query builder. * * @param QueryBuilder $queryBuilder + * * @return Crud */ public function setQueryBuilder($queryBuilder) @@ -221,7 +227,7 @@ public function setQueryBuilder($queryBuilder) } /** - * Returns available results per page + * Returns available results per page. * * @return array */ @@ -231,13 +237,13 @@ public function getAvailableResultsPerPage() } /** - * Sets available results per page + * Sets available results per page. * - * @param array $availableResultsPerPage * @param int $defaultValue + * * @return Crud */ - public function setAvailableResultsPerPage(Array $availableResultsPerPage, $defaultValue) + public function setAvailableResultsPerPage(array $availableResultsPerPage, $defaultValue) { $this->availableResultsPerPage = $availableResultsPerPage; $this->defaultResultsPerPage = $defaultValue; @@ -246,10 +252,11 @@ public function setAvailableResultsPerPage(Array $availableResultsPerPage, $defa } /** - * Set the default sort + * Set the default sort. + * + * @param string $sort Column id + * @param const $sense Sense (Crud::ASC / Crud::DESC) * - * @param string $sort Column id - * @param const $sense Sense (Crud::ASC / Crud::DESC) * @return Crud */ public function setDefaultSort($sort, $sense) @@ -261,11 +268,12 @@ public function setDefaultSort($sort, $sense) } /** - * Set the default personalized sort + * Set the default personalized sort. * * @param array $criterias Criterias : - * If key is defined: Key = Sort Value = Sense - * If key is not defined: Value = Sort + * If key is defined: Key = Sort Value = Sense + * If key is not defined: Value = Sort + * * @return Crud */ public function setDefaultPersonalizedSort(array $criterias) @@ -279,13 +287,14 @@ public function setDefaultPersonalizedSort(array $criterias) } /** - * Sets the list route + * Sets the list route. * * @param string $routeName - * @param array $parameters + * @param array $parameters + * * @return Crud */ - public function setRoute($routeName, $parameters = array()) + public function setRoute($routeName, $parameters = []) { $this->routeName = $routeName; $this->routeParams = $parameters; @@ -294,7 +303,7 @@ public function setRoute($routeName, $parameters = array()) } /** - * Returns the route name + * Returns the route name. * * @return string */ @@ -304,7 +313,7 @@ public function getRouteName() } /** - * Returns the route params + * Returns the route params. * * @return array */ @@ -314,26 +323,28 @@ public function getRouteParams() } /** - * Returns the list url + * Returns the list url. * * @param array $parameters Additional parameters + * * @return string */ - public function getUrl($parameters = array()) + public function getUrl($parameters = []) { - $parameters = \array_merge($this->routeParams, $parameters); + $parameters = array_merge($this->routeParams, $parameters); return $this->router->generate($this->routeName, $parameters); } /** - * Sets the search route + * Sets the search route. * * @param string $routeName - * @param array $parameters + * @param array $parameters + * * @return Crud */ - public function setSearchRoute($routeName, $parameters = array()) + public function setSearchRoute($routeName, $parameters = []) { $this->searchRouteName = $routeName; $this->searchRouteParams = $parameters; @@ -342,20 +353,21 @@ public function setSearchRoute($routeName, $parameters = array()) } /** - * Returns the search url + * Returns the search url. * * @param array $parameters Additional parameters + * * @return string */ - public function getSearchUrl($parameters = array()) + public function getSearchUrl($parameters = []) { - $parameters = \array_merge($this->searchRouteParams, $parameters); + $parameters = array_merge($this->searchRouteParams, $parameters); return $this->router->generate($this->searchRouteName, $parameters); } /** - * Enables (or not) the auto build paginator + * Enables (or not) the auto build paginator. * * @param bool|closure|array $value */ @@ -368,7 +380,7 @@ public function setBuildPaginator($value) /* * Use (or not) persistent settings - * + * * @param bool $value */ public function setPersistentSettings($value) @@ -382,14 +394,14 @@ public function setPersistentSettings($value) } /** - * Adds search form + * Adds search form. + * + * @param string|null $type The type of the form. If null, FormSearchType is used + * @param array $options The options * - * @param AbstractFormSearcher $defaultFormSearcherData - * @param null|string $type The type of the form. If null, FormSearchType is used - * @param array $options The options * @return Crud */ - public function createSearcherForm(AbstractFormSearcher $defaultFormSearcherData, $type = null, $options = array()) + public function createSearcherForm(AbstractFormSearcher $defaultFormSearcherData, $type = null, $options = []) { $this->defaultFormSearcherData = $defaultFormSearcherData; $this->initializeFieldsFilter($defaultFormSearcherData); @@ -402,14 +414,12 @@ public function createSearcherForm(AbstractFormSearcher $defaultFormSearcherData } foreach ($defaultFormSearcherData->getFieldsFilter($this->registry) as $field) { if (!($field instanceof \Ecommit\CrudBundle\Form\Filter\AbstractFieldFilter)) { - throw new \Exception( - 'Crud: AbstractFormSearcher: getFieldsFilter() must only returns AbstractFieldFilter implementations' - ); + throw new \Exception('Crud: AbstractFormSearcher: getFieldsFilter() must only returns AbstractFieldFilter implementations'); } $formBuilder = $field->addField($formBuilder); } - //Global + //Global $formBuilder = $defaultFormSearcherData->globalBuildForm($formBuilder); $this->formSearcher = $formBuilder; @@ -417,10 +427,9 @@ public function createSearcherForm(AbstractFormSearcher $defaultFormSearcherData } /** - * Process search form - * + * Process search form. */ - public function processForm() + public function processForm(): void { if (empty($this->defaultFormSearcherData)) { throw new NotFoundHttpException('Crud: Form searcher does not exist'); @@ -429,7 +438,7 @@ public function processForm() if ($this->request->query->has('raz')) { return; } - if ($this->request->getMethod() == 'POST') { + if ('POST' == $this->request->getMethod()) { if ($this->displayResultsOnlyIfSearch) { $this->displayResults = false; } @@ -445,11 +454,11 @@ public function processForm() } /** - * User action: Changes search form values + * User action: Changes search form values. * - * @param Object $value + * @param object $value */ - protected function changeFilterValues($value) + protected function changeFilterValues($value): void { if (empty($this->defaultFormSearcherData)) { return; @@ -462,16 +471,16 @@ protected function changeFilterValues($value) } /** - * User action: Changes page number + * User action: Changes page number. * * @param string $value Page number */ - protected function changePage($value) + protected function changePage($value): void { if (!is_scalar($value)) { $value = 1; } - $value = \intval($value); + $value = (int) $value; if ($value > 1000000000000) { $value = 1; } @@ -479,15 +488,14 @@ protected function changePage($value) } /** - * Saves user value - * + * Saves user value. */ - protected function save() + protected function save(): void { //Save in session $session = $this->request->getSession(); $sessionValuesClean = clone $this->sessionValues; - if (is_object($this->sessionValues->formSearcherData)) { + if (\is_object($this->sessionValues->formSearcherData)) { $sessionValuesClean->formSearcherData = clone $this->sessionValues->formSearcherData; $sessionValuesClean->formSearcherData->clear(); } @@ -496,10 +504,10 @@ protected function save() //Save in database if ($this->persistentSettings && $this->updateDatabase) { $objectDatabase = $this->registry->getRepository('EcommitCrudBundle:UserCrudSettings')->findOneBy( - array( + [ 'user' => $this->user, - 'crudName' => $this->sessionName - ) + 'crudName' => $this->sessionName, + ] ); $em = $this->registry->getManager(); @@ -526,19 +534,19 @@ protected function save() } /** - * Return default displayed columns + * Return default displayed columns. * * @return array */ public function getDefaultDisplayedColumns() { - $columns = array(); + $columns = []; foreach ($this->availableColumns as $column) { if ($column->defaultDisplayed) { $columns[] = $column->id; } } - if (count($columns) == 0) { + if (0 == \count($columns)) { throw new \Exception('Config Crud: One column displayed is required'); } @@ -546,27 +554,26 @@ public function getDefaultDisplayedColumns() } /** - * Inits the CRUD - * + * Inits the CRUD. */ - public function init() + public function init(): void { //Cheks not empty values - $check_values = array( + $check_values = [ 'availableColumns', 'availableResultsPerPage', 'defaultSort', 'defaultSense', 'defaultResultsPerPage', 'queryBuilder', - 'routeName' - ); + 'routeName', + ]; if (!empty($this->defaultFormSearcherData)) { $check_values[] = 'searchRouteName'; } foreach ($check_values as $value) { if (empty($this->$value)) { - throw new \Exception('Config Crud: Option ' . $value . ' is required'); + throw new \Exception('Config Crud: Option '.$value.' is required'); } } @@ -606,16 +613,13 @@ public function init() /** * Init "fieldFilters" property in $formSearcherData object - * Inject the registry in $formSearcherData objet if implements FieldFilterDoctrineInterface - * @param AbstractFormSearcher $formSearcherData + * Inject the registry in $formSearcherData objet if implements FieldFilterDoctrineInterface. */ - protected function initializeFieldsFilter(AbstractFormSearcher $formSearcherData) + protected function initializeFieldsFilter(AbstractFormSearcher $formSearcherData): void { foreach ($formSearcherData->getFieldsFilter($this->registry) as $field) { if (!($field instanceof \Ecommit\CrudBundle\Form\Filter\AbstractFieldFilter)) { - throw new \Exception( - 'Crud: AbstractFormSearcher: getFieldsFilter() must only returns AbstractFieldFilter implementations' - ); + throw new \Exception('Crud: AbstractFormSearcher: getFieldsFilter() must only returns AbstractFieldFilter implementations'); } if (isset($this->availableColumns[$field->getColumnId()])) { @@ -623,22 +627,17 @@ protected function initializeFieldsFilter(AbstractFormSearcher $formSearcherData } elseif (isset($this->availableVirtualColumns[$field->getColumnId()])) { $column = $this->availableVirtualColumns[$field->getColumnId()]; } else { - throw new \Exception( - 'Crud: AbstractFormSearcher: getFieldsFilter(): Column id does not exit: ' . $field->getColumnId( - ) - ); + throw new \Exception('Crud: AbstractFormSearcher: getFieldsFilter(): Column id does not exit: '.$field->getColumnId()); } $field->setLabel($column->label, $formSearcherData->displayLabelInErrors()); } } - /** - * Load user values - * + * Load user values. */ - protected function load() + protected function load(): void { $session = $this->request->getSession(); $object = $session->get($this->sessionName); //Load from session @@ -657,10 +656,10 @@ protected function load() //Only if persistent settings is enabled if ($this->persistentSettings) { $objectDatabase = $this->registry->getRepository('EcommitCrudBundle:UserCrudSettings')->findOneBy( - array( + [ 'user' => $this->user, - 'crudName' => $this->sessionName - ) + 'crudName' => $this->sessionName, + ] ); if ($objectDatabase) { $this->sessionValues = $objectDatabase->transformToCrudSession(new CrudSession()); @@ -684,9 +683,9 @@ protected function load() } /** - * Checks user values + * Checks user values. */ - protected function checkCrudSession() + protected function checkCrudSession(): void { //Forces change => checks $this->changeNumberResultsDisplayed($this->sessionValues->resultsPerPage); @@ -698,14 +697,14 @@ protected function checkCrudSession() } /** - * User Action: Changes number of displayed results + * User Action: Changes number of displayed results. * * @param int $value */ - protected function changeNumberResultsDisplayed($value) + protected function changeNumberResultsDisplayed($value): void { $oldValue = $this->sessionValues->resultsPerPage; - if (in_array($value, $this->availableResultsPerPage)) { + if (\in_array($value, $this->availableResultsPerPage)) { $this->sessionValues->resultsPerPage = $value; } else { $this->sessionValues->resultsPerPage = $this->defaultResultsPerPage; @@ -713,7 +712,7 @@ protected function changeNumberResultsDisplayed($value) $this->testIfDatabaseMustMeUpdated($oldValue, $value); } - protected function testIfDatabaseMustMeUpdated($oldValue, $new_value) + protected function testIfDatabaseMustMeUpdated($oldValue, $new_value): void { if ($oldValue != $new_value) { $this->updateDatabase = true; @@ -721,24 +720,24 @@ protected function testIfDatabaseMustMeUpdated($oldValue, $new_value) } /** - * User Action: Changes displayed columns + * User Action: Changes displayed columns. * * @param array $value (columns id) */ - protected function changeColumnsDisplayed($value) + protected function changeColumnsDisplayed($value): void { $oldValue = $this->sessionValues->displayedColumns; - if (!is_array($value)) { + if (!\is_array($value)) { $value = $this->getDefaultDisplayedColumns(); } - $newDisplayedColumns = array(); + $newDisplayedColumns = []; $availableColumns = $this->availableColumns; foreach ($value as $column_name) { - if (array_key_exists($column_name, $availableColumns)) { + if (\array_key_exists($column_name, $availableColumns)) { $newDisplayedColumns[] = $column_name; } } - if (count($newDisplayedColumns) == 0) { + if (0 == \count($newDisplayedColumns)) { $newDisplayedColumns = $this->getDefaultDisplayedColumns(); } $this->sessionValues->displayedColumns = $newDisplayedColumns; @@ -746,16 +745,16 @@ protected function changeColumnsDisplayed($value) } /** - * User Action: Changes sort + * User Action: Changes sort. * * @param string $value Column id */ - protected function changeSort($value) + protected function changeSort($value): void { $oldValue = $this->sessionValues->sort; $availableColumns = $this->availableColumns; - if ((is_scalar($value) && array_key_exists($value, $availableColumns) && $availableColumns[$value]->sortable) - || (is_scalar($value) && $value == 'defaultPersonalizedSort' && $this->defaultPersonalizedSort)) { + if ((is_scalar($value) && \array_key_exists($value, $availableColumns) && $availableColumns[$value]->sortable) + || (is_scalar($value) && 'defaultPersonalizedSort' == $value && $this->defaultPersonalizedSort)) { $this->sessionValues->sort = $value; $this->testIfDatabaseMustMeUpdated($oldValue, $value); } else { @@ -765,14 +764,14 @@ protected function changeSort($value) } /** - * User action: Changes sense + * User action: Changes sense. * * @param const $value Sens (ASC / DESC) */ - protected function changeSense($value) + protected function changeSense($value): void { $oldValue = $this->sessionValues->sense; - if (is_scalar($value) && ($value == self::ASC || $value == self::DESC)) { + if (is_scalar($value) && (self::ASC == $value || self::DESC == $value)) { $this->sessionValues->sense = $value; $this->testIfDatabaseMustMeUpdated($oldValue, $value); } else { @@ -782,10 +781,9 @@ protected function changeSense($value) } /** - * Process request - * + * Process request. */ - protected function processRequest() + protected function processRequest(): void { if ($this->request->query->has('razsettings')) { //Reset display settings @@ -801,10 +799,10 @@ protected function processRequest() $displaySettingsFormName = sprintf('crud_display_settings_%s', $this->sessionName); if ($this->request->request->has($displaySettingsFormName)) { $displaySettings = $this->request->request->get($displaySettingsFormName); - if (is_array($displaySettings) && isset($displaySettings['displayedColumns'])) { + if (\is_array($displaySettings) && isset($displaySettings['displayedColumns'])) { $this->changeColumnsDisplayed($displaySettings['displayedColumns']); } - if (is_array($displaySettings) && isset($displaySettings['resultsPerPage'])) { + if (\is_array($displaySettings) && isset($displaySettings['resultsPerPage'])) { $this->changeNumberResultsDisplayed($displaySettings['resultsPerPage']); } } @@ -820,10 +818,9 @@ protected function processRequest() } /** - * Reset display settings - * + * Reset display settings. */ - protected function razDisplaySettings() + protected function razDisplaySettings(): void { $this->sessionValues->displayedColumns = $this->getDefaultDisplayedColumns(); $this->sessionValues->resultsPerPage = $this->defaultResultsPerPage; @@ -835,17 +832,16 @@ protected function razDisplaySettings() $qb = $this->registry->getManager()->createQueryBuilder(); $qb->delete('EcommitCrudBundle:UserCrudSettings', 's') ->andWhere('s.user = :user AND s.crudName = :crud_name') - ->setParameters(array('user' => $this->user, 'crud_name' => $this->sessionName)) + ->setParameters(['user' => $this->user, 'crud_name' => $this->sessionName]) ->getQuery() ->execute(); } } /** - * Reset search form values - * + * Reset search form values. */ - public function raz() + public function raz(): void { if ($this->defaultFormSearcherData) { $newValue = clone $this->defaultFormSearcherData; @@ -861,10 +857,9 @@ public function raz() } /** - * Reset sort - * + * Reset sort. */ - public function razSort() + public function razSort(): void { $this->sessionValues->sense = $this->defaultSense; $this->sessionValues->sort = $this->defaultSort; @@ -872,17 +867,16 @@ public function razSort() } /** - * Builds the query - * + * Builds the query. */ - public function buildQuery() + public function buildQuery(): void { //Builds query $columnSortId = $this->sessionValues->sort; - if ($columnSortId == 'defaultPersonalizedSort') { + if ('defaultPersonalizedSort' == $columnSortId) { //Default personalised sort is used foreach ($this->defaultPersonalizedSort as $key => $value) { - if (is_int($key)) { + if (\is_int($key)) { $sort = $value; $sense = $this->defaultSense; } else { @@ -897,7 +891,7 @@ public function buildQuery() //Sort alias is not defined. Alias is used $columnSortAlias = $this->availableColumns[$columnSortId]->alias; $this->queryBuilder->orderBy($columnSortAlias, $this->sessionValues->sense); - } elseif (is_array($columnSortAlias)) { + } elseif (\is_array($columnSortAlias)) { //Sort alias is defined in many columns foreach ($columnSortAlias as $oneColumnSortAlias) { $this->queryBuilder->addOrderBy($oneColumnSortAlias, $this->sessionValues->sense); @@ -921,10 +915,7 @@ public function buildQuery() } elseif (isset($this->availableVirtualColumns[$field->getColumnId()])) { $column = $this->availableVirtualColumns[$field->getColumnId()]; } else { - throw new \Exception( - 'Crud: AbstractFormSearcher: getFieldsFilter(): Column id does not exit: ' . $field->getColumnId( - ) - ); + throw new \Exception('Crud: AbstractFormSearcher: getFieldsFilter(): Column id does not exit: '.$field->getColumnId()); } //Get alias search @@ -945,20 +936,19 @@ public function buildQuery() $this->queryBuilder = $this->sessionValues->formSearcherData->globalChangeQuery($this->queryBuilder); } - //Builds paginator if ($this->displayResults) { - if (is_object($this->buildPaginator) && $this->buildPaginator instanceof \Closure) { + if (\is_object($this->buildPaginator) && $this->buildPaginator instanceof \Closure) { //Case: Manual paginator (by closure) is enabled $this->paginator = $this->buildPaginator->__invoke( $this->queryBuilder, $this->sessionValues->page, $this->sessionValues->resultsPerPage ); - } elseif (true === $this->buildPaginator || is_array($this->buildPaginator)) { + } elseif (true === $this->buildPaginator || \is_array($this->buildPaginator)) { //Case: Auto paginator is enabled - $paginatorOptions = array(); - if (is_array($this->buildPaginator)) { + $paginatorOptions = []; + if (\is_array($this->buildPaginator)) { $paginatorOptions = $this->buildPaginator; } @@ -974,7 +964,8 @@ public function buildQuery() } /** - * Return default results per page + * Return default results per page. + * * @return int */ public function getDefaultResultsPerPage() @@ -983,10 +974,9 @@ public function getDefaultResultsPerPage() } /** - * Clears this object, before sending it to template - * + * Clears this object, before sending it to template. */ - public function clearTemplate() + public function clearTemplate(): void { $this->queryBuilder = null; $this->formFactory = null; @@ -1001,7 +991,7 @@ public function clearTemplate() } /** - * Returns availabled columns + * Returns availabled columns. * * @return array */ @@ -1011,20 +1001,20 @@ public function getColumns() } /** - * Returns one column + * Returns one column. * - * @return CrudColumn $columnId + * @return CrudColumn $columnId */ public function getColumn($columnId) { if (isset($this->availableColumns[$columnId])) { return $this->availableColumns[$columnId]; } - throw new \Exception('Crud: Column ' . $columnId . ' does not exist'); + throw new \Exception('Crud: Column '.$columnId.' does not exist'); } /** - * Returns user values + * Returns user values. * * @return CrudSession */ @@ -1034,8 +1024,9 @@ public function getSessionValues() } /** - * Returns the paginator - * @return Object + * Returns the paginator. + * + * @return object */ public function getPaginator() { @@ -1043,9 +1034,9 @@ public function getPaginator() } /** - * Sets the paginator + * Sets the paginator. * - * @param Object $value + * @param object $value */ public function setPaginator($value) { @@ -1055,7 +1046,7 @@ public function setPaginator($value) } /** - * Returns the search form + * Returns the search form. * * @return FormBuilder (before init) or Form (before clearTemplate) or FormView (after clearTemplate) */ @@ -1065,7 +1056,7 @@ public function getSearcherForm() } /** - * Returns the div id search + * Returns the div id search. * * @return string */ @@ -1075,9 +1066,10 @@ public function getDivIdSearch() } /** - * Sets the div id search + * Sets the div id search. * * @param string + * * @return Crud */ public function setDivIdSearch($divIdSearch) @@ -1088,7 +1080,7 @@ public function setDivIdSearch($divIdSearch) } /** - * Returns the div id list + * Returns the div id list. * * @return string */ @@ -1098,9 +1090,10 @@ public function getDivIdList() } /** - * Sets the div id list + * Sets the div id list. * * @param string + * * @return Crud */ public function setDivIdList($divIdList) @@ -1111,7 +1104,7 @@ public function setDivIdList($divIdList) } /** - * Gets session name + * Gets session name. * * @return string */ @@ -1121,7 +1114,7 @@ public function getSessionName() } /** - * @return boolean + * @return bool */ public function getDisplayResultsOnlyIfSearch() { @@ -1129,7 +1122,8 @@ public function getDisplayResultsOnlyIfSearch() } /** - * @param boolean $displayResultsOnlyIfSearch + * @param bool $displayResultsOnlyIfSearch + * * @return Crud */ public function setDisplayResultsOnlyIfSearch($displayResultsOnlyIfSearch) @@ -1140,7 +1134,7 @@ public function setDisplayResultsOnlyIfSearch($displayResultsOnlyIfSearch) } /** - * @return boolean + * @return bool */ public function getDisplayResults() { @@ -1149,6 +1143,7 @@ public function getDisplayResults() /** * @param bool $displayResults + * * @return Crud */ public function setDisplayResults($displayResults) @@ -1160,12 +1155,12 @@ public function setDisplayResults($displayResults) /** * @param string $functionName - * @param mixed $value + * @param mixed $value */ - public function configureTemplate($functionName, $value) + public function configureTemplate($functionName, $value): void { if (!self::validateConfigureTemplateFunctionName($functionName)) { - throw new \Exception(\sprintf('%s method is not allowed in configureTemplate', $functionName)); + throw new \Exception(sprintf('%s method is not allowed in configureTemplate', $functionName)); } if (isset($this->templateConfiguration[$functionName])) { @@ -1180,11 +1175,12 @@ public function configureTemplate($functionName, $value) /** * @param string $functionName + * * @return bool */ public static function validateConfigureTemplateFunctionName($functionName) { - $functionsAllowed = array( + $functionsAllowed = [ 'paginator_links', 'crud_paginator_links', 'crud_th', @@ -1192,13 +1188,14 @@ public static function validateConfigureTemplateFunctionName($functionName) 'crud_search_reset', 'crud_remote_modal', 'crud_display_settings', - ); + ]; - return in_array($functionName, $functionsAllowed); + return \in_array($functionName, $functionsAllowed); } /** * @param string $functionName + * * @return array */ public function getTemplateConfiguration($functionName) @@ -1207,6 +1204,6 @@ public function getTemplateConfiguration($functionName) return $this->templateConfiguration[$functionName]; } - return array(); + return []; } } diff --git a/src/Crud/CrudColumn.php b/src/Crud/CrudColumn.php index 799af5d..b8fe869 100644 --- a/src/Crud/CrudColumn.php +++ b/src/Crud/CrudColumn.php @@ -1,5 +1,7 @@ @@ -66,6 +69,7 @@ public function __construct( /** * @param $sessionName + * * @return Crud */ public function create($sessionName) diff --git a/src/Crud/CrudSession.php b/src/Crud/CrudSession.php index 7b2931a..252b3a4 100644 --- a/src/Crud/CrudSession.php +++ b/src/Crud/CrudSession.php @@ -1,5 +1,7 @@ @@ -24,8 +27,5 @@ public function addOrderBy($sort, $sense); */ public function orderBy($sort, $sense); - /** - * @param QueryBuilderParameterInterface $parameter - */ public function addParameter(QueryBuilderParameterInterface $parameter); } diff --git a/src/Crud/QueryBuilderParameterInterface.php b/src/Crud/QueryBuilderParameterInterface.php index 9dec07e..49b1a46 100644 --- a/src/Crud/QueryBuilderParameterInterface.php +++ b/src/Crud/QueryBuilderParameterInterface.php @@ -1,5 +1,8 @@ diff --git a/src/Crud/Rest/RestQueryBuilder.php b/src/Crud/Rest/RestQueryBuilder.php index 7dcaa3e..9e963a2 100644 --- a/src/Crud/Rest/RestQueryBuilder.php +++ b/src/Crud/Rest/RestQueryBuilder.php @@ -1,5 +1,8 @@ @@ -28,12 +31,12 @@ class RestQueryBuilder implements QueryBuilderInterface /** * @var array */ - protected $queryParameters = array(); + protected $queryParameters = []; /** * @var array */ - protected $formParameters = array(); + protected $formParameters = []; /** * @var string @@ -53,15 +56,16 @@ class RestQueryBuilder implements QueryBuilderInterface /** * @var array */ - protected $orders = array(); + protected $orders = []; /** * RestQueryBuilder constructor. + * * @param string $url * @param string $method - * @param array $defaultParameters Array of RestQueryBuilderParameter objects + * @param array $defaultParameters Array of RestQueryBuilderParameter objects */ - public function __construct($url, $method, $defaultParameters = array()) + public function __construct($url, $method, $defaultParameters = []) { $this->url = $url; $this->method = $method; @@ -71,7 +75,6 @@ public function __construct($url, $method, $defaultParameters = array()) } /** - * @param \Closure $orderBuilder * @return $this */ public function setOrderBuilder(\Closure $orderBuilder) @@ -82,7 +85,6 @@ public function setOrderBuilder(\Closure $orderBuilder) } /** - * @param \Closure $paginationBuilder * @return $this */ public function setPaginationBuilder(\Closure $paginationBuilder) @@ -96,8 +98,10 @@ public function setPaginationBuilder(\Closure $paginationBuilder) * @param string $parameter * @param string $value * @param string $method - * @return $this + * * @throws \Exception + * + * @return $this */ public function addParameter(QueryBuilderParameterInterface $parameter) { @@ -125,6 +129,7 @@ public function addParameter(QueryBuilderParameterInterface $parameter) /** * @param string $sort * @param string $sense + * * @return $this */ public function addOrderBy($sort, $sense) @@ -137,29 +142,32 @@ public function addOrderBy($sort, $sense) /** * @param string $sort * @param string $sense + * * @return $this */ public function orderBy($sort, $sense) { - $this->orders = array(); + $this->orders = []; $this->addOrderBy($sort, $sense); return $this; } /** - * @param int $page - * @param int $resultsPerPage + * @param int $page + * @param int $resultsPerPage * @param array $options - * @return mixed|\Psr\Http\Message\ResponseInterface + * * @throws \Exception + * + * @return mixed|\Psr\Http\Message\ResponseInterface */ - public function getResponse($page, $resultsPerPage, $options = array()) + public function getResponse($page, $resultsPerPage, $options = []) { $client = new \GuzzleHttp\Client(); //Add paginator parameters - if ($this->paginationBuilder && $this->paginationBuilder instanceof \Closure) { + if ($this->paginationBuilder && $this->paginationBuilder instanceof \Closure) { $parameters = $this->paginationBuilder->__invoke($page, $resultsPerPage); foreach ($parameters as $parameter) { $this->addParameter($parameter); @@ -167,7 +175,7 @@ public function getResponse($page, $resultsPerPage, $options = array()) } //Add sort parameters - if (count($this->orders) > 0 && $this->orderBuilder && $this->orderBuilder instanceof \Closure) { + if (\count($this->orders) > 0 && $this->orderBuilder && $this->orderBuilder instanceof \Closure) { $parameters = $this->orderBuilder->__invoke($this->orders); foreach ($parameters as $parameter) { $this->addParameter($parameter); diff --git a/src/Crud/Rest/RestQueryBuilderParameter.php b/src/Crud/Rest/RestQueryBuilderParameter.php index 5b7d24e..444977e 100644 --- a/src/Crud/Rest/RestQueryBuilderParameter.php +++ b/src/Crud/Rest/RestQueryBuilderParameter.php @@ -1,5 +1,8 @@ diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index aea2c09..8f7aa19 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -1,5 +1,7 @@ children() ->arrayNode('template_configuration') - ->treatNullLike(array()) + ->treatNullLike([]) ->prototype('variable') ->end() ->validate() @@ -60,4 +62,3 @@ public function getConfigTreeBuilder() return $treeBuilder; } } - diff --git a/src/DependencyInjection/EcommitCrudExtension.php b/src/DependencyInjection/EcommitCrudExtension.php index 3d06ea1..e07a6a6 100644 --- a/src/DependencyInjection/EcommitCrudExtension.php +++ b/src/DependencyInjection/EcommitCrudExtension.php @@ -1,5 +1,7 @@ processConfiguration($configuration, $config); - $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); + $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('services.xml'); $container->setParameter('ecommit_crud.template_configuration', $config['template_configuration']); - $container->setParameter('ecommit_crud.images', array('th_image_up' => $config['images']['th_image_up'], 'th_image_down' => $config['images']['th_image_down'])); + $container->setParameter('ecommit_crud.images', ['th_image_up' => $config['images']['th_image_up'], 'th_image_down' => $config['images']['th_image_down']]); } } diff --git a/src/DoctrineExtension/Paginate.php b/src/DoctrineExtension/Paginate.php index 8e45236..1c6c4a1 100644 --- a/src/DoctrineExtension/Paginate.php +++ b/src/DoctrineExtension/Paginate.php @@ -1,5 +1,7 @@ setDefaults(array( + $resolver->setDefaults([ //Behavior. Availabled values: // - count_by_alias: Use alias. Option "alias" is required // - count_by_sub_request: Use sub request @@ -51,14 +54,14 @@ static public function countQueryBuilder($queryBuilder, array $options = array() 'alias' => null, //Used when behavior=count_by_alias 'distinct_alias' => true, - )); - $resolver->setAllowedTypes('distinct_alias', array('boolean')); + ]); + $resolver->setAllowedTypes('distinct_alias', ['boolean']); if ($useORM) { - $resolver->setAllowedValues('behavior', array('count_by_alias', 'count_by_sub_request', 'orm')); + $resolver->setAllowedValues('behavior', ['count_by_alias', 'count_by_sub_request', 'orm']); //Use only when ORM and behavior=orm $resolver->setDefault('simplified_request', true); } else { - $resolver->setAllowedValues('behavior', array('count_by_alias', 'count_by_sub_request')); + $resolver->setAllowedValues('behavior', ['count_by_alias', 'count_by_sub_request']); } $options = $resolver->resolve($options); if ('count_by_alias' === $options['behavior'] && null === $options['alias']) { @@ -75,8 +78,8 @@ static public function countQueryBuilder($queryBuilder, array $options = array() } elseif ('count_by_alias' === $options['behavior']) { /** @var \Doctrine\ORM\QueryBuilder $countQueryBuilder */ $countQueryBuilder = clone $queryBuilder; - $distinct = ($options['distinct_alias'])? 'DISTINCT ' : ''; - $countQueryBuilder->select(\sprintf('count(%s%s)', $distinct, $options['alias'])); + $distinct = ($options['distinct_alias']) ? 'DISTINCT ' : ''; + $countQueryBuilder->select(sprintf('count(%s%s)', $distinct, $options['alias'])); $countQueryBuilder->resetDQLPart('orderBy'); return (int) $countQueryBuilder->getQuery()->getSingleScalarResult(); @@ -86,13 +89,13 @@ static public function countQueryBuilder($queryBuilder, array $options = array() $cloneQueryBuilder->resetDQLPart('orderBy'); $rsm = new ResultSetMapping(); $rsm->addScalarResult('cnt', 'cnt'); - $countSql = \sprintf('SELECT count(*) as cnt FROM (%s) mainquery', $cloneQueryBuilder->getQuery()->getSQL()); + $countSql = sprintf('SELECT count(*) as cnt FROM (%s) mainquery', $cloneQueryBuilder->getQuery()->getSQL()); /** @var NativeQuery $countQuery */ $countQuery = $queryBuilder->getEntityManager()->createNativeQuery($countSql, $rsm); $i = 0; /** @var Parameter $parameter */ foreach ($queryBuilder->getParameters() as $parameter) { - $i++; + ++$i; $countQuery->setParameter($i, $parameter->getValue(), $parameter->getType()); } @@ -102,8 +105,8 @@ static public function countQueryBuilder($queryBuilder, array $options = array() if ('count_by_alias' === $options['behavior']) { /** @var \Doctrine\DBAL\Query\QueryBuilder $countQueryBuilder */ $countQueryBuilder = clone $queryBuilder; - $distinct = ($options['distinct_alias'])? 'DISTINCT ' : ''; - $countQueryBuilder->select(\sprintf('count(%s%s)', $distinct, $options['alias'])); + $distinct = ($options['distinct_alias']) ? 'DISTINCT ' : ''; + $countQueryBuilder->select(sprintf('count(%s%s)', $distinct, $options['alias'])); $countQueryBuilder->resetQueryPart('orderBy'); return (int) $countQueryBuilder->execute()->fetchColumn(0); @@ -115,7 +118,7 @@ static public function countQueryBuilder($queryBuilder, array $options = array() $queryBuilderCount->resetQueryParts(); //Remove Query Parts $queryBuilderCount->select('count(*)') - ->from('(' . $queryBuilderClone->getSql() . ')', 'mainquery'); + ->from('('.$queryBuilderClone->getSql().')', 'mainquery'); return (int) $queryBuilderCount->execute()->fetchColumn(0); } @@ -124,6 +127,7 @@ static public function countQueryBuilder($queryBuilder, array $options = array() /** * @param \Doctrine\ORM\QueryBuilde|\Doctrine\DBAL\Query\QueryBuilder $queryBuilder + * * @return string */ public static function getDefaultCountBehavior($queryBuilder) @@ -139,13 +143,14 @@ public static function getDefaultCountBehavior($queryBuilder) /** * @param \Doctrine\ORM\QueryBuilde|\Doctrine\DBAL\Query\QueryBuilder $queryBuilder - * @param int $page Page number to display - * @param int $perPage Results per page - * @param array $options - * @return AbstractPaginator + * @param int $page Page number to display + * @param int $perPage Results per page + * * @throws \Exception + * + * @return AbstractPaginator */ - static public function createDoctrinePaginator($queryBuilder, $page, $perPage, array $options = array()) + public static function createDoctrinePaginator($queryBuilder, $page, $perPage, array $options = []) { if ($queryBuilder instanceof \Doctrine\ORM\QueryBuilder) { $useORM = true; @@ -156,7 +161,7 @@ static public function createDoctrinePaginator($queryBuilder, $page, $perPage, a } $resolver = new OptionsResolver(); - $resolver->setDefaults(array( + $resolver->setDefaults([ //Behavior for create paginator. Availabled values : // - doctrine_paginator: Return DoctrineORMPaginator or DoctrineDBALPaginator object // - identifier_by_sub_request: Primary keys are found by sub request. Return ArrayPaginator. Option "identifier" is required @@ -164,14 +169,14 @@ static public function createDoctrinePaginator($queryBuilder, $page, $perPage, a //Manual value for the number of results 'count_manual_value' => null, //Count options. See countQueryBuilder. Used only if count_manual_value = null - 'count_options' => array(), + 'count_options' => [], //Identifier used when behavior=identifier_by_sub_request 'identifier' => null, //Used only when ORM and behavior=doctrine_paginator 'simplified_request' => true, 'fetch_join_collection' => false, - )); - $resolver->setAllowedValues('behavior', array('doctrine_paginator', 'identifier_by_sub_request')); + ]); + $resolver->setAllowedValues('behavior', ['doctrine_paginator', 'identifier_by_sub_request']); $options = $resolver->resolve($options); if ('identifier_by_sub_request' === $options['behavior']) { @@ -198,7 +203,7 @@ static public function createDoctrinePaginator($queryBuilder, $page, $perPage, a return $paginator; } elseif ('identifier_by_sub_request' === $options['behavior']) { - $result = array(); + $result = []; if (null === $options['count_manual_value']) { $countResults = self::countQueryBuilder($queryBuilder, $options['count_options']); @@ -208,7 +213,7 @@ static public function createDoctrinePaginator($queryBuilder, $page, $perPage, a if ($countResults) { $idsQueryBuilder = clone $queryBuilder; - $idsQueryBuilder->select(\sprintf('DISTINCT %s as pk', $options['identifier'])); + $idsQueryBuilder->select(sprintf('DISTINCT %s as pk', $options['identifier'])); if ($useORM) { $tmpPaginator = new DoctrineORMPaginator($perPage); @@ -222,7 +227,7 @@ static public function createDoctrinePaginator($queryBuilder, $page, $perPage, a $tmpPaginator->setManualCountResults($countResults); $tmpPaginator->init(); - $ids = array(); + $ids = []; foreach ($tmpPaginator->getResults() as $line) { $ids[] = $line['pk']; } @@ -230,12 +235,12 @@ static public function createDoctrinePaginator($queryBuilder, $page, $perPage, a $finalQueryBuilder = clone $queryBuilder; if ($useORM) { $finalQueryBuilder->resetDQLPart('where'); - $finalQueryBuilder->setParameters(array()); + $finalQueryBuilder->setParameters([]); QueryBuilderFilter::addMultiFilter($finalQueryBuilder, QueryBuilderFilter::SELECT_IN, $ids, $options['identifier'], 'paginate_pks'); $result = $finalQueryBuilder->getQuery()->getResult(); } else { $finalQueryBuilder->resetQueryPart('where'); - $finalQueryBuilder->setParameters(array()); + $finalQueryBuilder->setParameters([]); QueryBuilderFilter::addMultiFilter($finalQueryBuilder, QueryBuilderFilter::SELECT_IN, $ids, $options['identifier'], 'paginate_pks'); $result = $finalQueryBuilder->execute()->fetchAll(); } diff --git a/src/DoctrineExtension/QueryBuilderFilter.php b/src/DoctrineExtension/QueryBuilderFilter.php index 003c8d6..02bb53a 100644 --- a/src/DoctrineExtension/QueryBuilderFilter.php +++ b/src/DoctrineExtension/QueryBuilderFilter.php @@ -1,6 +1,9 @@ * @@ -12,33 +15,35 @@ class QueryBuilderFilter { - const SELECT_IN = 'IN'; //WHERE IN - const SELECT_NOT_IN = 'NIN'; //WHERE NOT IN - const SELECT_ALL = 'ALL'; //No Filter (all values) - const SELECT_AUTO = 'AUT'; //WHERE IN. If filter values are empty, no filter (all values) - const SELECT_NO = 'NO'; //Must return no result + public const SELECT_IN = 'IN'; //WHERE IN + public const SELECT_NOT_IN = 'NIN'; //WHERE NOT IN + public const SELECT_ALL = 'ALL'; //No Filter (all values) + public const SELECT_AUTO = 'AUT'; //WHERE IN. If filter values are empty, no filter (all values) + public const SELECT_NO = 'NO'; //Must return no result /** - * Add SQL WHERE IN or WHERE NOT IN filter + * Add SQL WHERE IN or WHERE NOT IN filter. + * * @param \Doctrine\DBAL\Query\QueryBuilder|\Doctrine\ORM\QueryBuilder $queryBuilder - * @param string $filterSign ALL (no filter), IN (WHERE IN), NIN (WHERE NOT IN), AUT (WHERE IN if $filterValues is not empty. No filter else), NO (no result) - * @param array $filterValues Values - * @param string $sqlField SQL field name - * @param string $paramName SQL parameter name + * @param string $filterSign ALL (no filter), IN (WHERE IN), NIN (WHERE NOT IN), AUT (WHERE IN if $filterValues is not empty. No filter else), NO (no result) + * @param array $filterValues Values + * @param string $sqlField SQL field name + * @param string $paramName SQL parameter name + * * @return \Doctrine\DBAL\Query\QueryBuilder|\Doctrine\ORM\QueryBuilder */ public static function addMultiFilter($queryBuilder, $filterSign, $filterValues, $sqlField, $paramName) { - if (self::SELECT_NO == $filterSign) { + if (self::SELECT_NO == $filterSign) { //Must return no result $queryBuilder->andWhere('0 = 1'); return $queryBuilder; } - if ($filterSign != self::SELECT_IN && $filterSign != self::SELECT_NOT_IN && $filterSign != self::SELECT_AUTO) { + if (self::SELECT_IN != $filterSign && self::SELECT_NOT_IN != $filterSign && self::SELECT_AUTO != $filterSign) { return $queryBuilder; } - if (null === $filterValues || 0 === count($filterValues)) { + if (null === $filterValues || 0 === \count($filterValues)) { if (self::SELECT_NOT_IN == $filterSign || self::SELECT_AUTO == $filterSign) { return $queryBuilder; } @@ -49,7 +54,7 @@ public static function addMultiFilter($queryBuilder, $filterSign, $filterValues, return $queryBuilder; } - if (count($filterValues) > 1000) { + if (\count($filterValues) > 1000) { return self::addGroupMultiFilter($queryBuilder, $filterSign, $filterValues, $sqlField, $paramName); } @@ -57,43 +62,47 @@ public static function addMultiFilter($queryBuilder, $filterSign, $filterValues, } /** - * Add SQL WHERE IN or WHERE NOT IN filter without group + * Add SQL WHERE IN or WHERE NOT IN filter without group. + * * @param \Doctrine\DBAL\Query\QueryBuilder|\Doctrine\ORM\QueryBuilder $queryBuilder - * @param string $filterSign ALL (no filter), IN (WHERE IN), NIN (WHERE NOT IN), AUT (WHERE IN if $filterValues is not empty. No filter else), NO (no result) - * @param array $filterValues Values - * @param string $sqlField SQL field name - * @param string $paramName SQL parameter name + * @param string $filterSign ALL (no filter), IN (WHERE IN), NIN (WHERE NOT IN), AUT (WHERE IN if $filterValues is not empty. No filter else), NO (no result) + * @param array $filterValues Values + * @param string $sqlField SQL field name + * @param string $paramName SQL parameter name + * * @return \Doctrine\DBAL\Query\QueryBuilder|\Doctrine\ORM\QueryBuilder */ protected static function addSimpleMultiFilter($queryBuilder, $filterSign, $filterValues, $sqlField, $paramName) { - $clauseSql = ($filterSign == self::SELECT_IN || $filterSign == self::SELECT_AUTO)? 'IN' : 'NOT IN'; + $clauseSql = (self::SELECT_IN == $filterSign || self::SELECT_AUTO == $filterSign) ? 'IN' : 'NOT IN'; - $queryBuilder->andWhere(\sprintf('%s %s (:%s)', $sqlField, $clauseSql, $paramName)); + $queryBuilder->andWhere(sprintf('%s %s (:%s)', $sqlField, $clauseSql, $paramName)); $queryBuilder->setParameter($paramName, $filterValues, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY); return $queryBuilder; } /** - * Add SQL WHERE IN or WHERE NOT IN filter with group + * Add SQL WHERE IN or WHERE NOT IN filter with group. + * * @param \Doctrine\DBAL\Query\QueryBuilder|\Doctrine\ORM\QueryBuilder $queryBuilder - * @param string $filterSign ALL (no filter), IN (WHERE IN), NIN (WHERE NOT IN), AUT (WHERE IN if $filterValues is not empty. No filter else), NO (no result) - * @param array $filterValues Values - * @param string $sqlField SQL field name - * @param string $paramName SQL parameter name + * @param string $filterSign ALL (no filter), IN (WHERE IN), NIN (WHERE NOT IN), AUT (WHERE IN if $filterValues is not empty. No filter else), NO (no result) + * @param array $filterValues Values + * @param string $sqlField SQL field name + * @param string $paramName SQL parameter name + * * @return \Doctrine\DBAL\Query\QueryBuilder|\Doctrine\ORM\QueryBuilder */ protected static function addGroupMultiFilter($queryBuilder, $filterSign, $filterValues, $sqlField, $paramName) { - $clauseSql = ($filterSign == self::SELECT_IN || $filterSign == self::SELECT_AUTO)? 'IN' : 'NOT IN'; - $separatorClauseSql = ($filterSign == self::SELECT_IN || $filterSign == self::SELECT_AUTO)? 'OR' : 'AND'; + $clauseSql = (self::SELECT_IN == $filterSign || self::SELECT_AUTO == $filterSign) ? 'IN' : 'NOT IN'; + $separatorClauseSql = (self::SELECT_IN == $filterSign || self::SELECT_AUTO == $filterSign) ? 'OR' : 'AND'; $groupNumber = 0; - $groups = array(); - foreach (\array_chunk($filterValues, 1000) as $filterValuesGroup) { - $groupNumber++; - $groups[] = \sprintf('%s %s (:%s%s)', $sqlField, $clauseSql, $paramName, $groupNumber); + $groups = []; + foreach (array_chunk($filterValues, 1000) as $filterValuesGroup) { + ++$groupNumber; + $groups[] = sprintf('%s %s (:%s%s)', $sqlField, $clauseSql, $paramName, $groupNumber); $queryBuilder->setParameter($paramName.$groupNumber, $filterValuesGroup, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY); } @@ -103,44 +112,46 @@ protected static function addGroupMultiFilter($queryBuilder, $filterSign, $filte } /** - * Add SQL WHERE IN or WHERE NOT IN filter. And result MUST BE in the whitelist (if $restrictSign=IN) or MUST NOT BE in the blacklist (if $restrictSign=NIN) + * Add SQL WHERE IN or WHERE NOT IN filter. And result MUST BE in the whitelist (if $restrictSign=IN) or MUST NOT BE in the blacklist (if $restrictSign=NIN). + * * @param \Doctrine\DBAL\Query\QueryBuilder|\Doctrine\ORM\QueryBuilder $queryBuilder - * @param string $filterSign ALL (no filter), IN (WHERE IN), NIN (WHERE NOT IN), AUT (WHERE IN if $filterValues is not empty. No filter else), NO (no result) - * @param array $filterValues Values - * @param string $sqlField SQL field name - * @param string $paramName SQL parameter name - * @param string $restrictSign IN (WHERE IN), NIN (WHERE NOT IN), AUT (WHERE IN if $restrictValues is not empty. No filter else), NO (no result) - * @param array $restrictValues Whitelist (if $restrictSign=IN or AUT) or blacklist (if $restrictSign=NIN) + * @param string $filterSign ALL (no filter), IN (WHERE IN), NIN (WHERE NOT IN), AUT (WHERE IN if $filterValues is not empty. No filter else), NO (no result) + * @param array $filterValues Values + * @param string $sqlField SQL field name + * @param string $paramName SQL parameter name + * @param string $restrictSign IN (WHERE IN), NIN (WHERE NOT IN), AUT (WHERE IN if $restrictValues is not empty. No filter else), NO (no result) + * @param array $restrictValues Whitelist (if $restrictSign=IN or AUT) or blacklist (if $restrictSign=NIN) + * * @return \Doctrine\DBAL\Query\QueryBuilder|\Doctrine\ORM\QueryBuilder */ public static function addMultiFilterWithRestrictValues($queryBuilder, $filterSign, $filterValues, $sqlField, $paramName, $restrictSign, $restrictValues) { - if (self::SELECT_NO == $filterSign || self::SELECT_NO == $restrictSign) { + if (self::SELECT_NO == $filterSign || self::SELECT_NO == $restrictSign) { //Must return no result $queryBuilder->andWhere('0 = 1'); return $queryBuilder; } - if (in_array($restrictSign, array(self::SELECT_IN, self::SELECT_AUTO)) && in_array($filterSign, array(self::SELECT_IN, self::SELECT_AUTO)) && count($filterValues) > 0 && count($restrictValues) > 0) { + if (\in_array($restrictSign, [self::SELECT_IN, self::SELECT_AUTO]) && \in_array($filterSign, [self::SELECT_IN, self::SELECT_AUTO]) && \count($filterValues) > 0 && \count($restrictValues) > 0) { //We can simplify the query //Data cleaning - $cleanValues = array(); + $cleanValues = []; foreach ($filterValues as $value) { - if (in_array($value, $restrictValues)) { + if (\in_array($value, $restrictValues)) { $cleanValues[] = $value; } } $queryBuilder = self::addMultiFilter($queryBuilder, self::SELECT_IN, $cleanValues, $sqlField, $paramName); - } elseif (self::SELECT_NOT_IN === $restrictSign && self::SELECT_NOT_IN === $filterSign && count($filterValues) > 0 && count($restrictValues) > 0) { + } elseif (self::SELECT_NOT_IN === $restrictSign && self::SELECT_NOT_IN === $filterSign && \count($filterValues) > 0 && \count($restrictValues) > 0) { //We can simplify the query //Data fusion $cleanValues = $restrictValues; foreach ($filterValues as $value) { - if (!in_array($value, $restrictValues)) { + if (!\in_array($value, $restrictValues)) { $cleanValues[] = $value; } } @@ -156,12 +167,14 @@ public static function addMultiFilterWithRestrictValues($queryBuilder, $filterSi } /** - * Add SQL "equal" or "not equal" filter + * Add SQL "equal" or "not equal" filter. + * * @param \Doctrine\DBAL\Query\QueryBuilder|\Doctrine\ORM\QueryBuilder $queryBuilder - * @param bool $equal Equal or not - * @param string $filterValue Value - * @param string $sqlField SQL field name - * @param string $paramName SQL parameter name + * @param bool $equal Equal or not + * @param string $filterValue Value + * @param string $sqlField SQL field name + * @param string $paramName SQL parameter name + * * @return \Doctrine\DBAL\Query\QueryBuilder|\Doctrine\ORM\QueryBuilder */ public static function addEqualFilter($queryBuilder, $equal, $filterValue, $sqlField, $paramName) @@ -181,12 +194,14 @@ public static function addEqualFilter($queryBuilder, $equal, $filterValue, $sqlF } /** - * Add SQL comparator filter + * Add SQL comparator filter. + * * @param \Doctrine\DBAL\Query\QueryBuilder|\Doctrine\ORM\QueryBuilder $queryBuilder - * @param string $sign Comparator sign (< > <= >=) - * @param string $filterValue Value - * @param string $sqlField SQL field name - * @param string $paramName SQL parameter name + * @param string $sign Comparator sign (< > <= >=) + * @param string $filterValue Value + * @param string $sqlField SQL field name + * @param string $paramName SQL parameter name + * * @return \Doctrine\DBAL\Query\QueryBuilder|\Doctrine\ORM\QueryBuilder */ public static function addComparatorFilter($queryBuilder, $sign, $filterValue, $sqlField, $paramName) @@ -195,19 +210,21 @@ public static function addComparatorFilter($queryBuilder, $sign, $filterValue, $ return $queryBuilder; } - $queryBuilder->andWhere(\sprintf('%s %s :%s', $sqlField, $sign, $paramName)); + $queryBuilder->andWhere(sprintf('%s %s :%s', $sqlField, $sign, $paramName)); $queryBuilder->setParameter($paramName, $filterValue); return $queryBuilder; } /** - * Add SQL "LIKE" or "NOT LIKE" filter + * Add SQL "LIKE" or "NOT LIKE" filter. + * * @param \Doctrine\DBAL\Query\QueryBuilder|\Doctrine\ORM\QueryBuilder $queryBuilder - * @param bool $contain Contain or not - * @param string $filterValue Value - * @param string $sqlField SQL field name - * @param string $paramName SQL parameter name + * @param bool $contain Contain or not + * @param string $filterValue Value + * @param string $sqlField SQL field name + * @param string $paramName SQL parameter name + * * @return \Doctrine\DBAL\Query\QueryBuilder|\Doctrine\ORM\QueryBuilder */ public static function addContainFilter($queryBuilder, $contain, $filterValue, $sqlField, $paramName) @@ -218,9 +235,9 @@ public static function addContainFilter($queryBuilder, $contain, $filterValue, $ $filterValue = addcslashes($filterValue, '%_'); if ($contain) { - $queryBuilder->andWhere($queryBuilder->expr()->like($sqlField, ':' . $paramName)); + $queryBuilder->andWhere($queryBuilder->expr()->like($sqlField, ':'.$paramName)); } else { - $queryBuilder->andWhere($queryBuilder->expr()->notLike($sqlField, ':' . $paramName)); + $queryBuilder->andWhere($queryBuilder->expr()->notLike($sqlField, ':'.$paramName)); } $queryBuilder->setParameter($paramName, '%'.$filterValue.'%'); diff --git a/src/EcommitCrudBundle.php b/src/EcommitCrudBundle.php index a030e57..3ed6eda 100644 --- a/src/EcommitCrudBundle.php +++ b/src/EcommitCrudBundle.php @@ -1,5 +1,7 @@ crudName = $crudName; - + return $this; } /** - * Get crudName + * Get crudName. * - * @return string + * @return string */ public function getCrudName() { @@ -76,22 +79,23 @@ public function getCrudName() } /** - * Set resultsDisplayed + * Set resultsDisplayed. + * + * @param int $resultsDisplayed * - * @param integer $resultsDisplayed * @return UserCrudSettings */ public function setResultsDisplayed($resultsDisplayed) { $this->resultsDisplayed = $resultsDisplayed; - + return $this; } /** - * Get resultsDisplayed + * Get resultsDisplayed. * - * @return integer + * @return int */ public function getResultsDisplayed() { @@ -99,22 +103,23 @@ public function getResultsDisplayed() } /** - * Set displayedColumns + * Set displayedColumns. * * @param array $displayedColumns + * * @return UserCrudSettings */ public function setDisplayedColumns($displayedColumns) { $this->displayedColumns = $displayedColumns; - + return $this; } /** - * Get displayedColumns + * Get displayedColumns. * - * @return array + * @return array */ public function getDisplayedColumns() { @@ -122,22 +127,23 @@ public function getDisplayedColumns() } /** - * Set sort + * Set sort. * * @param string $sort + * * @return UserCrudSettings */ public function setSort($sort) { $this->sort = $sort; - + return $this; } /** - * Get sort + * Get sort. * - * @return string + * @return string */ public function getSort() { @@ -145,22 +151,23 @@ public function getSort() } /** - * Set sense + * Set sense. * * @param string $sense + * * @return UserCrudSettings */ public function setSense($sense) { $this->sense = $sense; - + return $this; } /** - * Get sense + * Get sense. * - * @return string + * @return string */ public function getSense() { @@ -168,32 +175,30 @@ public function getSense() } /** - * Set user + * Set user. * - * @param \Ecommit\CrudBundle\Entity\UserCrudInterface $user * @return UserCrudSettings */ public function setUser(\Ecommit\CrudBundle\Entity\UserCrudInterface $user) { $this->user = $user; - + return $this; } /** - * Get user + * Get user. * - * @return \Ecommit\CrudBundle\Entity\UserCrudInterface + * @return \Ecommit\CrudBundle\Entity\UserCrudInterface */ public function getUser() { return $this->user; } - + /** - * Create CrudSession from this object - * - * @param \Ecommit\CrudBundle\Crud\CrudSession $crudSessionManager + * Create CrudSession from this object. + * * @return \Ecommit\CrudBundle\Crud\CrudSession */ public function transformToCrudSession(CrudSession $crudSessionManager) @@ -202,16 +207,14 @@ public function transformToCrudSession(CrudSession $crudSessionManager) $crudSessionManager->resultsPerPage = $this->resultsDisplayed; $crudSessionManager->sense = $this->sense; $crudSessionManager->sort = $this->sort; - + return $crudSessionManager; } - + /** - * Update this object from CrudSession - * - * @param \Ecommit\CrudBundle\Crud\CrudSession $crudSessionManager + * Update this object from CrudSession. */ - public function updateFromSessionManager(CrudSession $crudSessionManager) + public function updateFromSessionManager(CrudSession $crudSessionManager): void { $this->displayedColumns = $crudSessionManager->displayedColumns; $this->resultsDisplayed = $crudSessionManager->resultsPerPage; diff --git a/src/EventListener/MappingEntities.php b/src/EventListener/MappingEntities.php index 742f3f0..cfafe2e 100644 --- a/src/EventListener/MappingEntities.php +++ b/src/EventListener/MappingEntities.php @@ -1,5 +1,8 @@ @@ -17,7 +20,7 @@ class MappingEntities { protected $isLoad = false; - public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs) + public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs): void { $metadata = $eventArgs->getClassMetadata(); @@ -36,22 +39,22 @@ public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs) } } - protected function mappUserCrudSettings(ClassMetadataInfo $userCrudSettingsMetadata, ClassMetadataInfo $userMetadata) + protected function mappUserCrudSettings(ClassMetadataInfo $userCrudSettingsMetadata, ClassMetadataInfo $userMetadata): void { $this->isLoad = true; $userCrudSettingsMetadata->setAssociationOverride( 'user', - array( + [ 'targetEntity' => $userMetadata->getName(), 'fieldName' => 'user', 'id' => true, - 'joinColumns' => array(array( + 'joinColumns' => [[ 'name' => 'user_id', 'referencedColumnName' => $userMetadata->getSingleIdentifierColumnName(), 'onDelete' => 'CASCADE', - )) - ) + ]], + ] ); } -} +} diff --git a/src/Form/Filter/AbstractFieldFilter.php b/src/Form/Filter/AbstractFieldFilter.php index d6481ce..f3c886a 100644 --- a/src/Form/Filter/AbstractFieldFilter.php +++ b/src/Form/Filter/AbstractFieldFilter.php @@ -1,5 +1,7 @@ columnId = $columnId; $this->property = $property; @@ -42,7 +44,7 @@ public function __construct($columnId, $property, $options = array(), $typeOptio $this->typeOptions = $typeOptions; } - public function init() + public function init(): void { if ($this->isInitiated) { return; @@ -60,27 +62,22 @@ public function init() $this->isInitiated = true; } - /** - * @param OptionsResolver $resolver - */ - protected function configureCommonOptions(OptionsResolver $resolver) + protected function configureCommonOptions(OptionsResolver $resolver): void { $resolver->setDefaults( - array( + [ 'validate' => true, - ) + ] ); } - /** - * @param OptionsResolver $resolver - */ - protected function configureOptions(OptionsResolver $resolver) + protected function configureOptions(OptionsResolver $resolver): void { } /** * @param array $typeOptions + * * @return array */ protected function configureTypeOptions($typeOptions) @@ -89,33 +86,34 @@ protected function configureTypeOptions($typeOptions) } /** - * Adds the field into the form - * @param FormBuilder $formBuilder + * Adds the field into the form. + * * @return FormBuilder */ abstract public function addField(FormBuilder $formBuilder); /** - * Changes the query + * Changes the query. + * * @param QueryBuilder $queryBuilder - * @param AbstractFormSearcher $formData - * @param string $aliasSearch + * @param string $aliasSearch + * * @return QueryBuilder */ abstract public function changeQuery($queryBuilder, AbstractFormSearcher $formData, $aliasSearch); /** - * Add auto validation + * Add auto validation. + * * @param $value - * @param ExecutionContextInterface $context */ - public function autoValidate(AbstractFormSearcher $value, ExecutionContextInterface $context) + public function autoValidate(AbstractFormSearcher $value, ExecutionContextInterface $context): void { if (!$this->options['validate']) { return; } $autoConstraints = $this->getAutoConstraints(); - if (count($autoConstraints) == 0) { + if (0 == \count($autoConstraints)) { return; } $context->getValidator() @@ -125,16 +123,17 @@ public function autoValidate(AbstractFormSearcher $value, ExecutionContextInterf } /** - * Gets auto constraints list + * Gets auto constraints list. + * * @return array */ protected function getAutoConstraints() { - return array(); + return []; } /** - * Returns the column id associated at this object + * Returns the column id associated at this object. * * @return string */ @@ -144,7 +143,7 @@ public function getColumnId() } /** - * Returns the property associated at this object + * Returns the property associated at this object. * * @return string */ @@ -155,9 +154,9 @@ public function getProperty() /** * @param string $label - * @param bool $displayLabelInErrors + * @param bool $displayLabelInErrors */ - public function setLabel($label, $displayLabelInErrors = false) + public function setLabel($label, $displayLabelInErrors = false): void { if (!empty($label) && !isset($this->typeOptions['label'])) { $this->typeOptions['label'] = $label; diff --git a/src/Form/Filter/FieldFilterBoolean.php b/src/Form/Filter/FieldFilterBoolean.php index 949bbd3..6c85175 100644 --- a/src/Form/Filter/FieldFilterBoolean.php +++ b/src/Form/Filter/FieldFilterBoolean.php @@ -1,5 +1,7 @@ setDefaults( - array( + [ 'value_true' => 1, 'value_false' => 0, 'not_null_is_true' => false, 'null_is_false' => true, - ) + ] ); } /** - * {@inheritDoc} + * {@inheritdoc} */ protected function configureTypeOptions($typeOptions) { @@ -48,7 +50,7 @@ protected function configureTypeOptions($typeOptions) } /** - * {@inheritDoc} + * {@inheritdoc} */ public function addField(FormBuilder $formBuilder) { @@ -58,7 +60,7 @@ public function addField(FormBuilder $formBuilder) } /** - * {@inheritDoc} + * {@inheritdoc} */ public function changeQuery($queryBuilder, AbstractFormSearcher $formData, $aliasSearch) { @@ -67,10 +69,10 @@ public function changeQuery($queryBuilder, AbstractFormSearcher $formData, $alia return $queryBuilder; } - if ($value == 'T') { - $parameterName = 'value_true' . str_replace(' ', '', $this->property); + if ('T' == $value) { + $parameterName = 'value_true'.str_replace(' ', '', $this->property); if ($this->options['not_null_is_true']) { - $parameterNameFalse = 'value_false' . str_replace(' ', '', $this->property); + $parameterNameFalse = 'value_false'.str_replace(' ', '', $this->property); $queryBuilder->andWhere( sprintf( '(%s = :%s OR (%s IS NOT NULL AND %s != :%s))', @@ -84,14 +86,14 @@ public function changeQuery($queryBuilder, AbstractFormSearcher $formData, $alia ->setParameter($parameterName, $this->options['value_true']) ->setParameter($parameterNameFalse, $this->options['value_false']); } else { - $queryBuilder->andWhere(sprintf('%s = :%s',$aliasSearch, $parameterName)) + $queryBuilder->andWhere(sprintf('%s = :%s', $aliasSearch, $parameterName)) ->setParameter($parameterName, $this->options['value_true']); } return $queryBuilder; - } elseif ($value == 'F') { - $parameterName = 'value_false' . str_replace(' ', '', $this->property); - if (is_null($this->options['value_false'])) { + } elseif ('F' == $value) { + $parameterName = 'value_false'.str_replace(' ', '', $this->property); + if (null === $this->options['value_false']) { $queryBuilder->andWhere(sprintf('%s IS NULL', $aliasSearch)); } elseif ($this->options['null_is_false']) { $queryBuilder->andWhere( @@ -108,17 +110,17 @@ public function changeQuery($queryBuilder, AbstractFormSearcher $formData, $alia ->setParameter($parameterName, $this->options['value_false']); } - return $queryBuilder; - } else { return $queryBuilder; } + + return $queryBuilder; } public static function getChoices() { - return array( + return [ 'filter.true' => 'T', 'filter.false' => 'F', - ); + ]; } } diff --git a/src/Form/Filter/FieldFilterChoice.php b/src/Form/Filter/FieldFilterChoice.php index 3a89b0e..8e55f48 100644 --- a/src/Form/Filter/FieldFilterChoice.php +++ b/src/Form/Filter/FieldFilterChoice.php @@ -1,5 +1,7 @@ setDefaults( - array( + [ 'multiple' => false, 'min' => null, 'max' => 99, - ) + ] ); } /** - * {@inheritDoc} + * {@inheritdoc} */ - protected function configureOptions(OptionsResolver $resolver) + protected function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults( - array( + [ 'choices' => null, - ) + ] ); } /** - * {@inheritDoc} + * {@inheritdoc} */ protected function configureTypeOptions($typeOptions) { @@ -63,9 +65,8 @@ protected function configureTypeOptions($typeOptions) return $typeOptions; } - /** - * {@inheritDoc} + * {@inheritdoc} */ public function addField(FormBuilder $formBuilder) { @@ -75,44 +76,44 @@ public function addField(FormBuilder $formBuilder) } /** - * {@inheritDoc} + * {@inheritdoc} */ protected function getAutoConstraints() { if ($this->options['multiple']) { - return array( + return [ new Assert\Count( - array( + [ 'min' => $this->options['min'], 'max' => $this->options['max'], - ) + ] ), - ); - } else { - return array(); + ]; } + + return []; } /** - * {@inheritDoc} + * {@inheritdoc} */ public function changeQuery($queryBuilder, AbstractFormSearcher $formData, $aliasSearch) { $value = $formData->get($this->property); - $parameterName = 'value_choice' . str_replace(' ', '', $this->property); - if (null === $value || '' === $value || array() === $value) { + $parameterName = 'value_choice'.str_replace(' ', '', $this->property); + if (null === $value || '' === $value || [] === $value) { return $queryBuilder; } if ($this->options['multiple']) { - if (!is_array($value)) { - $value = array($value); + if (!\is_array($value)) { + $value = [$value]; } $value = Util::filterScalarValues($value); - if (count($value) > $this->options['max'] || 0 === count($value)) { + if (\count($value) > $this->options['max'] || 0 === \count($value)) { return $queryBuilder; } - if ($this->options['min'] && count($value) < $this->options['min']) { + if ($this->options['min'] && \count($value) < $this->options['min']) { return $queryBuilder; } QueryBuilderFilter::addMultiFilter($queryBuilder, QueryBuilderFilter::SELECT_IN, $value, $aliasSearch, $parameterName); diff --git a/src/Form/Filter/FieldFilterDate.php b/src/Form/Filter/FieldFilterDate.php index 2afe167..c307e1a 100644 --- a/src/Form/Filter/FieldFilterDate.php +++ b/src/Form/Filter/FieldFilterDate.php @@ -1,5 +1,7 @@ '; - const GREATER_EQUAL = '>='; - const SMALLER_THAN = '<'; - const SMALLER_EQUAL = '<='; - const EQUAL = '='; + public const GREATER_THAN = '>'; + public const GREATER_EQUAL = '>='; + public const SMALLER_THAN = '<'; + public const SMALLER_EQUAL = '<='; + public const EQUAL = '='; /** - * {@inheritDoc} + * {@inheritdoc} */ - protected function configureOptions(OptionsResolver $resolver) + protected function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults( - array( + [ 'type' => JqueryDatePickerType::class, 'with_time' => function (Options $options) { if (DateTimeType::class === $options['type']) { @@ -43,33 +45,33 @@ protected function configureOptions(OptionsResolver $resolver) return false; }, - ) + ] ); $resolver->setRequired( - array( + [ 'comparator', - ) + ] ); $resolver->setAllowedValues( 'comparator', - array( + [ self::EQUAL, self::GREATER_EQUAL, self::GREATER_THAN, self::SMALLER_EQUAL, self::SMALLER_THAN, - ) + ] ); $resolver->setAllowedValues( 'type', - array( + [ DateType::class, DateTimeType::class, JqueryDatePickerType::class, - ) + ] ); } @@ -85,7 +87,7 @@ protected function configureTypeOptions($typeOptions) } /** - * {@inheritDoc} + * {@inheritdoc} */ public function addField(FormBuilder $formBuilder) { @@ -95,59 +97,59 @@ public function addField(FormBuilder $formBuilder) } /** - * {@inheritDoc} + * {@inheritdoc} */ protected function getAutoConstraints() { - return array( + return [ new Assert\Date(), - ); + ]; } /** - * {@inheritDoc} + * {@inheritdoc} */ public function changeQuery($queryBuilder, AbstractFormSearcher $formData, $aliasSearch) { $value = $formData->get($this->property); if (!empty($value) && $value instanceof \DateTime) { - $parameterName = 'value_date_' . str_replace(' ', '', $this->property); + $parameterName = 'value_date_'.str_replace(' ', '', $this->property); switch ($this->options['comparator']): - case FieldFilterDate::SMALLER_THAN: - case FieldFilterDate::GREATER_EQUAL: + case self::SMALLER_THAN: + case self::GREATER_EQUAL: if (!$this->options['with_time']) { $value->setTime(0, 0, 0); } - $value = $value->format('Y-m-d H:i:s'); - $queryBuilder->andWhere( + $value = $value->format('Y-m-d H:i:s'); + $queryBuilder->andWhere( sprintf('%s %s :%s', $aliasSearch, $this->options['comparator'], $parameterName) ) ->setParameter($parameterName, $value); - break; - case FieldFilterDate::SMALLER_EQUAL: - case FieldFilterDate::GREATER_THAN: + break; + case self::SMALLER_EQUAL: + case self::GREATER_THAN: if (!$this->options['with_time']) { $value->setTime(23, 59, 59); } - $value = $value->format('Y-m-d H:i:s'); - $queryBuilder->andWhere( + $value = $value->format('Y-m-d H:i:s'); + $queryBuilder->andWhere( sprintf('%s %s :%s', $aliasSearch, $this->options['comparator'], $parameterName) ) ->setParameter($parameterName, $value); - break; - default: + break; + default: $valueDateInf = clone $value; - $valueDateSup = clone $value; - if (!$this->options['with_time']) { - $valueDateInf->setTime(0, 0, 0); - $valueDateSup->setTime(23, 59, 59); - } - $valueDateInf = $valueDateInf->format('Y-m-d H:i:s'); - $valueDateSup = $valueDateSup->format('Y-m-d H:i:s'); - $parameterNameInf = 'value_date_inf_' . str_replace(' ', '', $this->property); - $parameterNameSup = 'value_date_sup_' . str_replace(' ', '', $this->property); - $queryBuilder->andWhere( + $valueDateSup = clone $value; + if (!$this->options['with_time']) { + $valueDateInf->setTime(0, 0, 0); + $valueDateSup->setTime(23, 59, 59); + } + $valueDateInf = $valueDateInf->format('Y-m-d H:i:s'); + $valueDateSup = $valueDateSup->format('Y-m-d H:i:s'); + $parameterNameInf = 'value_date_inf_'.str_replace(' ', '', $this->property); + $parameterNameSup = 'value_date_sup_'.str_replace(' ', '', $this->property); + $queryBuilder->andWhere( sprintf( '%s >= :%s AND %s <= :%s', $aliasSearch, @@ -158,7 +160,7 @@ public function changeQuery($queryBuilder, AbstractFormSearcher $formData, $alia ) ->setParameter($parameterNameInf, $valueDateInf) ->setParameter($parameterNameSup, $valueDateSup); - break; + break; endswitch; } diff --git a/src/Form/Filter/FieldFilterDoctrineInterface.php b/src/Form/Filter/FieldFilterDoctrineInterface.php index d2cb9a7..f1d8bc0 100644 --- a/src/Form/Filter/FieldFilterDoctrineInterface.php +++ b/src/Form/Filter/FieldFilterDoctrineInterface.php @@ -1,5 +1,7 @@ andWhere( sprintf('(%s IS NULL OR %s = \'\')', $aliasSearch, $aliasSearch) ); diff --git a/src/Form/Filter/FieldFilterEntity.php b/src/Form/Filter/FieldFilterEntity.php index d7e34e5..36addfd 100644 --- a/src/Form/Filter/FieldFilterEntity.php +++ b/src/Form/Filter/FieldFilterEntity.php @@ -1,5 +1,7 @@ setDefaults( - array( + [ 'choice_label' => null, 'em' => null, 'query_builder' => null, 'identifier' => null, - ) + ] ); $resolver->setRequired( - array( + [ 'class', - ) + ] ); $resolver->setNormalizer('em', $this->getEmNormalizer($this->registry)); @@ -60,7 +62,7 @@ protected function configureTypeOptions($typeOptions) $queryBuilderLoader = new ORMQueryBuilderLoader($this->options['query_builder']); $accessor = PropertyAccess::createPropertyAccessor(); - $choices = array(); + $choices = []; foreach ($queryBuilderLoader->getEntities() as $entity) { $id = $accessor->getValue($entity, $this->options['identifier']); $choices[$this->extractLabel($entity)] = $id; @@ -75,8 +77,10 @@ protected function configureTypeOptions($typeOptions) } /** - * Extract property that should be used for displaying the entities as text in the HTML element + * Extract property that should be used for displaying the entities as text in the HTML element. + * * @param object $object + * * @throws \Exception */ protected function extractLabel($object) @@ -86,10 +90,9 @@ protected function extractLabel($object) return $accessor->getValue($object, $this->options['choice_label']); } elseif (method_exists($object, '__toString')) { - return (string)$object; - } else { - throw new \Exception('"choice_label" option or "__toString" method must be defined"'); + return (string) $object; } + throw new \Exception('"choice_label" option or "__toString" method must be defined"'); } public function getRegistry() @@ -97,7 +100,7 @@ public function getRegistry() return $this->registry; } - public function setRegistry(ManagerRegistry $registry) + public function setRegistry(ManagerRegistry $registry): void { $this->registry = $registry; } diff --git a/src/Form/Filter/FieldFilterInteger.php b/src/Form/Filter/FieldFilterInteger.php index 49cdb14..2c7e091 100644 --- a/src/Form/Filter/FieldFilterInteger.php +++ b/src/Form/Filter/FieldFilterInteger.php @@ -1,5 +1,7 @@ '; - const GREATER_EQUAL = '>='; - const SMALLER_THAN = '<'; - const SMALLER_EQUAL = '<='; - const EQUAL = '='; + public const GREATER_THAN = '>'; + public const GREATER_EQUAL = '>='; + public const SMALLER_THAN = '<'; + public const SMALLER_EQUAL = '<='; + public const EQUAL = '='; /** - * {@inheritDoc} + * {@inheritdoc} */ - protected function configureOptions(OptionsResolver $resolver) + protected function configureOptions(OptionsResolver $resolver): void { $resolver->setRequired( - array( + [ 'comparator', - ) + ] ); $resolver->setAllowedValues( 'comparator', - array( + [ self::EQUAL, self::GREATER_EQUAL, self::GREATER_THAN, self::SMALLER_EQUAL, self::SMALLER_THAN, - ) + ] ); } /** - * {@inheritDoc} + * {@inheritdoc} */ public function addField(FormBuilder $formBuilder) { @@ -58,13 +60,13 @@ public function addField(FormBuilder $formBuilder) } /** - * {@inheritDoc} + * {@inheritdoc} */ public function changeQuery($queryBuilder, AbstractFormSearcher $formData, $aliasSearch) { $value = $formData->get($this->property); - if (!is_null($value) && is_numeric($value)) { //Important: Is_null but not is_empty - $parameterName = 'value_integer_' . str_replace(' ', '', $this->property); + if (null !== $value && is_numeric($value)) { //Important: Is_null but not is_empty + $parameterName = 'value_integer_'.str_replace(' ', '', $this->property); $queryBuilder->andWhere( sprintf('%s %s :%s', $aliasSearch, $this->options['comparator'], $parameterName) ) diff --git a/src/Form/Filter/FieldFilterJqueryAutocompleteEntityAjax.php b/src/Form/Filter/FieldFilterJqueryAutocompleteEntityAjax.php index 144b5aa..8486ced 100644 --- a/src/Form/Filter/FieldFilterJqueryAutocompleteEntityAjax.php +++ b/src/Form/Filter/FieldFilterJqueryAutocompleteEntityAjax.php @@ -1,5 +1,8 @@ @@ -19,12 +22,12 @@ class FieldFilterJqueryAutocompleteEntityAjax extends AbstractFieldFilter { /** - * {@inheritDoc} + * {@inheritdoc} */ - protected function configureOptions(OptionsResolver $resolver) + protected function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults( - array( + [ 'em' => null, 'query_builder' => null, 'choice_label' => null, @@ -33,23 +36,23 @@ protected function configureOptions(OptionsResolver $resolver) 'route_name' => null, 'route_params' => null, 'max_length' => 255, - ) + ] ); $resolver->setRequired( - array( + [ 'class', - ) + ] ); } /** - * {@inheritDoc} + * {@inheritdoc} */ protected function configureTypeOptions($typeOptions) { foreach ($this->options as $optionName => $optionValue) { - if (!empty($optionValue) && !in_array($optionName, array('validate', 'max_length'))) { + if (!empty($optionValue) && !\in_array($optionName, ['validate', 'max_length'])) { $typeOptions[$optionName] = $optionValue; } } @@ -59,7 +62,7 @@ protected function configureTypeOptions($typeOptions) } /** - * {@inheritDoc} + * {@inheritdoc} */ public function addField(FormBuilder $formBuilder) { @@ -69,26 +72,26 @@ public function addField(FormBuilder $formBuilder) } /** - * {@inheritDoc} + * {@inheritdoc} */ protected function getAutoConstraints() { - return array( + return [ new Assert\Length( - array( + [ 'max' => $this->options['max_length'], - ) + ] ), - ); + ]; } /** - * {@inheritDoc} + * {@inheritdoc} */ public function changeQuery($queryBuilder, AbstractFormSearcher $formData, $aliasSearch) { $value = $formData->get($this->property); - $parameterName = 'value_jquery_auto' . str_replace(' ', '', $this->property); + $parameterName = 'value_jquery_auto'.str_replace(' ', '', $this->property); if (null === $value || '' === $value || !is_scalar($value)) { return $queryBuilder; } diff --git a/src/Form/Filter/FieldFilterNumber.php b/src/Form/Filter/FieldFilterNumber.php index 5de370a..2c96ea7 100644 --- a/src/Form/Filter/FieldFilterNumber.php +++ b/src/Form/Filter/FieldFilterNumber.php @@ -1,5 +1,7 @@ @@ -16,11 +19,11 @@ class FieldFilterSelect2Choice extends FieldFilterChoice { /** - * {@inheritDoc} + * {@inheritdoc} */ public function addField(FormBuilder $formBuilder) { - $formBuilder->add($this->property, Select2ChoiceType::class , $this->typeOptions); + $formBuilder->add($this->property, Select2ChoiceType::class, $this->typeOptions); return $formBuilder; } diff --git a/src/Form/Filter/FieldFilterSelect2Country.php b/src/Form/Filter/FieldFilterSelect2Country.php index b57932a..f2b91e0 100644 --- a/src/Form/Filter/FieldFilterSelect2Country.php +++ b/src/Form/Filter/FieldFilterSelect2Country.php @@ -1,5 +1,8 @@ @@ -16,11 +19,11 @@ class FieldFilterSelect2Country extends FieldFilterChoice { /** - * {@inheritDoc} + * {@inheritdoc} */ public function addField(FormBuilder $formBuilder) { - $formBuilder->add($this->property, Select2CountryType::class , $this->typeOptions); + $formBuilder->add($this->property, Select2CountryType::class, $this->typeOptions); return $formBuilder; } diff --git a/src/Form/Filter/FieldFilterSelect2Entity.php b/src/Form/Filter/FieldFilterSelect2Entity.php index 05ef219..a219774 100644 --- a/src/Form/Filter/FieldFilterSelect2Entity.php +++ b/src/Form/Filter/FieldFilterSelect2Entity.php @@ -1,5 +1,8 @@ @@ -16,7 +19,7 @@ class FieldFilterSelect2Entity extends FieldFilterEntity { /** - * {@inheritDoc} + * {@inheritdoc} */ public function addField(FormBuilder $formBuilder) { diff --git a/src/Form/Filter/FieldFilterSelect2EntityAjax.php b/src/Form/Filter/FieldFilterSelect2EntityAjax.php index 06c0b5e..8a493a9 100644 --- a/src/Form/Filter/FieldFilterSelect2EntityAjax.php +++ b/src/Form/Filter/FieldFilterSelect2EntityAjax.php @@ -1,5 +1,8 @@ @@ -17,12 +20,12 @@ class FieldFilterSelect2EntityAjax extends FieldFilterChoice { /** - * {@inheritDoc} + * {@inheritdoc} */ - protected function configureOptions(OptionsResolver $resolver) + protected function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults( - array( + [ 'em' => null, 'query_builder' => null, 'choice_label' => null, @@ -30,23 +33,23 @@ protected function configureOptions(OptionsResolver $resolver) 'url' => null, //Required in FormType if route_name is empty 'route_name' => null, 'route_params' => null, - ) + ] ); $resolver->setRequired( - array( + [ 'class', - ) + ] ); } /** - * {@inheritDoc} + * {@inheritdoc} */ protected function configureTypeOptions($typeOptions) { foreach ($this->options as $optionName => $optionValue) { - if (!empty($optionValue) && !in_array($optionName, array('validate', 'min'))) { + if (!empty($optionValue) && !\in_array($optionName, ['validate', 'min'])) { $typeOptions[$optionName] = $optionValue; } } @@ -56,7 +59,7 @@ protected function configureTypeOptions($typeOptions) } /** - * {@inheritDoc} + * {@inheritdoc} */ public function addField(FormBuilder $formBuilder) { diff --git a/src/Form/Filter/FieldFilterText.php b/src/Form/Filter/FieldFilterText.php index 8dfd7b3..18bc8cb 100644 --- a/src/Form/Filter/FieldFilterText.php +++ b/src/Form/Filter/FieldFilterText.php @@ -1,5 +1,7 @@ setDefaults( - array( + [ 'must_begin' => false, 'must_end' => false, 'min_length' => null, 'max_length' => 255, - ) + ] ); } /** - * {@inheritDoc} + * {@inheritdoc} */ public function addField(FormBuilder $formBuilder) { @@ -45,27 +47,27 @@ public function addField(FormBuilder $formBuilder) } /** - * {@inheritDoc} + * {@inheritdoc} */ protected function getAutoConstraints() { - return array( + return [ new Assert\Length( - array( + [ 'min' => $this->options['min_length'], 'max' => $this->options['max_length'], - ) + ] ), - ); + ]; } /** - * {@inheritDoc} + * {@inheritdoc} */ public function changeQuery($queryBuilder, AbstractFormSearcher $formData, $aliasSearch) { $value = $formData->get($this->property); - $parameterName = 'value_text_' . str_replace(' ', '', $this->property); + $parameterName = 'value_text_'.str_replace(' ', '', $this->property); if (null === $value || '' === $value || !is_scalar($value)) { return $queryBuilder; } @@ -77,9 +79,9 @@ public function changeQuery($queryBuilder, AbstractFormSearcher $formData, $alia $after = ($this->options['must_begin']) ? '' : '%'; $before = ($this->options['must_end']) ? '' : '%'; $value = addcslashes($value, '%_'); - $like = $after . $value . $before; + $like = $after.$value.$before; $queryBuilder->andWhere( - $queryBuilder->expr()->like($aliasSearch, ':' . $parameterName) + $queryBuilder->expr()->like($aliasSearch, ':'.$parameterName) ) ->setParameter($parameterName, $like); } diff --git a/src/Form/Filter/FieldFilterTokenInputEntitiesAjax.php b/src/Form/Filter/FieldFilterTokenInputEntitiesAjax.php index 7364b18..b17447b 100644 --- a/src/Form/Filter/FieldFilterTokenInputEntitiesAjax.php +++ b/src/Form/Filter/FieldFilterTokenInputEntitiesAjax.php @@ -1,5 +1,8 @@ @@ -20,12 +23,12 @@ class FieldFilterTokenInputEntitiesAjax extends AbstractFieldFilter { /** - * {@inheritDoc} + * {@inheritdoc} */ - protected function configureOptions(OptionsResolver $resolver) + protected function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults( - array( + [ 'em' => null, 'query_builder' => null, 'choice_label' => null, @@ -35,23 +38,23 @@ protected function configureOptions(OptionsResolver $resolver) 'route_params' => null, 'min' => null, 'max' => 99, - ) + ] ); $resolver->setRequired( - array( + [ 'class', - ) + ] ); } /** - * {@inheritDoc} + * {@inheritdoc} */ protected function configureTypeOptions($typeOptions) { foreach ($this->options as $optionName => $optionValue) { - if (!empty($optionValue) && !in_array($optionName, array('validate', 'min'))) { + if (!empty($optionValue) && !\in_array($optionName, ['validate', 'min'])) { $typeOptions[$optionName] = $optionValue; } } @@ -61,7 +64,7 @@ protected function configureTypeOptions($typeOptions) } /** - * {@inheritDoc} + * {@inheritdoc} */ public function addField(FormBuilder $formBuilder) { @@ -71,42 +74,43 @@ public function addField(FormBuilder $formBuilder) } /** - * {@inheritDoc} + * {@inheritdoc} */ protected function getAutoConstraints() { - return array( + return [ new Assert\Count( - array( + [ 'min' => $this->options['min'], 'max' => $this->options['max'], - ) + ] ), - ); + ]; } /** - * {@inheritDoc} + * {@inheritdoc} */ public function changeQuery($queryBuilder, AbstractFormSearcher $formData, $aliasSearch) { $value = $formData->get($this->property); - $parameterName = 'value_choice' . str_replace(' ', '', $this->property); - if (null === $value || '' === $value || !is_array($value)) { + $parameterName = 'value_choice'.str_replace(' ', '', $this->property); + if (null === $value || '' === $value || !\is_array($value)) { return $queryBuilder; } $value = Util::filterScalarValues($value); - if (0 === count($value)) { + if (0 === \count($value)) { return $queryBuilder; } - if (count($value) > $this->options['max']) { + if (\count($value) > $this->options['max']) { return $queryBuilder; } - if ($this->options['min'] && count($value) < $this->options['min']) { + if ($this->options['min'] && \count($value) < $this->options['min']) { return $queryBuilder; } - return $queryBuilder->andWhere($queryBuilder->expr()->in($aliasSearch, ':' . $parameterName)) + + return $queryBuilder->andWhere($queryBuilder->expr()->in($aliasSearch, ':'.$parameterName)) ->setParameter($parameterName, $value); } } diff --git a/src/Form/Searcher/AbstractFormSearcher.php b/src/Form/Searcher/AbstractFormSearcher.php index fc22029..419e24e 100644 --- a/src/Form/Searcher/AbstractFormSearcher.php +++ b/src/Form/Searcher/AbstractFormSearcher.php @@ -1,5 +1,7 @@ fieldFilters); unset($this->accessor); @@ -76,27 +79,27 @@ public function clear() * Sub method of "clear" method * Remove properties if they are not fields * If one property is not public and it doesn't begin - * by "field", it will be deleted + * by "field", it will be deleted. */ - protected function clearExceptFields() + protected function clearExceptFields(): void { foreach ($this as $key => $value) { $variable = new \ReflectionProperty($this, $key); - if (!$variable->isPublic() && !\preg_match('/^field/', $key)) { + if (!$variable->isPublic() && !preg_match('/^field/', $key)) { unset($this->$key); } } } /** - * Returns fields + * Returns fields. * * @return array */ public function getFieldsFilter($registry = null) { if (!$this->fieldFilters) { - $this->fieldFilters = array(); + $this->fieldFilters = []; foreach ($this->configureFieldsFilter() as $field) { $this->fieldFilters[] = $field; if (!empty($registry) && $field instanceof \Ecommit\CrudBundle\Form\Filter\FieldFilterDoctrineInterface) { @@ -110,19 +113,16 @@ public function getFieldsFilter($registry = null) } /** - * Sets fields - * - * @param array $filters + * Sets fields. */ - public function setFieldsFilter(array $filters) + public function setFieldsFilter(array $filters): void { $this->fieldFilters = $filters; } /** - * Changes the form (global change) + * Changes the form (global change). * - * @param \Symfony\Component\Form\FormBuilderInterface $formBuilder * @return \Symfony\Component\Form\FormBuilderInterface */ public function globalBuildForm(FormBuilderInterface $formBuilder) @@ -131,9 +131,10 @@ public function globalBuildForm(FormBuilderInterface $formBuilder) } /** - * Changes the query (global change) + * Changes the query (global change). * * @param QueryBuilder $queryBuilder + * * @return QueryBuilder */ public function globalChangeQuery($queryBuilder) @@ -142,7 +143,8 @@ public function globalChangeQuery($queryBuilder) } /** - * Returns true if auto validation is enabled + * Returns true if auto validation is enabled. + * * @return bool */ public function automaticValidationIsEnabled() @@ -151,7 +153,8 @@ public function automaticValidationIsEnabled() } /** - * Returns true if labels are displayed in errors messages + * Returns true if labels are displayed in errors messages. + * * @return bool */ public function displayLabelInErrors() @@ -159,12 +162,12 @@ public function displayLabelInErrors() return false; } - public static function loadValidatorMetadata(ClassMetadata $metadata) + public static function loadValidatorMetadata(ClassMetadata $metadata): void { $metadata->addConstraint(new Callback('validateFormSearcher')); } - public static function validateFormSearcher($value, ExecutionContextInterface $context) + public static function validateFormSearcher($value, ExecutionContextInterface $context): void { if ($value->automaticValidationIsEnabled()) { foreach ($value->getFieldsFilter() as $field) { @@ -181,6 +184,7 @@ protected function getAccessor() if (!isset($this->accessor) || !$this->accessor) { $this->accessor = PropertyAccess::createPropertyAccessor(); } + return $this->accessor; } @@ -195,7 +199,7 @@ public function getCommonOptions() /** * @param array $commonOptions */ - public function setCommonOptions($commonOptions) + public function setCommonOptions($commonOptions): void { $this->commonOptions = $commonOptions; } diff --git a/src/Form/Type/DisplaySettingsType.php b/src/Form/Type/DisplaySettingsType.php index ce314d1..4ba8a70 100644 --- a/src/Form/Type/DisplaySettingsType.php +++ b/src/Form/Type/DisplaySettingsType.php @@ -1,5 +1,7 @@ add( 'resultsPerPage', ChoiceType::class, - array( + [ 'choices' => array_flip($options['resultsPerPageChoices']), 'label' => 'Number of results per page', 'choice_translation_domain' => false, - ) + ] ); //Field "displayedColumns" $builder->add( 'displayedColumns', ChoiceType::class, - array( + [ 'choices' => array_flip($options['columnsChoices']), 'multiple' => true, 'expanded' => true, - 'label' => 'Columns to be shown' - ) + 'label' => 'Columns to be shown', + ] ); } /** - * {@inheritDoc} + * {@inheritdoc} */ - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults( - array( + [ 'csrf_protection' => false, - ) + ] ); $resolver->setRequired( - array( + [ 'resultsPerPageChoices', 'columnsChoices', - ) + ] ); } /** - * {@inheritDoc} + * {@inheritdoc} */ public function getBlockPrefix() { diff --git a/src/Form/Type/FormSearchType.php b/src/Form/Type/FormSearchType.php index 8b8a3cf..23ad991 100644 --- a/src/Form/Type/FormSearchType.php +++ b/src/Form/Type/FormSearchType.php @@ -1,5 +1,7 @@ setDefaults(array( + $resolver->setDefaults([ 'data_class' => 'Ecommit\CrudBundle\Form\Searcher\AbstractFormSearcher', 'csrf_protection' => false, - )); + ]); } - + /** - * {@inheritDoc} + * {@inheritdoc} */ public function getBlockPrefix() { diff --git a/src/Helper/CrudHelper.php b/src/Helper/CrudHelper.php index 6caa0f2..520d1ed 100644 --- a/src/Helper/CrudHelper.php +++ b/src/Helper/CrudHelper.php @@ -1,5 +1,7 @@ " (only if image buttons is used) + * * image_last: Url image ">>" (only if image buttons is used) + * * text_first: Text "<<" (only if text buttons is used) + * * text_previous: Text "<" (only if text buttons is used) + * * text_next: Text ">" (only if text buttons is used) + * * text_last: Text ">>" (only if text buttons is used) + * * use_bootstrap: Use or not bootstap + * * bootstrap_size: Bootstrap nav size (lg sm or null) + * * template: Template used. If null, default template is used * - * @param AbstractPaginator $paginator - * @param string $routeName Route name - * @param array $routeParams Route parameters - * @param array $options Options: - * * ajax_options: Ajax Options. If null, Ajax is not used. Default: null - * * attribute_page: Attribute inside url. Default: page - * * type: Type of links paginator: elastic (all links) or sliding. Default: sliding - * * max_pages_before: Max links before current page (only if sliding type is used). Default: 3 - * * max_pages_after: Max links after current page (only if sliding type is used). Default: 3 - * * buttons: Type of buttons: text or image. Default: text - * * image_first: Url image "<<" (only if image buttons is used) - * * image_previous: Url image "<" (only if image buttons is used) - * * image_next: Url image ">" (only if image buttons is used) - * * image_last: Url image ">>" (only if image buttons is used) - * * text_first: Text "<<" (only if text buttons is used) - * * text_previous: Text "<" (only if text buttons is used) - * * text_next: Text ">" (only if text buttons is used) - * * text_last: Text ">>" (only if text buttons is used) - * * use_bootstrap: Use or not bootstap - * * bootstrap_size: Bootstrap nav size (lg sm or null) - * * template: Template used. If null, default template is used * @return string */ public function paginatorLinks(AbstractPaginator $paginator, $routeName, $routeParams, $options) @@ -154,7 +154,7 @@ public function paginatorLinks(AbstractPaginator $paginator, $routeName, $routeP } $resolver = new OptionsResolver(); $resolver->setDefaults( - array( + [ 'ajax_options' => null, 'attribute_page' => 'page', 'type' => 'sliding', @@ -172,39 +172,39 @@ public function paginatorLinks(AbstractPaginator $paginator, $routeName, $routeP 'use_bootstrap' => $this->useBootstrap, 'bootstrap_size' => null, 'template' => null, - ) + ] ); - $resolver->setAllowedTypes('ajax_options', array('null', 'array')); + $resolver->setAllowedTypes('ajax_options', ['null', 'array']); $resolver->setAllowedTypes('max_pages_before', 'int'); $resolver->setAllowedTypes('max_pages_after', 'int'); - $resolver->setAllowedValues('type', array('sliding', 'elastic')); - $resolver->setAllowedValues('buttons', array('text', 'image')); + $resolver->setAllowedValues('type', ['sliding', 'elastic']); + $resolver->setAllowedValues('buttons', ['text', 'image']); $resolver->setAllowedTypes('use_bootstrap', 'bool'); - $resolver->setAllowedValues('bootstrap_size', array('lg', 'sm', null)); + $resolver->setAllowedValues('bootstrap_size', ['lg', 'sm', null]); $options = $resolver->resolve($options); if ($options['template']) { return $this->templating->render( $options['template'], - array( + [ 'paginator' => $paginator, 'routeName' => $routeName, 'routeParams' => $routeParams, 'options' => $options, - ) + ] ); } $navigation = ''; if ($paginator->haveToPaginate()) { - $navigationClass = $options['use_bootstrap']? 'pagination': 'pagination_nobootstrap'; + $navigationClass = $options['use_bootstrap'] ? 'pagination' : 'pagination_nobootstrap'; if ($options['use_bootstrap'] && $options['bootstrap_size']) { $navigationClass .= ' pagination-'.$options['bootstrap_size']; } - $navigation .= \sprintf(' {% endif %} -{% endspaceless %} +{% endapply %} {% endblock %} {% block paginator_links_content %} @@ -85,7 +85,7 @@ {# th #} {% block th %} -{% spaceless %} +{% apply spaceless %} {% if not column.sortable %} {{ block('th_not_sortable', template_name) }} {% elseif crud.sessionValues.sort != column.id %} @@ -95,7 +95,7 @@ {% else %} {{ block('th_sortable_active_asc', template_name) }} {% endif %} -{% endspaceless %} +{% endapply %} {% endblock %} {% block th_not_sortable %} @@ -144,7 +144,7 @@ {# td #} {% block td %} -{% spaceless %} +{% apply spaceless %} {% if repeatedValue %} {% if td_attr.title is not defined %} {% set td_attr = td_attr|merge({title: value}) %} @@ -154,13 +154,13 @@ {{- (options.escape) ? value : value|raw -}} -{% endspaceless %} +{% endapply %} {% endblock %} {# Display settings #} {% block display_settings %} -{% spaceless %} +{% apply spaceless %} {% if crud.displayResults %} {% set displaySettingsContainerAttributes = {'class': 'ec-crud-display-settings', 'id': 'ec-crud-display-settings-'~crud.sessionName, 'data-crud-list-id': crud.divIdList} %} {% if modal %} @@ -171,7 +171,7 @@ {{ block('display_settings_container_without_modal', template_name) }} {% endif %} {% endif %} -{% endspaceless %} +{% endapply %} {% endblock %} {% block display_settings_button_modal %} @@ -231,29 +231,29 @@ {# search_form_start #} {% block search_form_start %} -{% spaceless %} +{% apply spaceless %} {{ form_start(crud.searchForm, {'attr': form_attr}) }} -{% endspaceless %} +{% endapply %} {% endblock %} {# search_form_submit #} {% block search_form_submit %} - {% spaceless %} + {% apply spaceless %} - {% endspaceless %} + {% endapply %} {% endblock %} {# search_form_reset #} {% block search_form_reset %} -{% spaceless %} +{% apply spaceless %} -{% endspaceless %} +{% endapply %} {% endblock %} {# Attributes #} diff --git a/src/Resources/views/Theme/bootstrap3.html.twig b/src/Resources/views/Theme/bootstrap3.html.twig index de5cbc6..aea6dec 100644 --- a/src/Resources/views/Theme/bootstrap3.html.twig +++ b/src/Resources/views/Theme/bootstrap3.html.twig @@ -86,17 +86,17 @@ {# search_form_submit #} {% block search_form_submit %} -{% spaceless %} +{% apply spaceless %} {% set button_attr = button_attr|default([])|merge({class: (button_attr.class|default('') ~ ' btn btn-default btn-sm')|trim}) %} {{ parent() }} -{% endspaceless %} +{% endapply %} {% endblock %} {# search_form_reset #} {% block search_form_reset %} -{% spaceless %} +{% apply spaceless %} {% set button_attr = button_attr|default([])|merge({class: (button_attr.class|default('') ~ ' btn btn-default btn-sm')|trim}) %} {{ parent() }} -{% endspaceless %} +{% endapply %} {% endblock %} diff --git a/tests/Functional/App/Kernel.php b/tests/Functional/App/Kernel.php index 2dbe1bc..a8009fe 100644 --- a/tests/Functional/App/Kernel.php +++ b/tests/Functional/App/Kernel.php @@ -16,8 +16,6 @@ use Doctrine\Bundle\DoctrineBundle\DoctrineBundle; use Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle; use Ecommit\CrudBundle\EcommitCrudBundle; -use Ecommit\JavascriptBundle\EcommitJavascriptBundle; -use Ecommit\UtilBundle\EcommitUtilBundle; use Symfony\Bundle\FrameworkBundle\FrameworkBundle; use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; use Symfony\Bundle\SecurityBundle\SecurityBundle; @@ -61,8 +59,6 @@ public function registerBundles() new SecurityBundle(), new WebpackEncoreBundle(), new EcommitCrudBundle(), - new EcommitJavascriptBundle(), - new EcommitUtilBundle(), ]; } From 8292b461fb3eb28d14a75afa62dac3e89c6f351b Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sat, 15 May 2021 21:18:19 +0200 Subject: [PATCH 052/264] Add PHP hint in Crud classes --- src/Crud/Crud.php | 199 ++++++--------------------------------- src/Crud/CrudColumn.php | 2 +- src/Crud/CrudFactory.php | 7 +- 3 files changed, 32 insertions(+), 176 deletions(-) diff --git a/src/Crud/Crud.php b/src/Crud/Crud.php index 58c4704..7ab5428 100644 --- a/src/Crud/Crud.php +++ b/src/Crud/Crud.php @@ -77,13 +77,6 @@ class Crud */ protected $container; - /** - * Constructor. - * - * @param string $sessionName Session name - * - * @return Crud - */ public function __construct($sessionName, ContainerInterface $container) { if (!preg_match('/^[a-zA-Z0-9_]{1,50}$/', $sessionName)) { @@ -107,10 +100,8 @@ public function __construct($sessionName, ContainerInterface $container) * * default_displayed: If the column is displayed, by default (Default: true) * * alias_search: Column SQL alias, used during searchs. If null, $alias is used. * * alias_sort: Column(s) SQL alias (string or array of strings), used during sorting. If null, $alias is used. - * - * @return Crud */ - public function addColumn($id, $alias, $label, $options = []) + public function addColumn(string $id, string $alias, string $label, array $options = []): self { if (mb_strlen($id) > 30) { throw new \Exception('Column id is too long'); @@ -148,10 +139,8 @@ public function addColumn($id, $alias, $label, $options = []) * * @param string $id Column id (used everywhere inside the crud) * @param string $aliasSearch column SQL alias, used during searchs - * - * @return Crud */ - public function addVirtualColumn($id, $aliasSearch) + public function addVirtualColumn(string $id, string $aliasSearch): self { $column = new CrudColumn($id, $aliasSearch, null, false, false, null, null); $this->availableVirtualColumns[$id] = $column; @@ -159,24 +148,12 @@ public function addVirtualColumn($id, $aliasSearch) return $this; } - /** - * Gets the query builder. - * - * @return QueryBuilder - */ public function getQueryBuilder() { return $this->queryBuilder; } - /** - * Sets the query builder. - * - * @param QueryBuilder $queryBuilder - * - * @return Crud - */ - public function setQueryBuilder($queryBuilder) + public function setQueryBuilder($queryBuilder): self { if (!($queryBuilder instanceof \Doctrine\ORM\QueryBuilder) && !($queryBuilder instanceof \Doctrine\DBAL\Query\QueryBuilder) && @@ -188,24 +165,12 @@ public function setQueryBuilder($queryBuilder) return $this; } - /** - * Returns available results per page. - * - * @return array - */ - public function getAvailableResultsPerPage() + public function getAvailableResultsPerPage(): array { return $this->availableResultsPerPage; } - /** - * Sets available results per page. - * - * @param int $defaultValue - * - * @return Crud - */ - public function setAvailableResultsPerPage(array $availableResultsPerPage, $defaultValue) + public function setAvailableResultsPerPage(array $availableResultsPerPage, int $defaultValue): self { $this->availableResultsPerPage = $availableResultsPerPage; $this->defaultResultsPerPage = $defaultValue; @@ -218,10 +183,8 @@ public function setAvailableResultsPerPage(array $availableResultsPerPage, $defa * * @param string $sort Column id * @param const $sense Sense (Crud::ASC / Crud::DESC) - * - * @return Crud */ - public function setDefaultSort($sort, $sense) + public function setDefaultSort(string $sort, string $sense): self { $this->defaultSort = $sort; $this->defaultSense = $sense; @@ -235,10 +198,8 @@ public function setDefaultSort($sort, $sense) * @param array $criterias Criterias : * If key is defined: Key = Sort Value = Sense * If key is not defined: Value = Sort - * - * @return Crud */ - public function setDefaultPersonalizedSort(array $criterias) + public function setDefaultPersonalizedSort(array $criterias): self { $this->defaultPersonalizedSort = $criterias; @@ -248,15 +209,7 @@ public function setDefaultPersonalizedSort(array $criterias) return $this; } - /** - * Sets the list route. - * - * @param string $routeName - * @param array $parameters - * - * @return Crud - */ - public function setRoute($routeName, $parameters = []) + public function setRoute(string $routeName, array $parameters = []): self { $this->routeName = $routeName; $this->routeParams = $parameters; @@ -264,22 +217,12 @@ public function setRoute($routeName, $parameters = []) return $this; } - /** - * Returns the route name. - * - * @return string - */ - public function getRouteName() + public function getRouteName(): ?string { return $this->routeName; } - /** - * Returns the route params. - * - * @return array - */ - public function getRouteParams() + public function getRouteParams(): array { return $this->routeParams; } @@ -288,10 +231,8 @@ public function getRouteParams() * Returns the list url. * * @param array $parameters Additional parameters - * - * @return string */ - public function getUrl($parameters = []) + public function getUrl($parameters = []): string { $parameters = array_merge($this->routeParams, $parameters); @@ -302,10 +243,8 @@ public function getUrl($parameters = []) * Returns the search url. * * @param array $parameters Additional parameters - * - * @return string */ - public function getSearchUrl($parameters = []) + public function getSearchUrl($parameters = []): string { $parameters = array_merge($this->routeParams, ['search' => 1], $parameters); @@ -317,7 +256,7 @@ public function getSearchUrl($parameters = []) * * @param bool|closure|array $value */ - public function setBuildPaginator($value) + public function setBuildPaginator($value): self { $this->buildPaginator = $value; @@ -329,7 +268,7 @@ public function setBuildPaginator($value) * * @param bool $value */ - public function setPersistentSettings($value) + public function setPersistentSettings(bool $value): self { if ($value && !($this->container->get('security.token_storage')->getToken()->getUser() instanceof UserInterface)) { $value = false; @@ -456,10 +395,8 @@ protected function save(): void /** * Return default displayed columns. - * - * @return array */ - public function getDefaultDisplayedColumns() + public function getDefaultDisplayedColumns(): array { $columns = []; foreach ($this->availableColumns as $column) { @@ -851,10 +788,8 @@ public function buildQuery(): void /** * Return default results per page. - * - * @return int */ - public function getDefaultResultsPerPage() + public function getDefaultResultsPerPage(): ?int { return $this->defaultResultsPerPage; } @@ -874,20 +809,13 @@ public function clearTemplate(): void /** * Returns availabled columns. - * - * @return array */ - public function getColumns() + public function getColumns(): array { return $this->availableColumns; } - /** - * Returns one column. - * - * @return CrudColumn $columnId - */ - public function getColumn($columnId) + public function getColumn(string $columnId): CrudColumn { if (isset($this->availableColumns[$columnId])) { return $this->availableColumns[$columnId]; @@ -897,20 +825,13 @@ public function getColumn($columnId) /** * Returns availabled virtual columns. - * - * @return array */ - public function getVirtualColumns() + public function getVirtualColumns(): array { return $this->availableVirtualColumns; } - /** - * Returns one virtual column. - * - * @return CrudColumn $columnId - */ - public function getVirtualColumn($columnId) + public function getVirtualColumn(string $columnId): CrudColumn { if (isset($this->availableVirtualColumns[$columnId])) { return $this->availableVirtualColumns[$columnId]; @@ -918,31 +839,16 @@ public function getVirtualColumn($columnId) throw new \Exception('Crud: Column '.$columnId.' does not exist'); } - /** - * Returns user values. - * - * @return CrudSession - */ - public function getSessionValues() + public function getSessionValues(): CrudSession { return $this->sessionValues; } - /** - * Returns the paginator. - * - * @return object - */ public function getPaginator() { return $this->paginator; } - /** - * Sets the paginator. - * - * @param object $value - */ public function setPaginator($value) { $this->paginator = $value; @@ -970,98 +876,53 @@ public function getDisplaySettingsForm() return $this->formDisplaySettings; } - /** - * Returns the div id search. - * - * @return string - */ - public function getDivIdSearch() + public function getDivIdSearch(): string { return $this->divIdSearch; } - /** - * Sets the div id search. - * - * @param string - * - * @return Crud - */ - public function setDivIdSearch($divIdSearch) + public function setDivIdSearch(string $divIdSearch): self { $this->divIdSearch = $divIdSearch; return $this; } - /** - * Returns the div id list. - * - * @return string - */ - public function getDivIdList() + public function getDivIdList(): string { return $this->divIdList; } - /** - * Sets the div id list. - * - * @param string - * - * @return Crud - */ - public function setDivIdList($divIdList) + public function setDivIdList(string $divIdList): self { $this->divIdList = $divIdList; return $this; } - /** - * Gets session name. - * - * @return string - */ - public function getSessionName() + public function getSessionName(): ?string { return $this->sessionName; } - /** - * @return bool - */ - public function getDisplayResultsOnlyIfSearch() + public function getDisplayResultsOnlyIfSearch(): bool { return $this->displayResultsOnlyIfSearch; } - /** - * @param bool $displayResultsOnlyIfSearch - * - * @return Crud - */ - public function setDisplayResultsOnlyIfSearch($displayResultsOnlyIfSearch) + public function setDisplayResultsOnlyIfSearch(bool $displayResultsOnlyIfSearch): self { $this->displayResultsOnlyIfSearch = $displayResultsOnlyIfSearch; return $this; } - /** - * @return bool - */ - public function getDisplayResults() + public function getDisplayResults(): bool { return $this->displayResults; } - /** - * @param bool $displayResults - * - * @return Crud - */ - public function setDisplayResults($displayResults) + public function setDisplayResults(bool $displayResults): self { $this->displayResults = $displayResults; diff --git a/src/Crud/CrudColumn.php b/src/Crud/CrudColumn.php index ca6e080..d2f8d14 100644 --- a/src/Crud/CrudColumn.php +++ b/src/Crud/CrudColumn.php @@ -34,7 +34,7 @@ class CrudColumn * @param string $aliasSearch Column SQL alias, used during searchs * @param string $aliasSort Column(s) SQL alias (string or array of strings), used during sorting */ - public function __construct($id, $alias, $label, $sortable, $defaultDisplayed, $aliasSearch, $aliasSort) + public function __construct(string $id, string $alias, ?string $label, bool $sortable, bool $defaultDisplayed, ?string $aliasSearch, ?string $aliasSort) { $this->id = $id; $this->alias = $alias; diff --git a/src/Crud/CrudFactory.php b/src/Crud/CrudFactory.php index 6d166dd..3b49567 100644 --- a/src/Crud/CrudFactory.php +++ b/src/Crud/CrudFactory.php @@ -27,12 +27,7 @@ public function __construct(ContainerInterface $container) $this->container = $container; } - /** - * @param $sessionName - * - * @return Crud - */ - public function create($sessionName) + public function create(string $sessionName): Crud { return new Crud($sessionName, $this->container); } From 8134e02209851142cd35674ffa65bcbc28ec05fc Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sun, 16 May 2021 11:44:21 +0200 Subject: [PATCH 053/264] Changing the order of methods --- src/Crud/Crud.php | 112 +++++++++++++++++++++++----------------------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/src/Crud/Crud.php b/src/Crud/Crud.php index 7ab5428..978ef67 100644 --- a/src/Crud/Crud.php +++ b/src/Crud/Crud.php @@ -89,6 +89,62 @@ public function __construct($sessionName, ContainerInterface $container) return $this; } + /** + * Inits the CRUD. + */ + public function init(): void + { + //Cheks not empty values + $check_values = [ + 'availableColumns', + 'availableResultsPerPage', + 'defaultSort', + 'defaultSense', + 'defaultResultsPerPage', + 'queryBuilder', + 'routeName', + ]; + foreach ($check_values as $value) { + if (empty($this->$value)) { + throw new \Exception('Config Crud: Option '.$value.' is required'); + } + } + + if ($this->searchForm) { + $this->searchForm->createForm(); + } + + //Loads user values inside this object + $this->load(); + + //Display or not results + if ($this->searchForm && $this->displayResultsOnlyIfSearch) { + $this->displayResults = $this->sessionValues->searchFormIsSubmitted; + } + + $this->createDisplaySettingsForm(); + + //Process request (resultsPerPage, sort, sense, change_columns) + $this->processRequest(); + + //Searcher form: Allocates object + if ($this->searchForm && !$this->container->get('request_stack')->getCurrentRequest()->query->has('raz')) { + //IMPORTANT + //We have not to allocate directelly the "$this->sessionValues->searchFormData" object + //because otherwise it will be linked to form, and will be updated when the "bind" function will + //be called (If form is not valid, the session values will still be updated: Undesirable behavior) + $values = clone $this->sessionValues->searchFormData; + try { + $this->searchForm->getForm()->setData($values); + } catch (TransformationFailedException $exception) { + //Avoid error if data stored in session is invalid + } + } + + //Saves + $this->save(); + } + /** * Add a column inside the crud. * @@ -411,62 +467,6 @@ public function getDefaultDisplayedColumns(): array return $columns; } - /** - * Inits the CRUD. - */ - public function init(): void - { - //Cheks not empty values - $check_values = [ - 'availableColumns', - 'availableResultsPerPage', - 'defaultSort', - 'defaultSense', - 'defaultResultsPerPage', - 'queryBuilder', - 'routeName', - ]; - foreach ($check_values as $value) { - if (empty($this->$value)) { - throw new \Exception('Config Crud: Option '.$value.' is required'); - } - } - - if ($this->searchForm) { - $this->searchForm->createForm(); - } - - //Loads user values inside this object - $this->load(); - - //Display or not results - if ($this->searchForm && $this->displayResultsOnlyIfSearch) { - $this->displayResults = $this->sessionValues->searchFormIsSubmitted; - } - - $this->createDisplaySettingsForm(); - - //Process request (resultsPerPage, sort, sense, change_columns) - $this->processRequest(); - - //Searcher form: Allocates object - if ($this->searchForm && !$this->container->get('request_stack')->getCurrentRequest()->query->has('raz')) { - //IMPORTANT - //We have not to allocate directelly the "$this->sessionValues->searchFormData" object - //because otherwise it will be linked to form, and will be updated when the "bind" function will - //be called (If form is not valid, the session values will still be updated: Undesirable behavior) - $values = clone $this->sessionValues->searchFormData; - try { - $this->searchForm->getForm()->setData($values); - } catch (TransformationFailedException $exception) { - //Avoid error if data stored in session is invalid - } - } - - //Saves - $this->save(); - } - /** * Load user values. */ From 1ffd5d17b9347cbf0649b1472bbb9671170bc090 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sun, 16 May 2021 12:15:15 +0200 Subject: [PATCH 054/264] Several modifications in CRUD classes * CrudSession::searchFormIsSubmitted property is renamed to searchFormIsSubmittedAndValid * Minor fixes --- src/Crud/Crud.php | 86 ++++++++++++++++++++-------------------- src/Crud/CrudSession.php | 2 +- 2 files changed, 44 insertions(+), 44 deletions(-) diff --git a/src/Crud/Crud.php b/src/Crud/Crud.php index 978ef67..d7bdb7a 100644 --- a/src/Crud/Crud.php +++ b/src/Crud/Crud.php @@ -95,7 +95,7 @@ public function __construct($sessionName, ContainerInterface $container) public function init(): void { //Cheks not empty values - $check_values = [ + $checkValues = [ 'availableColumns', 'availableResultsPerPage', 'defaultSort', @@ -104,7 +104,7 @@ public function init(): void 'queryBuilder', 'routeName', ]; - foreach ($check_values as $value) { + foreach ($checkValues as $value) { if (empty($this->$value)) { throw new \Exception('Config Crud: Option '.$value.' is required'); } @@ -119,7 +119,7 @@ public function init(): void //Display or not results if ($this->searchForm && $this->displayResultsOnlyIfSearch) { - $this->displayResults = $this->sessionValues->searchFormIsSubmitted; + $this->displayResults = $this->sessionValues->searchFormIsSubmittedAndValid; } $this->createDisplaySettingsForm(); @@ -362,7 +362,7 @@ public function processForm(): void $searchForm->handleRequest($request); if ($searchForm->isSubmitted() && $searchForm->isValid()) { $this->displayResults = true; - $this->sessionValues->searchFormIsSubmitted = true; + $this->sessionValues->searchFormIsSubmittedAndValid = true; $this->changeFilterValues($searchForm->getData()); $this->changePage(1); $this->save(); @@ -370,38 +370,11 @@ public function processForm(): void } } - /** - * User action: Changes search form values. - * - * @param object $value - */ - protected function changeFilterValues($value): void - { - if (!$this->searchForm) { - return; - } - if (\get_class($value) === \get_class($this->searchForm->getDefaultData())) { - $this->sessionValues->searchFormData = $value; - } else { - $this->sessionValues->searchFormData = clone $this->searchForm->getDefaultData(); - } - } - - /** - * User action: Changes page number. - * - * @param string $value Page number - */ - protected function changePage($value): void + protected function testIfDatabaseMustMeUpdated($oldValue, $new_value): void { - if (!is_scalar($value)) { - $value = 1; - } - $value = (int) $value; - if ($value > 1000000000000) { - $value = 1; + if ($oldValue != $new_value) { + $this->updateDatabase = true; } - $this->sessionValues->page = $value; } /** @@ -542,13 +515,6 @@ protected function changeNumberResultsDisplayed($value): void $this->testIfDatabaseMustMeUpdated($oldValue, $value); } - protected function testIfDatabaseMustMeUpdated($oldValue, $new_value): void - { - if ($oldValue != $new_value) { - $this->updateDatabase = true; - } - } - /** * User Action: Changes displayed columns. * @@ -584,7 +550,7 @@ protected function changeSort($value): void $oldValue = $this->sessionValues->sort; $availableColumns = $this->availableColumns; if ((is_scalar($value) && \array_key_exists($value, $availableColumns) && $availableColumns[$value]->sortable) - || (is_scalar($value) && 'defaultPersonalizedSort' == $value && $this->defaultPersonalizedSort)) { + || (is_scalar($value) && 'defaultPersonalizedSort' === $value && $this->defaultPersonalizedSort)) { $this->sessionValues->sort = $value; $this->testIfDatabaseMustMeUpdated($oldValue, $value); } else { @@ -601,7 +567,7 @@ protected function changeSort($value): void protected function changeSense($value): void { $oldValue = $this->sessionValues->sense; - if (is_scalar($value) && (self::ASC == $value || self::DESC == $value)) { + if (is_scalar($value) && (self::ASC === $value || self::DESC === $value)) { $this->sessionValues->sense = $value; $this->testIfDatabaseMustMeUpdated($oldValue, $value); } else { @@ -610,6 +576,40 @@ protected function changeSense($value): void } } + /** + * User action: Changes search form values. + * + * @param object $value + */ + protected function changeFilterValues($value): void + { + if (!$this->searchForm) { + return; + } + if (\get_class($value) === \get_class($this->searchForm->getDefaultData())) { + $this->sessionValues->searchFormData = $value; + } else { + $this->sessionValues->searchFormData = clone $this->searchForm->getDefaultData(); + } + } + + /** + * User action: Changes page number. + * + * @param string $value Page number + */ + protected function changePage($value): void + { + if (!is_scalar($value)) { + $value = 1; + } + $value = (int) $value; + if ($value > 1000000000000) { + $value = 1; + } + $this->sessionValues->page = $value; + } + /** * Process request. */ diff --git a/src/Crud/CrudSession.php b/src/Crud/CrudSession.php index 131743c..6585b39 100644 --- a/src/Crud/CrudSession.php +++ b/src/Crud/CrudSession.php @@ -25,7 +25,7 @@ class CrudSession */ public $searchFormData = null; - public $searchFormIsSubmitted = false; + public $searchFormIsSubmittedAndValid = false; /** * Number of results, in one page. From 99056db7e5f3666c43589416c6f57e6d90148eda Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sun, 16 May 2021 12:17:19 +0200 Subject: [PATCH 055/264] Remove ecommit_crud.images option --- src/DependencyInjection/Configuration.php | 7 ------- src/DependencyInjection/EcommitCrudExtension.php | 1 - 2 files changed, 8 deletions(-) diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index e664b86..a997ebf 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -35,13 +35,6 @@ public function getConfigTreeBuilder() ->children() ->scalarNode('theme')->isRequired()->end() ->scalarNode('icon_theme')->isRequired()->end() - ->arrayNode('images') - ->addDefaultsIfNotSet() - ->children() - ->scalarNode('th_image_up')->defaultValue('/bundles/ecommitcrud/images/i16/sort_incr.png')->end() - ->scalarNode('th_image_down')->defaultValue('/bundles/ecommitcrud/images/i16/sort_decrease.png')->end() - ->end() - ->end() ->end() ; diff --git a/src/DependencyInjection/EcommitCrudExtension.php b/src/DependencyInjection/EcommitCrudExtension.php index 15b74c5..0dbeef1 100644 --- a/src/DependencyInjection/EcommitCrudExtension.php +++ b/src/DependencyInjection/EcommitCrudExtension.php @@ -39,6 +39,5 @@ public function load(array $config, ContainerBuilder $container): void $container->setParameter('ecommit_crud.theme', $config['theme']); $container->setParameter('ecommit_crud.icon_theme', $config['icon_theme']); - $container->setParameter('ecommit_crud.images', ['th_image_up' => $config['images']['th_image_up'], 'th_image_down' => $config['images']['th_image_down']]); } } From 18cf61cdb7b4611aebd6de57836bfb38feadc0fb Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sun, 16 May 2021 12:17:50 +0200 Subject: [PATCH 056/264] Remove list.png --- src/Resources/public/images/i16/LICENSE | 32 ----------------------- src/Resources/public/images/i16/list.png | Bin 3383 -> 0 bytes 2 files changed, 32 deletions(-) delete mode 100644 src/Resources/public/images/i16/LICENSE delete mode 100644 src/Resources/public/images/i16/list.png diff --git a/src/Resources/public/images/i16/LICENSE b/src/Resources/public/images/i16/LICENSE deleted file mode 100644 index a065aca..0000000 --- a/src/Resources/public/images/i16/LICENSE +++ /dev/null @@ -1,32 +0,0 @@ -Icons used: - --------------------------------------------------------- -FAM FAM FAM - -Mark James -http://www.famfamfam.com/lab/icons/silk/ - -This work is licensed under a -Creative Commons Attribution 2.5 License. -[ http://creativecommons.org/licenses/by/2.5/ ] - -This means you may use it for any purpose, -and make any changes you like. -All I ask is that you include a link back -to this page in your credits. - -Are you using this icon set? Send me an email -(including a link or picture if available) to -mjames@gmail.com - -Any other questions about this icon set please -contact mjames@gmail.com - --------------------------------------------------------- - -TITLE: Crystal Project Icons -AUTHOR: Everaldo Coelho -SITE: http://www.everaldo.com -CONTACT: everaldo@everaldo.com - -Copyright (c) 2006-2007 Everaldo Coelho. diff --git a/src/Resources/public/images/i16/list.png b/src/Resources/public/images/i16/list.png deleted file mode 100644 index 2c9c154bc43d433e489677f05f7ee7388ffeaf0f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3383 zcmV-74ao9|P)KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=00004XF*Lt006O$eEU(80000WV@Og>004R=004l4008;_004mL004C` z008P>0026e000+nl3&F}0006~NklG!F(i&xHFxU@cfU@#SDjibrE^O0e*vb7CFRJt9 za^@sW=0IHgUjG5G3=XIce}1zS<{pd&aHMR(9$*DlUV-Rf=Q&`#Iu6tgw9RV(m`G3q zxONU7|A0S!1N-!QqRjD|6XfLterUQE*!_??Sw{#gJa7lBMOW_;8?PTcY#e>(HzfE!;?XZKOPdr1WXgmDM1c?=p4B?2h_%9!`lpwEADx!c2CMt^z zojy;R>mmn9k#rRK=qg0|Z!`x^RuHtY?PpK!e0TZkYFB%s0TGozKxAQ6B^LJ~q7sT= zN`A8S*Ik?L?)D%y|UF!n Date: Sun, 16 May 2021 12:19:58 +0200 Subject: [PATCH 057/264] Remove old translations (used by paginator and picker) --- src/Resources/translations/messages.en.yml | 7 ------- src/Resources/translations/messages.fr.yml | 7 ------- 2 files changed, 14 deletions(-) delete mode 100644 src/Resources/translations/messages.en.yml delete mode 100644 src/Resources/translations/messages.fr.yml diff --git a/src/Resources/translations/messages.en.yml b/src/Resources/translations/messages.en.yml deleted file mode 100644 index c8228c3..0000000 --- a/src/Resources/translations/messages.en.yml +++ /dev/null @@ -1,7 +0,0 @@ -'{0} No results|{1} 1 result found|]1,Inf] %count% results found': '{0} No results|{1} 1 result found|]1,Inf] %count% results found' -Page %firstPage%/%lastPage%: Page %page%/%lastPage% -Results %first%-%last%: Results %first%-%last% -Page %page%/%lastPage%: Page %page%/%lastPage% -filter.choices.placeholder: All -picker.add: Add -picker.list: Select diff --git a/src/Resources/translations/messages.fr.yml b/src/Resources/translations/messages.fr.yml deleted file mode 100644 index 28cef22..0000000 --- a/src/Resources/translations/messages.fr.yml +++ /dev/null @@ -1,7 +0,0 @@ -'{0} No results|{1} 1 result found|]1,Inf] %count% results found': '{0} Pas de résultat|{1} 1 résultat trouvé|]1,Inf] %count% résultats trouvés' -Page %firstPage%/%lastPage%: Page %page%/%lastPage% -Results %first%-%last%: Résultats %first%-%last% -Page %page%/%lastPage%: Page %page%/%lastPage% -filter.choices.placeholder: Tous -picker.add: Ajouter -picker.list: Sélectionner From 4dedbaa2bc5f4aa2b9523c20c0d37c7460dbb061 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sun, 16 May 2021 18:31:20 +0200 Subject: [PATCH 058/264] [Twig] Use snake_case for template variables --- src/Resources/views/Theme/base.html.twig | 24 +++++++++---------- .../views/Theme/bootstrap3.html.twig | 4 ++-- src/Twig/CrudExtension.php | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Resources/views/Theme/base.html.twig b/src/Resources/views/Theme/base.html.twig index 3e4382e..a727cee 100644 --- a/src/Resources/views/Theme/base.html.twig +++ b/src/Resources/views/Theme/base.html.twig @@ -117,16 +117,16 @@ {% block th_sortable_active_asc %} {% set th_attr = th_attr.sortable_active_asc|merge({class: (th_attr.sortable_active_asc.class|default('') ~ ' ec-crud-th ec-crud-th-sortable-active-asc')|trim}) %} {% set a_attr = a_attr.sortable_active_asc %} - {% set newSense = constant('Ecommit\\CrudBundle\\Crud\\Crud::DESC') %} - {% set iconName = 'asc' %} + {% set new_sense = constant('Ecommit\\CrudBundle\\Crud\\Crud::DESC') %} + {% set icon_name = 'asc' %} {{ block('th_sortable_active', template_name) }} {% endblock %} {% block th_sortable_active_desc %} {% set th_attr = th_attr.sortable_active_desc|merge({class: (th_attr.sortable_active_desc.class|default('') ~ ' ec-crud-th ec-crud-th-sortable-active-desc')|trim}) %} {% set a_attr = a_attr.sortable_active_desc %} - {% set newSense = constant('Ecommit\\CrudBundle\\Crud\\Crud::ASC') %} - {% set iconName = 'desc' %} + {% set new_sense = constant('Ecommit\\CrudBundle\\Crud\\Crud::ASC') %} + {% set icon_name = 'desc' %} {{ block('th_sortable_active', template_name) }} {% endblock %} @@ -135,8 +135,8 @@ {% set a_attr = a_attr|merge({class: (a_attr.class|default('') ~ ' ec-crud-ajax-link-auto')|trim}) %} {% endif %} - - {{- label|trans }} {{ crud_icon(iconName) -}} + + {{- label|trans }} {{ crud_icon(icon_name) -}} {% endblock %} @@ -145,7 +145,7 @@ {% block td %} {% apply spaceless %} - {% if repeatedValue %} + {% if repeated_value %} {% if td_attr.title is not defined %} {% set td_attr = td_attr|merge({title: value}) %} {% endif %} @@ -162,7 +162,7 @@ {% block display_settings %} {% apply spaceless %} {% if crud.displayResults %} - {% set displaySettingsContainerAttributes = {'class': 'ec-crud-display-settings', 'id': 'ec-crud-display-settings-'~crud.sessionName, 'data-crud-list-id': crud.divIdList} %} + {% set display_settings_container_attributes = {'class': 'ec-crud-display-settings', 'id': 'ec-crud-display-settings-'~crud.sessionName, 'data-crud-list-id': crud.divIdList} %} {% if modal %} {{ block('display_settings_button_modal', template_name) }} {{ block('display_settings_container_modal', template_name) }} @@ -197,8 +197,8 @@ {% endblock %} {% block display_settings_container_without_modal %} - {% set displaySettingsContainerAttributes = displaySettingsContainerAttributes|default({})|merge({'data-modal': '0', 'style': 'display: none;'}) %} - + {% set display_settings_container_attributes = display_settings_container_attributes|default({})|merge({'data-modal': '0', 'style': 'display: none;'}) %} + {{ block('display_settings_content_without_modal', template_name) }} {% endblock %} @@ -259,8 +259,8 @@ {# Attributes #} {% block attributes %} - {%- for attrName, attrValue in attr -%} + {%- for attr_name, attr_value in attr -%} {{- " " -}} - {{- attrName }}="{{ attrValue }}" + {{- attr_name }}="{{ attr_value }}" {%- endfor -%} {% endblock %} diff --git a/src/Resources/views/Theme/bootstrap3.html.twig b/src/Resources/views/Theme/bootstrap3.html.twig index aea6dec..0bd32db 100644 --- a/src/Resources/views/Theme/bootstrap3.html.twig +++ b/src/Resources/views/Theme/bootstrap3.html.twig @@ -26,8 +26,8 @@ {% endblock %} {% block display_settings_container_modal %} - {% set displaySettingsContainerAttributes = displaySettingsContainerAttributes|default({})|merge({class: (displaySettingsContainerAttributes.class|default('') ~ ' modal fade')|trim, 'data-modal': '1', 'style': 'display: none;', 'role': 'dialog'}) %} - + {% set display_settings_container_attributes = display_settings_container_attributes|default({})|merge({class: (display_settings_container_attributes.class|default('') ~ ' modal fade')|trim, 'data-modal': '1', 'style': 'display: none;', 'role': 'dialog'}) %} + {{ block('display_settings_content_modal', template_name) }} {% endblock %} diff --git a/src/Twig/CrudExtension.php b/src/Twig/CrudExtension.php index 155f90c..9f43505 100644 --- a/src/Twig/CrudExtension.php +++ b/src/Twig/CrudExtension.php @@ -389,7 +389,7 @@ public function td(Environment $environment, string $columnId, Crud $crud, $valu 'column' => $column, 'crud' => $crud, 'value' => $value, - 'repeatedValue' => $repeatedValue, + 'repeated_value' => $repeatedValue, 'options' => $options, ])); } From da36049384fa7e994773ed69191a9ca4908a4626 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sun, 16 May 2021 18:36:42 +0200 Subject: [PATCH 059/264] Add missing "options" Twig variables --- src/Twig/CrudExtension.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Twig/CrudExtension.php b/src/Twig/CrudExtension.php index 9f43505..2ba8d24 100644 --- a/src/Twig/CrudExtension.php +++ b/src/Twig/CrudExtension.php @@ -239,6 +239,7 @@ public function paginatorLinks(Environment $environment, AbstractPaginator $pagi 'pages' => $pages, 'route_name' => $routeName, 'route_params' => $routeParams, + 'options' => $options, ])); } @@ -424,6 +425,7 @@ public function displaySettings(Environment $environment, Crud $crud, array $opt return $this->renderBlock($environment, $this->theme, 'display_settings', array_merge($options, [ 'crud' => $crud, 'form' => $form, + 'options' => $options, ])); } @@ -473,6 +475,7 @@ public function searchFormStart(Environment $environment, Crud $crud, array $opt return $this->renderBlock($environment, $this->theme, 'search_form_start', array_merge($options, [ 'crud' => $crud, + 'options' => $options, ])); } @@ -500,6 +503,7 @@ public function searchFormSubmit(Environment $environment, Crud $crud, $options return $this->renderBlock($environment, $this->theme, 'search_form_submit', array_merge($options, [ 'crud' => $crud, + 'options' => $options, ])); } @@ -546,6 +550,7 @@ public function searchFormReset(Environment $environment, Crud $crud, $options = return $this->renderBlock($environment, $this->theme, 'search_form_reset', array_merge($options, [ 'crud' => $crud, + 'options' => $options, ])); } From 868dbf6ae3b70411c3eb1ccaf275ecd8a4643e5e Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sun, 16 May 2021 18:57:36 +0200 Subject: [PATCH 060/264] [Twig] Add "theme" and "block" options --- src/Twig/CrudExtension.php | 28 +++++++++++---- .../Functional/App/templates/theme.html.twig | 3 ++ tests/Twig/CrudExtensionTest.php | 35 +++++++++++++++++++ 3 files changed, 59 insertions(+), 7 deletions(-) create mode 100644 tests/Functional/App/templates/theme.html.twig diff --git a/src/Twig/CrudExtension.php b/src/Twig/CrudExtension.php index 2ba8d24..5f6f6e8 100644 --- a/src/Twig/CrudExtension.php +++ b/src/Twig/CrudExtension.php @@ -192,6 +192,8 @@ public function paginatorLinks(Environment $environment, AbstractPaginator $pagi } }, 'render' => null, + 'theme' => $this->theme, + 'block' => 'paginator_links', ]); $resolver->setAllowedTypes('ajax_options', ['null', 'array']); $resolver->setAllowedTypes('max_pages_before', 'int'); @@ -234,7 +236,7 @@ public function paginatorLinks(Environment $environment, AbstractPaginator $pagi } } - return $this->renderBlock($environment, $this->theme, 'paginator_links', array_merge($options, [ + return $this->renderBlock($environment, $options['theme'], $options['block'], array_merge($options, [ 'paginator' => $paginator, 'pages' => $pages, 'route_name' => $routeName, @@ -296,6 +298,8 @@ public function th(Environment $environment, string $columnId, Crud $crud, array } }, 'render' => null, + 'theme' => $this->theme, + 'block' => 'th', ]); $resolver->setAllowedTypes('ajax_options', ['null', 'array']); $resolver->setAllowedTypes('label', ['null', 'string']); @@ -326,7 +330,7 @@ public function th(Environment $environment, string $columnId, Crud $crud, array $label = $column->label; } - return $this->renderBlock($environment, $this->theme, 'th', array_merge($options, [ + return $this->renderBlock($environment, $options['theme'], $options['block'], array_merge($options, [ 'column' => $column, 'crud' => $crud, 'options' => $options, @@ -352,6 +356,8 @@ public function td(Environment $environment, string $columnId, Crud $crud, $valu 'repeated_values_string' => null, 'td_attr' => [], 'render' => null, + 'theme' => $this->theme, + 'block' => 'td', ]); $resolver->setAllowedTypes('escape', 'bool'); $resolver->setAllowedTypes('repeated_values_string', ['null', 'string']); @@ -386,7 +392,7 @@ public function td(Environment $environment, string $columnId, Crud $crud, $valu } } - return $this->renderBlock($environment, $this->theme, 'td', array_merge($options, [ + return $this->renderBlock($environment, $options['theme'], $options['block'], array_merge($options, [ 'column' => $column, 'crud' => $crud, 'value' => $value, @@ -408,6 +414,8 @@ public function displaySettings(Environment $environment, Crud $crud, array $opt $resolver->setDefaults([ 'modal' => true, 'render' => null, + 'theme' => $this->theme, + 'block' => 'display_settings', ]); $resolver->setAllowedTypes('modal', 'bool'); $options = $resolver->resolve($options); @@ -422,7 +430,7 @@ public function displaySettings(Environment $environment, Crud $crud, array $opt ]); } - return $this->renderBlock($environment, $this->theme, 'display_settings', array_merge($options, [ + return $this->renderBlock($environment, $options['theme'], $options['block'], array_merge($options, [ 'crud' => $crud, 'form' => $form, 'options' => $options, @@ -442,6 +450,8 @@ public function searchFormStart(Environment $environment, Crud $crud, array $opt 'ajax_options' => [], 'form_attr' => [], 'render' => null, + 'theme' => $this->theme, + 'block' => 'search_form_start', ]); $resolver->setAllowedTypes('ajax_options', ['array']); $resolver->setAllowedTypes('form_attr', ['array']); @@ -473,7 +483,7 @@ public function searchFormStart(Environment $environment, Crud $crud, array $opt $options['form_attr']['novalidate'] = 'novalidate'; } - return $this->renderBlock($environment, $this->theme, 'search_form_start', array_merge($options, [ + return $this->renderBlock($environment, $options['theme'], $options['block'], array_merge($options, [ 'crud' => $crud, 'options' => $options, ])); @@ -490,6 +500,8 @@ public function searchFormSubmit(Environment $environment, Crud $crud, $options $resolver->setDefaults([ 'button_attr' => [], 'render' => null, + 'theme' => $this->theme, + 'block' => 'search_form_submit', ]); $resolver->setAllowedTypes('button_attr', ['array']); $options = $resolver->resolve($options); @@ -501,7 +513,7 @@ public function searchFormSubmit(Environment $environment, Crud $crud, $options ]); } - return $this->renderBlock($environment, $this->theme, 'search_form_submit', array_merge($options, [ + return $this->renderBlock($environment, $options['theme'], $options['block'], array_merge($options, [ 'crud' => $crud, 'options' => $options, ])); @@ -520,6 +532,8 @@ public function searchFormReset(Environment $environment, Crud $crud, $options = 'ajax_options' => [], 'button_attr' => [], 'render' => null, + 'theme' => $this->theme, + 'block' => 'search_form_reset', ]); $resolver->setAllowedTypes('ajax_options', ['array']); $resolver->setAllowedTypes('button_attr', ['array']); @@ -548,7 +562,7 @@ public function searchFormReset(Environment $environment, Crud $crud, $options = $options['button_attr']['class'] = $class; } - return $this->renderBlock($environment, $this->theme, 'search_form_reset', array_merge($options, [ + return $this->renderBlock($environment, $options['theme'], $options['block'], array_merge($options, [ 'crud' => $crud, 'options' => $options, ])); diff --git a/tests/Functional/App/templates/theme.html.twig b/tests/Functional/App/templates/theme.html.twig new file mode 100644 index 0000000..a705f98 --- /dev/null +++ b/tests/Functional/App/templates/theme.html.twig @@ -0,0 +1,3 @@ +{% block hello_world %} + Hello world +{% endblock %} diff --git a/tests/Twig/CrudExtensionTest.php b/tests/Twig/CrudExtensionTest.php index a38bec2..3b3e841 100644 --- a/tests/Twig/CrudExtensionTest.php +++ b/tests/Twig/CrudExtensionTest.php @@ -377,6 +377,21 @@ public function testPaginatorWithRenderOption(): void $this->assertRegExp('/OK/', $result); } + public function testPaginatorWithThemeAndBlockOptions(): void + { + $paginator = new ArrayPaginator(5); + $paginator->setData(range(1, 100)); + $paginator->setPage(1); + $paginator->init(); + + $result = $this->crudExtension->paginatorLinks($this->environment, $paginator, 'user_crud', [], [ + 'theme' => 'theme.html.twig', + 'block' => 'hello_world', + ]); + + $this->assertRegExp('/Hello world/', $result); + } + public function testPaginatorWithBadOptions(): void { $this->expectException(UndefinedOptionsException::class); @@ -504,6 +519,16 @@ public function testThWithRenderOption(): void $this->assertRegExp('/OK/', $html); } + public function testThWithThemeAndBlockOptions(): void + { + $html = $this->crudExtension->th($this->environment, 'column1', $this->createCrud(), [ + 'theme' => 'theme.html.twig', + 'block' => 'hello_world', + ]); + + $this->assertRegExp('/Hello world/', $html); + } + public function testThWithBadOptions(): void { $this->expectException(UndefinedOptionsException::class); @@ -665,6 +690,16 @@ public function testTdWithRenderOption(): void $this->assertRegExp('/OK/', $html); } + public function testTdWithThemeAndBlockOptions(): void + { + $html = $this->crudExtension->td($this->environment, 'column1', $this->createCrud(), 'value1', [ + 'theme' => 'theme.html.twig', + 'block' => 'hello_world', + ]); + + $this->assertRegExp('/Hello world/', $html); + } + public function testTdWithBadOptions(): void { $this->expectException(UndefinedOptionsException::class); From 3c7f28cfa1ca043daf32c7ba6726b0066197eb32 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sun, 16 May 2021 19:01:24 +0200 Subject: [PATCH 061/264] [crud_icon] Add "iconTheme" argument --- src/Twig/CrudExtension.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Twig/CrudExtension.php b/src/Twig/CrudExtension.php index 5f6f6e8..d0f4d75 100644 --- a/src/Twig/CrudExtension.php +++ b/src/Twig/CrudExtension.php @@ -649,9 +649,13 @@ protected function getAjaxAttributes(array $options): array return $attributes; } - public function crudIcon(Environment $environment, string $iconName): string + public function crudIcon(Environment $environment, string $iconName, ?string $iconTheme = null): string { - return $this->renderBlock($environment, $this->iconTheme, $iconName); + if (null === $iconTheme) { + $iconTheme = $this->iconTheme; + } + + return $this->renderBlock($environment, $iconTheme, $iconName); } protected function renderBlock(Environment $environment, string $templateName, string $blockName, array $parameters = []): ?string From 831e1d584bee6c646b6ca6b75acc0f21af5b1c01 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sun, 16 May 2021 19:25:42 +0200 Subject: [PATCH 062/264] Add Twig configuration --- src/Crud/Crud.php | 23 ++++++++++++ src/DependencyInjection/Configuration.php | 4 +++ .../EcommitCrudExtension.php | 1 + src/Resources/config/services.xml | 1 + src/Twig/CrudExtension.php | 35 ++++++++++++++----- 5 files changed, 56 insertions(+), 8 deletions(-) diff --git a/src/Crud/Crud.php b/src/Crud/Crud.php index d7bdb7a..c7dfbb2 100644 --- a/src/Crud/Crud.php +++ b/src/Crud/Crud.php @@ -63,6 +63,8 @@ class Crud protected $displayResultsOnlyIfSearch = false; protected $displayResults = true; + protected $twigFunctionsConfiguration = []; + /* * Router */ @@ -928,4 +930,25 @@ public function setDisplayResults(bool $displayResults): self return $this; } + + public function getTwigFunctionsConfiguration(): array + { + return $this->twigFunctionsConfiguration; + } + + public function getTwigFunctionConfiguration(string $function): array + { + if (isset($this->twigFunctionsConfiguration[$function])) { + return $this->twigFunctionsConfiguration; + } + + return []; + } + + public function setTwigFunctionsConfiguration(array $twigFunctionsConfiguration): self + { + $this->twigFunctionsConfiguration = $twigFunctionsConfiguration; + + return $this; + } } diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index a997ebf..b9fcab8 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -35,6 +35,10 @@ public function getConfigTreeBuilder() ->children() ->scalarNode('theme')->isRequired()->end() ->scalarNode('icon_theme')->isRequired()->end() + ->arrayNode('twig_functions_configuration') + ->treatNullLike([]) + ->prototype('variable')->end() + ->end() ->end() ; diff --git a/src/DependencyInjection/EcommitCrudExtension.php b/src/DependencyInjection/EcommitCrudExtension.php index 0dbeef1..af8da93 100644 --- a/src/DependencyInjection/EcommitCrudExtension.php +++ b/src/DependencyInjection/EcommitCrudExtension.php @@ -39,5 +39,6 @@ public function load(array $config, ContainerBuilder $container): void $container->setParameter('ecommit_crud.theme', $config['theme']); $container->setParameter('ecommit_crud.icon_theme', $config['icon_theme']); + $container->setParameter('ecommit_crud.twig_functions_configuration', $config['twig_functions_configuration']); } } diff --git a/src/Resources/config/services.xml b/src/Resources/config/services.xml index a88fc74..f17e875 100644 --- a/src/Resources/config/services.xml +++ b/src/Resources/config/services.xml @@ -29,6 +29,7 @@ %ecommit_crud.theme% %ecommit_crud.icon_theme% + %ecommit_crud.twig_functions_configuration% diff --git a/src/Twig/CrudExtension.php b/src/Twig/CrudExtension.php index d0f4d75..9f7f461 100644 --- a/src/Twig/CrudExtension.php +++ b/src/Twig/CrudExtension.php @@ -31,14 +31,16 @@ class CrudExtension extends AbstractExtension protected $theme; protected $iconTheme; + protected $configuration; protected $lastTdValues = []; - public function __construct(FormRendererInterface $formRenderer, string $theme, string $iconTheme) + public function __construct(FormRendererInterface $formRenderer, string $theme, string $iconTheme, array $configuration) { $this->formRenderer = $formRenderer; $this->theme = $theme; $this->iconTheme = $iconTheme; + $this->configuration = $configuration; } public function getName() @@ -199,7 +201,7 @@ public function paginatorLinks(Environment $environment, AbstractPaginator $pagi $resolver->setAllowedTypes('max_pages_before', 'int'); $resolver->setAllowedTypes('max_pages_after', 'int'); $resolver->setAllowedValues('type', ['sliding', 'elastic']); - $options = $resolver->resolve($options); + $options = $resolver->resolve($this->buildOptions('paginator_links', $options)); if ($options['render']) { return $environment->render($options['render'], [ @@ -256,6 +258,8 @@ public function crudPaginatorLinks(Environment $environment, Crud $crud, array $ $options['ajax_options']['update'] = '#'.$crud->getDivIdList(); } + $options = $this->buildOptions('crud_paginator_links', $options, $crud); + return $this->paginatorLinks($environment, $crud->getPaginator(), $crud->getRouteName(), $crud->getRouteParams(), $options); } @@ -303,7 +307,7 @@ public function th(Environment $environment, string $columnId, Crud $crud, array ]); $resolver->setAllowedTypes('ajax_options', ['null', 'array']); $resolver->setAllowedTypes('label', ['null', 'string']); - $options = $resolver->resolve($options); + $options = $resolver->resolve($this->buildOptions('th', $options, $crud)); //If the column is not to be shown, returns empty $sessionValues = $crud->getSessionValues(); @@ -362,7 +366,7 @@ public function td(Environment $environment, string $columnId, Crud $crud, $valu $resolver->setAllowedTypes('escape', 'bool'); $resolver->setAllowedTypes('repeated_values_string', ['null', 'string']); $resolver->setAllowedTypes('td_attr', ['null', 'array']); - $options = $resolver->resolve($options); + $options = $resolver->resolve($this->buildOptions('td', $options, $crud)); //If the column is not to be shown, returns empty $sessionValues = $crud->getSessionValues(); @@ -418,7 +422,7 @@ public function displaySettings(Environment $environment, Crud $crud, array $opt 'block' => 'display_settings', ]); $resolver->setAllowedTypes('modal', 'bool'); - $options = $resolver->resolve($options); + $options = $resolver->resolve($this->buildOptions('display_settings', $options, $crud)); $form = $crud->getDisplaySettingsForm(); @@ -455,7 +459,7 @@ public function searchFormStart(Environment $environment, Crud $crud, array $opt ]); $resolver->setAllowedTypes('ajax_options', ['array']); $resolver->setAllowedTypes('form_attr', ['array']); - $options = $resolver->resolve($options); + $options = $resolver->resolve($this->buildOptions('search_form_start', $options, $crud)); if ($options['render']) { return $environment->render($options['render'], [ @@ -504,7 +508,7 @@ public function searchFormSubmit(Environment $environment, Crud $crud, $options 'block' => 'search_form_submit', ]); $resolver->setAllowedTypes('button_attr', ['array']); - $options = $resolver->resolve($options); + $options = $resolver->resolve($this->buildOptions('search_form_submit', $options, $crud)); if ($options['render']) { return $environment->render($options['render'], [ @@ -537,7 +541,7 @@ public function searchFormReset(Environment $environment, Crud $crud, $options = ]); $resolver->setAllowedTypes('ajax_options', ['array']); $resolver->setAllowedTypes('button_attr', ['array']); - $options = $resolver->resolve($options); + $options = $resolver->resolve($this->buildOptions('search_form_reset', $options, $crud)); if ($options['render']) { return $environment->render($options['render'], [ @@ -667,4 +671,19 @@ protected function renderBlock(Environment $environment, string $templateName, s return ob_get_clean(); } + + protected function buildOptions(string $function, array $inlineOptions, ?Crud $crud = null): array + { + $options = []; + + if (isset($this->configuration[$function])) { + $options = $this->configuration[$function]; + } + + if (null !== $crud) { + $options = array_merge($options, $crud->getTwigFunctionConfiguration($function)); + } + + return array_merge($options, $inlineOptions); + } } From 8a02f4c840ec6dda65cd4d4c78df53cd20636fe0 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sun, 16 May 2021 19:28:02 +0200 Subject: [PATCH 063/264] [Crud] Rename "formDisplaySettings" property to "displaySettingsForm" --- src/Crud/Crud.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Crud/Crud.php b/src/Crud/Crud.php index c7dfbb2..951a780 100644 --- a/src/Crud/Crud.php +++ b/src/Crud/Crud.php @@ -45,7 +45,7 @@ class Crud /** * @var Form */ - protected $formDisplaySettings = null; + protected $displaySettingsForm = null; protected $availableColumns = []; protected $availableVirtualColumns = []; @@ -630,9 +630,9 @@ protected function processRequest(): void return; } - $this->formDisplaySettings->handleRequest($request); - if ($this->formDisplaySettings->isSubmitted() && $this->formDisplaySettings->isValid()) { - $displaySettingsData = $this->formDisplaySettings->getData(); + $this->displaySettingsForm->handleRequest($request); + if ($this->displaySettingsForm->isSubmitted() && $this->displaySettingsForm->isValid()) { + $displaySettingsData = $this->displaySettingsForm->getData(); $this->changeColumnsDisplayed($displaySettingsData['displayedColumns']); $this->changeNumberResultsDisplayed($displaySettingsData['resultsPerPage']); } @@ -663,7 +663,7 @@ public function createDisplaySettingsForm(): void ]; $formName = sprintf('crud_display_settings_%s', $this->getSessionName()); - $this->formDisplaySettings = $this->container->get('form.factory')->createNamed($formName, DisplaySettingsType::class, $data, [ + $this->displaySettingsForm = $this->container->get('form.factory')->createNamed($formName, DisplaySettingsType::class, $data, [ 'results_per_page_choices' => $resultsPerPageChoices, 'columns_choices' => $columnsChoices, 'action' => $this->getUrl(['display-settings' => 1]), @@ -806,7 +806,7 @@ public function clearTemplate(): void $this->searchForm->createFormView(); $this->searchForm = $this->searchForm->getForm(); } - $this->formDisplaySettings = $this->formDisplaySettings->createView(); + $this->displaySettingsForm = $this->displaySettingsForm->createView(); } /** @@ -875,7 +875,7 @@ public function getSearchForm() */ public function getDisplaySettingsForm() { - return $this->formDisplaySettings; + return $this->displaySettingsForm; } public function getDivIdSearch(): string From b29e365add37ac67b014009bce63d3ecabc4a506 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sat, 3 Jul 2021 16:34:04 +0200 Subject: [PATCH 064/264] Add symfony/intl dependency --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index d615240..9ef6e6c 100644 --- a/composer.json +++ b/composer.json @@ -25,6 +25,7 @@ "symfony/http-client": "^4.4.11|^5.1.3", "symfony/http-foundation": "^4.4|^5.0", "symfony/http-kernel": "^4.4|^5.0", + "symfony/intl": "^4.4|^5.0", "symfony/options-resolver": "^4.4|^5.0", "symfony/property-access": "^4.4|^5.0", "symfony/routing": "^4.4|^5.0", From b2cec9f7e2d2f88e635f2ed59d0c73829e20db26 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sun, 4 Jul 2021 19:53:13 +0200 Subject: [PATCH 065/264] [CrudExtension] Add missing doc and fix configuration name --- src/Twig/CrudExtension.php | 80 +++++++++++++++++++++++++++++--------- 1 file changed, 62 insertions(+), 18 deletions(-) diff --git a/src/Twig/CrudExtension.php b/src/Twig/CrudExtension.php index 9f7f461..ea1d718 100644 --- a/src/Twig/CrudExtension.php +++ b/src/Twig/CrudExtension.php @@ -140,7 +140,7 @@ public function getFunctions() } /** - * Returns links paginator. + * Displays links paginator. * * @param string $routeName Route name * @param array $routeParams Route parameters @@ -154,7 +154,9 @@ public function getFunctions() * * ul_attr: "ul" attributes * * li_attr: "li" attributes for each page type (sub arrays: first_page, previous_page, current_page, next_page, last_page, other_page) * * a_attr: "a" CSS attributes for each page type (sub arrays: first_page, previous_page, current_page, next_page, last_page, other_page) - * * render: Template used for generation. If null, default template is used + * * render: Template used for generation without the default process. If null, default process is used + * * theme: Theme used. If null, default theme is used + * * block: Twig block used. Default: paginator_links */ public function paginatorLinks(Environment $environment, AbstractPaginator $paginator, string $routeName, array $routeParams = [], array $options = []): string { @@ -248,7 +250,7 @@ public function paginatorLinks(Environment $environment, AbstractPaginator $pagi } /** - * Returns CRUD links paginator. + * Displays CRUD links paginator. * * @see CrudExtension::paginatorLinks() */ @@ -264,7 +266,7 @@ public function crudPaginatorLinks(Environment $environment, Crud $crud, array $ } /** - * Returns CRUD th tag. + * Displays CRUD th tag. * * @param string $columnId Column to display * @param array $options Options: @@ -272,7 +274,9 @@ public function crudPaginatorLinks(Environment $environment, Crud $crud, array $ * * label: Th label. If null, CRUD configuration is used * * th_attr: "th" attributes * * a_attr: "a" attributes - * * render: Template used for generation. If null, default template is used + * * render: Template used for generation without the default process. If null, default process is used + * * theme: Theme used. If null, default theme is used + * * block: Twig block used. Default: th */ public function th(Environment $environment, string $columnId, Crud $crud, array $options = []): string { @@ -307,7 +311,7 @@ public function th(Environment $environment, string $columnId, Crud $crud, array ]); $resolver->setAllowedTypes('ajax_options', ['null', 'array']); $resolver->setAllowedTypes('label', ['null', 'string']); - $options = $resolver->resolve($this->buildOptions('th', $options, $crud)); + $options = $resolver->resolve($this->buildOptions('crud_th', $options, $crud)); //If the column is not to be shown, returns empty $sessionValues = $crud->getSessionValues(); @@ -343,14 +347,16 @@ public function th(Environment $environment, string $columnId, Crud $crud, array } /** - * Returns CRUD td tag. + * Displays CRUD td tag. * * @param string $columnId Column to display * @param array $options Options: * * escape: Escape or not value. Default: true * * repeated_values_string: If not null, use this value if the original value is repeated. Default: null * * td_attr: "td" attributes - * * render: Template used for generation. If null, default template is used + * * render: Template used for generation without the default process. If null, default process is used + * * theme: Theme used. If null, default theme is used + * * block: Twig block used. Default: td */ public function td(Environment $environment, string $columnId, Crud $crud, $value, $options = []): string { @@ -366,7 +372,7 @@ public function td(Environment $environment, string $columnId, Crud $crud, $valu $resolver->setAllowedTypes('escape', 'bool'); $resolver->setAllowedTypes('repeated_values_string', ['null', 'string']); $resolver->setAllowedTypes('td_attr', ['null', 'array']); - $options = $resolver->resolve($this->buildOptions('td', $options, $crud)); + $options = $resolver->resolve($this->buildOptions('crud_td', $options, $crud)); //If the column is not to be shown, returns empty $sessionValues = $crud->getSessionValues(); @@ -406,11 +412,13 @@ public function td(Environment $environment, string $columnId, Crud $crud, $valu } /** - * Returns CRUD td tag. + * Returns CRUD display settings button. * * @param array $options Options: * * modal: Use modal or not. Default: true - * * render: Template used for generation. If null, default template is used + * * render: Template used for generation without the default process. If null, default process is used + * * theme: Theme used. If null, default theme is used + * * block: Twig block used. Default: display_settings */ public function displaySettings(Environment $environment, Crud $crud, array $options = []): string { @@ -422,7 +430,7 @@ public function displaySettings(Environment $environment, Crud $crud, array $opt 'block' => 'display_settings', ]); $resolver->setAllowedTypes('modal', 'bool'); - $options = $resolver->resolve($this->buildOptions('display_settings', $options, $crud)); + $options = $resolver->resolve($this->buildOptions('crud_display_settings', $options, $crud)); $form = $crud->getDisplaySettingsForm(); @@ -442,10 +450,14 @@ public function displaySettings(Environment $environment, Crud $crud, array $opt } /** + * Search form start tag. + * * @param array $options Options: * * ajax_options: Ajax options. Default: [] * * form_attr: "form" attributes - * * render: Template used for generation. If null, default template is used + * * render: Template used for generation without the default process. If null, default process is used + * * theme: Theme used. If null, default theme is used + * * block: Twig block used. Default: search_form_start */ public function searchFormStart(Environment $environment, Crud $crud, array $options = []) { @@ -459,7 +471,7 @@ public function searchFormStart(Environment $environment, Crud $crud, array $opt ]); $resolver->setAllowedTypes('ajax_options', ['array']); $resolver->setAllowedTypes('form_attr', ['array']); - $options = $resolver->resolve($this->buildOptions('search_form_start', $options, $crud)); + $options = $resolver->resolve($this->buildOptions('crud_search_form_start', $options, $crud)); if ($options['render']) { return $environment->render($options['render'], [ @@ -494,9 +506,13 @@ public function searchFormStart(Environment $environment, Crud $crud, array $opt } /** + * Search form submit button. + * * @param array $options Options: * * button_attr: "button" attributes - * * render: Template used for generation. If null, default template is used + * * render: Template used for generation without the default process. If null, default process is used + * * theme: Theme used. If null, default theme is used + * * block: Twig block used. Default: search_form_submit */ public function searchFormSubmit(Environment $environment, Crud $crud, $options = [], $ajaxOptions = [], $htmlOptions = []) { @@ -508,7 +524,7 @@ public function searchFormSubmit(Environment $environment, Crud $crud, $options 'block' => 'search_form_submit', ]); $resolver->setAllowedTypes('button_attr', ['array']); - $options = $resolver->resolve($this->buildOptions('search_form_submit', $options, $crud)); + $options = $resolver->resolve($this->buildOptions('crud_search_form_submit', $options, $crud)); if ($options['render']) { return $environment->render($options['render'], [ @@ -524,10 +540,14 @@ public function searchFormSubmit(Environment $environment, Crud $crud, $options } /** + * Search form reset button. + * * @param array $options Options: * * ajax_options: Ajax options. Default: [] * * button_attr: "button" attributes - * * render: Template used for generation. If null, default template is used + * * render: Template used for generation without the default process. If null, default process is used + * * theme: Theme used. If null, default theme is used + * * block: Twig block used. Default: search_form_reset */ public function searchFormReset(Environment $environment, Crud $crud, $options = [], $ajaxOptions = [], $htmlOptions = []) { @@ -541,7 +561,7 @@ public function searchFormReset(Environment $environment, Crud $crud, $options = ]); $resolver->setAllowedTypes('ajax_options', ['array']); $resolver->setAllowedTypes('button_attr', ['array']); - $options = $resolver->resolve($this->buildOptions('search_form_reset', $options, $crud)); + $options = $resolver->resolve($this->buildOptions('crud_search_form_reset', $options, $crud)); if ($options['render']) { return $environment->render($options['render'], [ @@ -572,6 +592,13 @@ public function searchFormReset(Environment $environment, Crud $crud, $options = ])); } + /** + * Ajax form start tag. + * + * @param array $options Options: + * * auto_class: Auto CSS class used. Default: ec-crud-ajax-form-auto + * * ajax_options: See "ajaxAttributes" method options + */ public function formStartAjax(FormView $formView, array $options = []): string { $autoClass = 'ec-crud-ajax-form-auto'; @@ -597,6 +624,23 @@ public function formStartAjax(FormView $formView, array $options = []): string return $this->formRenderer->renderBlock($formView, 'form_start', $options); } + /** + * Displays Ajax attributes. + * + * @param array $ajaxOptions Options: + * * url: Ajax url + * * update: Update the DOM with the response - jquery selector (eg: #mydiv) + * * update_mode: Update the DOM with the response - mode (update / before / after / prepend / append). Default: update + * * on_before_send: Callback + * * on_success: Callback + * * on_error: Callback + * * on_complete: Callback + * * data_type: Request type. Default: html + * * method: Resquest method: Default: POST + * * data: Data sent + * * cache: Use cache. Default: false + * * options: Array of options + */ public function ajaxAttributes(Environment $environment, array $ajaxOptions): string { $this->validateAjaxOptions($ajaxOptions); From abd0e6604b19a54947d19abde2973d70137df879 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sun, 4 Jul 2021 20:02:13 +0200 Subject: [PATCH 066/264] Add doc and src/Resources/assets folder --- .eslintrc.js | 2 +- .github/workflows/tests.yml | 2 +- README.md | 8 + doc/cookbook/advanced-searcher.md | 124 ++++++ doc/cookbook/data-template.md | 29 ++ doc/cookbook/display_results.md | 37 ++ doc/cookbook/div_ids.md | 39 ++ doc/cookbook/paginator.md | 83 ++++ doc/cookbook/rest.md | 3 + doc/cookbook/sort.md | 97 ++++ doc/cookbook/template_configuration.md | 70 +++ doc/cookbook/virtual_columns.md | 38 ++ doc/crud.md | 416 ++++++++++++++++++ doc/index.md | 28 ++ doc/install.md | 103 +++++ doc/references/ajax.md | 180 ++++++++ doc/references/entity_ajax_type.md | 22 + doc/references/filters.md | 174 ++++++++ doc/references/js-callbacks.md | 66 +++ doc/references/modal.md | 117 +++++ doc/references/searcher.md | 16 + doc/references/twig.md | 19 + package.json | 1 + src/Resources/{public => assets}/js/ajax.js | 0 .../{public => assets}/js/callback.js | 0 src/Resources/{public => assets}/js/crud.js | 0 .../js/modal/engine/bootstrap3.js | 0 .../js/modal/modal-manager.js | 0 .../{public => assets}/js/options-resolver.js | 0 .../js/scrollToFirstMessage.js | 0 src/Resources/assets/package.json | 6 + tests/Functional/App/assets/js/app.js | 6 +- .../App/templates/user/list.html.twig | 42 +- tests/Resources/public/js/ajax.spec.js | 2 +- tests/Resources/public/js/callback.spec.js | 2 +- .../Resources/public/js/modal/engine/test.js | 2 +- .../public/js/modal/modal-manager.spec.js | 4 +- .../public/js/options-resolver.spec.js | 2 +- 38 files changed, 1708 insertions(+), 32 deletions(-) create mode 100644 doc/cookbook/advanced-searcher.md create mode 100644 doc/cookbook/data-template.md create mode 100644 doc/cookbook/display_results.md create mode 100644 doc/cookbook/div_ids.md create mode 100644 doc/cookbook/paginator.md create mode 100644 doc/cookbook/rest.md create mode 100644 doc/cookbook/sort.md create mode 100644 doc/cookbook/template_configuration.md create mode 100644 doc/cookbook/virtual_columns.md create mode 100644 doc/crud.md create mode 100644 doc/index.md create mode 100644 doc/install.md create mode 100644 doc/references/ajax.md create mode 100644 doc/references/entity_ajax_type.md create mode 100644 doc/references/filters.md create mode 100644 doc/references/js-callbacks.md create mode 100644 doc/references/modal.md create mode 100644 doc/references/searcher.md create mode 100644 doc/references/twig.md rename src/Resources/{public => assets}/js/ajax.js (100%) rename src/Resources/{public => assets}/js/callback.js (100%) rename src/Resources/{public => assets}/js/crud.js (100%) rename src/Resources/{public => assets}/js/modal/engine/bootstrap3.js (100%) rename src/Resources/{public => assets}/js/modal/modal-manager.js (100%) rename src/Resources/{public => assets}/js/options-resolver.js (100%) rename src/Resources/{public => assets}/js/scrollToFirstMessage.js (100%) create mode 100644 src/Resources/assets/package.json diff --git a/.eslintrc.js b/.eslintrc.js index 2eb458a..76d8e29 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -12,7 +12,7 @@ module.exports = { Atomics: 'readonly', SharedArrayBuffer: 'readonly' }, - ignorePatterns: ['/src/Resources/public/js/scrollToFirstMessage.js'], + ignorePatterns: ['/src/Resources/assets/js/scrollToFirstMessage.js'], parserOptions: { ecmaVersion: 2018, sourceType: 'module' diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7df04f9..bf6d29c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -106,4 +106,4 @@ jobs: - name: Run ESLint if: matrix.coding-standards - run: ./node_modules/.bin/eslint src/Resources/public/js/* tests/Resources/public/js/* + run: ./node_modules/.bin/eslint src/Resources/assets/js/* tests/Resources/public/js/* diff --git a/README.md b/README.md index 24b9200..54bac68 100644 --- a/README.md +++ b/README.md @@ -5,3 +5,11 @@ **WARNING: This branch is under development. Not use in production. You can use stable versions or 2.6 branch.** + +## Documentation ## + +[Read the Documentation (French documentation)](doc/index.md) + +## License ## + +This bundle is available under the MIT license. See the complete license in the *LICENSE* file. diff --git a/doc/cookbook/advanced-searcher.md b/doc/cookbook/advanced-searcher.md new file mode 100644 index 0000000..3b0fe4d --- /dev/null +++ b/doc/cookbook/advanced-searcher.md @@ -0,0 +1,124 @@ +# Personnalisation avancée de la classe Searcher + +## Solution 1: Utilisation de addField et updateQueryBuilder + +```php +addFilter('id', Filter\IntegerFilter::class, [ + 'comparator' => Filter\IntegerFilter::EQUAL, + ]); + + //Ajout manuel d'un champ dans filtre de recherche + $builder->addField('name', TextType::class, [ + 'required' => false, + ]); + } + + public function updateQueryBuilder($queryBuilder, array $options): void + { + //Traitement du filtre de recherche "name" + if (null !== $this->name) { + $queryBuilder->andWhere('c1.name = :name') + ->setParameter('name', $this->name); + } + } +} +``` + +## Solution 2: Utilisation d'une classe FormType + +```php +name) { + $queryBuilder->andWhere('c1.name = :name') + ->setParameter('name', $this->name); + } + } +} +``` + +> **_REMARQUE:_** Pensez à la validation de vos champs. + + +```php +addField('name', TextType::class, [ + 'required' => false, + ]); + } +} +``` + +```diff +createCrud('my_crud'); //Passé en argument: Nom du CRUD + $crud->addColumn('id', 'c1.id', 'Id') + //... + ->setRoute('my_crud_ajax') +- ->createSearchForm(new CarSearcher()) ++ ->createSearchForm(new CarSearcher(), CarSearcherType::class , [ ++ //Options ++ ]) + //... + ->init(); + + return $crud; + } +} +``` diff --git a/doc/cookbook/data-template.md b/doc/cookbook/data-template.md new file mode 100644 index 0000000..a218d2e --- /dev/null +++ b/doc/cookbook/data-template.md @@ -0,0 +1,29 @@ +# Ajout de données aux templates + +Par défaut, un contrôleur héritant de `AbstractCrudController` ou utilisant `CrudControllerTrait` retourne aux templates l'objet Crud (nom de variable Twig: `crud`). + +Pour passer des variables supplémentaires aux templates Twig, peuvent être surchargées les méthodes : +* `beforeBuildQuery` : Ajouter des données avant la génération de la requête Doctrine +* `afterBuildQuery` : Ajouter des données après la génération de la requête Doctrine + +Exemple : + +```php +createCrud('my_crud'); //Passé en argument: Nom du CRUD + $crud->addColumn('id', 'c1.id', 'Id') + //... + ->setRoute('my_crud_ajax') ++ ->setDisplayResultsOnlyIfSearch(true) + //... + ->init(); + + return $crud; + } + + //... +} +``` diff --git a/doc/cookbook/div_ids.md b/doc/cookbook/div_ids.md new file mode 100644 index 0000000..b13760b --- /dev/null +++ b/doc/cookbook/div_ids.md @@ -0,0 +1,39 @@ +# Personnaliser les IDs des Divs + +Par défaut, les IDs des DIVs de liste et recherche sont : + +| DIV | ID | +| --- | --- | +| DIV liste des résultats | crud_list | +| DIV formulaire de recherche | crud_search | + +Ces IDs peuvent être changés par les méthodes `setDivIdList` et `setDivIdSearch`: + +```diff +createCrud('my_crud'); //Passé en argument: Nom du CRUD + $crud->addColumn('id', 'c1.id', 'Id') + //... + ->setRoute('my_crud_ajax') ++ ->setDivIdList('my_div1') ++ ->setDivIdSearch('my_div2') + //... + ->init(); + + return $crud; + } + + //... +} +``` diff --git a/doc/cookbook/paginator.md b/doc/cookbook/paginator.md new file mode 100644 index 0000000..a80d80f --- /dev/null +++ b/doc/cookbook/paginator.md @@ -0,0 +1,83 @@ +# Création manuelle du paginator + +Par défaut, un paginator `Ecommit\CrudBundle\Paginator\AbstractDoctrinePaginator` est automatiquement créé. Le comportement par défaut peut être modifié par +la méthode `setBuildPaginator`. + +**Exemple 1 - Options de génération :** + +```diff +createCrud('my_crud'); //Passé en argument: Nom du CRUD + $crud->addColumn('id', 'c1.id', 'Id') + //... + ->setRoute('my_crud_ajax') ++ ->setBuildPaginator([ ++ //Options de Ecommit\CrudBundle\DoctrineExtension\Paginate::createDoctrinePaginator ++ 'behavior' => 'identifier_by_sub_request', ++ 'identifier' => 'c1.id', ++ 'count_options' => [ ++ 'behavior' => 'count_by_alias', ++ 'alias' => 'c1.id', ++ ], ++ ]) + //... + ->init(); + + return $crud; + } + + //... +} +``` + +**Exemple 2 - Création manuelle du paginator :** + +```diff +createCrud('my_crud'); //Passé en argument: Nom du CRUD + $crud->addColumn('id', 'c1.id', 'Id') + //... + ->setRoute('my_crud_ajax') ++ ->setBuildPaginator(function ($queryBuilder, $page, $resultsPerPage) { ++ $paginator = new DoctrineORMPaginator($resultsPerPage); ++ ++ $queryBuilder->andWhere('c1.active = 1'); ++ $paginator->setQueryBuilder($queryBuilder); ++ $paginator->setPage($page); ++ $paginator->init(); ++ ++ return $paginator; ++ }) + //... + ->init(); + + return $crud; + } + + //... +} +``` diff --git a/doc/cookbook/rest.md b/doc/cookbook/rest.md new file mode 100644 index 0000000..8787031 --- /dev/null +++ b/doc/cookbook/rest.md @@ -0,0 +1,3 @@ +# Utilisation d'un CRUD avec REST + +Documentation disponible prochainement. diff --git a/doc/cookbook/sort.md b/doc/cookbook/sort.md new file mode 100644 index 0000000..136213f --- /dev/null +++ b/doc/cookbook/sort.md @@ -0,0 +1,97 @@ +# Tri par défaut personnalisé + +La méthode `setDefaultSort` permet de définir le tri par défaut : + +```php +createCrud('my_crud'); //Passé en argument: Nom du CRUD + $crud->addColumn('id', 'c1.id', 'Id') + //... + ->setRoute('my_crud_ajax') + ->setDefaultSort('id', Crud::ASC) + //... + ->init(); + + return $crud; + } + + //... +} +``` + +Sur deux colonnes (ici va trier sur c1.id puis c1.name) : + +```php +createCrud('my_crud'); //Passé en argument: Nom du CRUD + $crud->addColumn('id', 'c1.id', 'Id', ['alias_sort' => ['c1.id', 'c1.name']]) + //... + ->setRoute('my_crud_ajax') + ->setDefaultSort('id', Crud::ASC) + //... + ->init(); + + return $crud; + } + + //... +} +``` + +Il est aussi possible de définir un tri par défaut personnalisé grâce à la méthode `setDefaultPersonalizedSort` : + +```php +createCrud('my_crud'); //Passé en argument: Nom du CRUD + $crud->addColumn('id', 'c1.id', 'Id') + //... + ->setRoute('my_crud_ajax') + ->setDefaultPersonalizedSort([ + 'c1.purchaseDate' => Crud::DESC, + 'c1.id' => Crud::ASC, + ]) + //... + ->init(); + + return $crud; + } + + //... +} +``` diff --git a/doc/cookbook/template_configuration.md b/doc/cookbook/template_configuration.md new file mode 100644 index 0000000..3c24dd1 --- /dev/null +++ b/doc/cookbook/template_configuration.md @@ -0,0 +1,70 @@ +# Configuration par défaut des templates des CRUD + +Il est possible de définir les options par défaut des fonctions Twig suivantes : +* paginator_links +* crud_paginator_links +* crud_th +* crud_td +* crud_display_settings +* crud_search_form_start +* crud_search_form_submit +* crud_search_form_reset + +Ces options par défaut peuvent être définies : +* Pour l'application +* Pour un CRUD + + +L'ordre de priorité prise en compte pour les options est le suivant : +* Options définies lors de l'appel de la méthode Twig +* Options définies dans le CRUD +* Options définies dans l'application +* Options par défaut de EcommitCrudBundle + +## Options définies dans l'application + +```yaml +#config/packages/ecommit_crud.yaml +ecommit_crud: + twig_functions_configuration: + #Nom de la fonction Twig + crud_td: + #Options par défaut + escape: false +``` + +## Options définies dans le CRUD + +```diff +createCrud('my_crud'); //Passé en argument: Nom du CRUD + $crud->addColumn('id', 'c1.id', 'Id') + //... + ->setRoute('my_crud_ajax') ++ ->setTwigFunctionsConfiguration([ ++ //Nom de la fonction Twig ++ 'crud_td' => [ ++ //Options par défaut ++ 'escape' => false, ++ ], ++ ]) + //... + ->init(); + + return $crud; + } + + //... +} +``` diff --git a/doc/cookbook/virtual_columns.md b/doc/cookbook/virtual_columns.md new file mode 100644 index 0000000..97c22e6 --- /dev/null +++ b/doc/cookbook/virtual_columns.md @@ -0,0 +1,38 @@ +# Colonnes virtuelles + +Des colonnes virtuelles peuvent être ajoutées au CRUD. Une colonne virtuelle ne s'affiche pas dans la liste des +résultats mais les utilisateurs peuvent faire des recherches dessus. + +Pour ajouter une colonne virtuelle, la méthode `addVirtualColumn` doit être utilisée. + +Cette méthode prend comme paramètres: +* Id de la colonne **Requis** +* Alias Doctrine utilisé lors de la recherche SQL. **Requis** + +```diff +createCrud('my_crud'); //Passé en argument: Nom du CRUD + $crud->addColumn('id', 'c1.id', 'Id') + //... ++ ->addVirtualColumn('my_virtual_column', 'c1.name') + ->setRoute('my_crud_ajax') + //... + ->init(); + + return $crud; + } + + //... +} +``` diff --git a/doc/crud.md b/doc/crud.md new file mode 100644 index 0000000..c255320 --- /dev/null +++ b/doc/crud.md @@ -0,0 +1,416 @@ +# Création d'un CRUD + +## Introduction + +Supposons que notre projet possède ces 2 entités Doctrine : + +```php +name; + } + + //Getters and Setters + + //... +} +``` + +## Création du contrôleur + +Nous devons créer une classe contrôleur héritant la classe abstraite `Ecommit\CrudBundle\Controller\AbstractCrudController`. + +Notre classe doit surcharger les méthodes abstraites : +* getCrud +* getTemplateName + +```php +getDoctrine()->getManager(); + $queryBuilder = $em->createQueryBuilder(); + $queryBuilder->from(Car::class, 'c1') + ->select('c1, c2') + ->innerJoin('c1.category', 'c2'); + + $crud = $this->createCrud('my_crud'); //Passé en argument: Nom du CRUD + $crud->addColumn('id', 'c1.id', 'Id') + ->addColumn('name', 'c1.name', 'Name') + ->addColumn('category', 'c2.name', 'Category', ['alias_search' => 'c2.id']) + ->addColumn('purchase_date', 'c1.purchaseDate', 'Purchase date') + ->addColumn('active', 'c1.active', 'Active') + ->setQueryBuilder($queryBuilder) + ->setAvailableResultsPerPage([2, 5, 10], 5) + ->setDefaultSort('id', Crud::ASC) + ->setRoute('my_crud_ajax') + ->setPersistentSettings(true) //Enregistre les paramètres d'affichage en base de données. Par défaut: false + ->init(); + + return $crud; + } + + protected function getTemplateName(string $action): string + { + return sprintf('my_crud/%s.html.twig', $action); + } + + /** + * @Route("/my-crud", name="my_crud") + */ + public function crudAction() + { + return $this->getCrudResponse(); + } + + /** + * @Route("/my-crud/ajax", name="my_crud_ajax") + */ + public function ajaxCrudAction() + { + return $this->getAjaxCrudResponse(); + } +} +``` + +Explications de configCrud(): + +* Avec `$this->createCrud('my_crud')`, nous créons un CRUD (instance de `Ecommit\CrudBundle\Crud\Crud`) dont le nom est `my_crud`. +* Nous déclarons chaque colonne du CRUD par la méthode `addColumn()`. Cette méthode prend en paramètres : + * Id de la colonne (Nous donnons un ID à la colonne). Cet ID est totalement indépendant des noms de colonnes DQL/SQL. **Dans ce document, à chaque fois que nous parlerons de l'ID d'une colonne, c'est ce paramètre qui sera concerné. Requis** + * Alias Doctrine (du query builder) utilisé pour la requête Doctrine **Requis** + * Label donné à la colonne (le label sera traduit si traduction est active sur le projet) **Requis** + * Tableau d'options (facultatif): + * **sortable**: Booléen qui définit si on active (ou non) le tri sur cette colonne **Défaut: True** + * **default_displayed**: Booléen qui définit si on affiche (ou non) cette colonne par défaut **Défaut: True** + * **alias_search**: Alias Doctrine utilisé lors de la recherche DQL/SQL. Si pas défini, utilise l'alias Doctrine défini en 2ème paramètre + * **alias_sort**: Alias Doctrine (chaine de caractères ou tableau de chaines de caractères) utilisé(s) lors du tri sur cette colonne. Si pas défini, utilise l'alias Doctrine défini en 2ème paramètre +* Nous donnons la requête Doctrine (sous forme d'objet QueryBuilder ou d'une fonction anonyme) avec la méthode setQueryBuilder() Le moteur du CRUD modifiera automatiquement cette requête, en fonction des actions demandées par l'utilisateur +* Nous définissions les paramètres du nombre de pages avec la méthode `setAvailableResultsPerPage()`. Cette méthode prend 2 paramètres : + * Un tableau contenant les différents nombres possibles du nombre de résultats par page. **Requis** + * Le nombre de résultats par page, par défaut. **Requis** +* Nous définissions le tri par défaut par la méthode `setDefaultSort`. Cette méthode prend 2 paramètres : + * L'id de la colonne utilisée pour le tri par défaut **Requis** + * Le sens du tri par défaut (`Crud::ASC` ou `Crud::DESC`). **Requis** +* On définit par la fonction `setRoute` la route Ajax utilisée pour mettre à jour notre liste +* On active par la fonction `setPersistentSettings` l'enregistrement des propriétés d'affichage des utilisateurs en base de données. Par défaut, désactivé. +* On initialise le Crud par la méthode `init()`. +* On retourne notre objet créé + +## Ajout templates + +```twig +{# templates/my_crud/index.html.twig #} +{% extends 'layout.html.twig' %} + +{% block content %} +
+ {% include 'my_crud/list.html.twig' %} +
+{% endblock %} +``` + +```twig +{# templates/my_crud/list.html.twig #} +{% if crud.paginator %} +
+ + + + {{ crud_th('id', crud) }} + {{ crud_th('name', crud) }} + {{ crud_th('category', crud) }} + {{ crud_th('purchase_date', crud) }} + {{ crud_th('active', crud) }} + + + + {% for car in crud.paginator %} + + {{ crud_td('id', crud, car.id) }} + {{ crud_td('name', crud, car.name) }} + {{ crud_td('category', crud, car.category.name) }} + {{ crud_td('purchase_date', crud, car.purchaseDate|date('Y-m-d')) }} + {{ crud_td('active', crud, (car.active) ? 'yes' : 'no') }} + + {% endfor %} + +
+
+ + +
+ {{ 'pagination_count_results'|trans({'count': crud.paginator.countResults}) }}
+ {{ 'pagination_indices'|trans({'first': crud.paginator.firstIndice, 'last': crud.paginator.lastIndice}) }} - + {{ 'pagination_pages'|trans({'page': crud.paginator.page, 'lastPage': crud.paginator.lastPage}) }} +
+ + {{ crud_paginator_links(crud) }} +{% endif %} + +{{ crud_display_settings(crud) }} +``` + +Vous pouvez ajouter par exemple les traductions suivantes (+intl-icu) à votre projet : + +```yaml +# translations/messages+intl-icu.fr.yaml +pagination_count_results: > + {count, plural, + =0 {Pas de résultat} + one {1 résultat trouvé} + other {# résultats trouvés} + } +pagination_indices: Résultats {first}-{last} +pagination_pages: Page {page}/{lastPage} +``` + +## Ajout formulaire de recherche (FACULTATIF) + +La classe Searcher représente les champs du formulaire de recherche. + +Si vous ne désirez pas activer le formulaire de recherche sur le CRUD, passez ce paragraphe. + +Note classe doit hériter de `Ecommit\CrudBundle\Form\Searcher\AbstractSearcher` (ou implémenter `Ecommit\CrudBundle\Form\Searcher\SearcherInterface`) : + +```php +addFilter('id', Filter\IntegerFilter::class, [ + 'comparator' => Filter\IntegerFilter::EQUAL, + ]); + + $builder->addFilter('name', Filter\TextFilter::class, [ + 'must_begin' => true, + ]); + + $builder->addFilter('nameEmpty', Filter\NullFilter::class, [ + //On ne souhaite pas relier le filtre "nameEmpty" à la colonne "nameEmpty" du CRUD (qui n'existe pas). + //On précise donc manuellement l'ID de la colonne + 'column_id' => 'name', + 'type_options' => [ + 'label' => 'Name empty', + ], + ]); + + $builder->addFilter('purchaseBeginningDate', Filter\DateFilter::class, [ + 'comparator' => Filter\DateFilter::GREATER_EQUAL, + 'column_id' => 'purchase_date', + 'type_options' => [ + 'label' => 'Purchase date - From' + ], + ]); + + $builder->addFilter('purchaseEndDate', Filter\DateFilter::class, [ + 'comparator' => Filter\DateFilter::SMALLER_EQUAL, + 'column_id' => 'purchase_date', + 'type_options' => [ + 'label' => 'Purchase date - To' + ], + ]); + + $builder->addFilter('active', Filter\BooleanFilter::class); + + $builder->addFilter('category', Filter\EntityFilter::class, [ + 'class' => Category::class, + 'multiple' => 'true', + ]); + } +} +``` + +> **_REMARQUE:_** La liste des différents filtres disponibles (ainsi que leurs configurations) est disponible [ici](references/filters.md) + +> **_REMARQUE:_** Il est aussi possible de faire des recherches plus complexes sans utiliser les filtres pré-définis. [En savoir plus](cookbook/advanced-searcher.md) + +Une fois la classe Searcher créée, nous devons modifier notre contrôleur : + +```diff +createCrud('my_crud'); //Passé en argument: Nom du CRUD + $crud->addColumn('id', 'c1.id', 'Id') + //... + ->setRoute('my_crud_ajax') ++ ->createSearchForm(new CarSearcher()) + //... + ->init(); + + return $crud; + } +} +``` + +Nous devons ensuite créer un nouveau template Twig : + +```twig +{# templates/my_crud/search.html.twig #} +{{ crud_search_form_start(crud) }} + {{ form_row(crud.searchForm.id) }} + {{ form_row(crud.searchForm.name) }} + {{ form_row(crud.searchForm.nameEmpty) }} + {{ form_row(crud.searchForm.purchaseBeginningDate) }} + {{ form_row(crud.searchForm.purchaseEndDate) }} + {{ form_row(crud.searchForm.active) }} + {{ form_row(crud.searchForm.category) }} + +
+ {{ crud_search_form_submit(crud) }} + {{ crud_search_form_reset(crud) }} +
+ +``` + +Et enfin modifier le template principal : + +```twig +{# templates/my_crud/index.html.twig #} +{% extends 'layout.html.twig' %} + +{% block content %} + + +
+ {% include 'my_crud/list.html.twig' %} +
+{% endblock %} +``` + +> **_REMARQUE:_** [En savoir plus sur la configuration du formulaire de recherche](references/searcher.md) + + + +> **_REMARQUE:_** [En savoir plus sur l'utilisation avancée du CRUD](index.md#fonctionnalités-avancées) diff --git a/doc/index.md b/doc/index.md new file mode 100644 index 0000000..a179a65 --- /dev/null +++ b/doc/index.md @@ -0,0 +1,28 @@ +# EcommitCrudBundle - Documentation + +## Pour commmencer + +* [Installation](install.md) +* [Création d'un CRUD](crud.md) + +## Références + +* [Options du formulaire de recherche](references/searcher.md) +* [Filtres du formulaire de recherche](references/filters.md) +* [EntityAjaxType](references/entity_ajax_type.md) +* [Fonctions Twig](references/twig.md) +* [Fonctions Ajax](references/ajax.md) +* [Gestionnaire de fenêtre modale](references/modal.md) +* [Callbacks JavaScript](references/js-callbacks.md) + +## Fonctionnalités avancées + +* [Personnalisation avancée de la classe Searcher](cookbook/advanced-searcher.md) +* [Ajout de données aux templates](cookbook/data-template.md) +* [Affichage des résultats uniquement si recherche envoyée](cookbook/display_results.md) +* [Personnaliser les IDs des Divs](cookbook/div_ids.md) +* [Création manuelle du paginator](cookbook/paginator.md) +* [Utilisation d'un CRUD avec REST](cookbook/rest.md) +* [Tri par défaut personnalisé](cookbook/sort.md) +* [Configuration par défaut des templates des CRUD](cookbook/template_configuration.md) +* [Colonnes virtuelles](cookbook/virtual_columns.md) diff --git a/doc/install.md b/doc/install.md new file mode 100644 index 0000000..a748ebe --- /dev/null +++ b/doc/install.md @@ -0,0 +1,103 @@ +# Installation + +Prérequis : +* Projet Symfony fonctionnel +* Entité utilisateur avec Doctrine +* Yarn +* Webpack Encore +* jQuery +* Un gestionnaire de thème chargé par Webpack Encore parmi : + * Bootstrap 3 + * Votre thème personnalisé (créer un thème Twig qui hérite `@EcommitCrud/Theme/base.html.twig`) +* Un gestionnaire d'icones chargé par Webpack Encore parmi : + * Fontawesome 4 + * Votre thème personnalisé (créer un thème Twig qui hérite `@EcommitCrud/IconTheme/base.html.twig`) + +Installez le bundle avec Composer : A la racine de votre projet Symfony, éxécutez la commande suivante : + +```bash +$ composer require ecommit/crud-bundle:3.*@dev +$ yarn add --dev @ecommit/crud-bundle@link:vendor/ecommit/crud-bundle/src/Resources/assets +``` + +Activez le bundle dans le fichier de configuration `config/bundles.php` de votre projet : + +```php +return [ + //... + Ecommit\CrudBundle\EcommitCrudBundle::class => ['all' => true], + //... +]; +``` + +Ajoutez à votre projet le fichier de configuration `config/packages/ecommit_crud.yaml` : + +```yaml +ecommit_crud: + #Theme + #Themes disponibles : + #@EcommitCrud/Theme/base.html.twig + #@EcommitCrud/Theme/bootstrap3.html.twig (boostrap3 requis) + theme: '@EcommitCrud/Theme/bootstrap3.html.twig' + + #Theme pour les icones + #Themes disponibles : + #@EcommitCrud/IconTheme/base.html.twig + #@EcommitCrud/IconTheme/fontawesome4.html.twig (fontawesome4 requis) + icon_theme: '@EcommitCrud/IconTheme/fontawesome4.html.twig' +``` + +Votre entité Doctrine "utilisateur" doit implémenter l'interface `Ecommit\CrudBundle\Entity\UserCrudInterface`. Exemple : + +```php +Go ! +``` + +### link + +Fonction permettant de faire une requête AJAX lors d'un clic sur lien. + +Toutes les options de la fonction `sendRequest` peuvent être utilisées en les passant par les attributs `data-`. Pour cela: +* Préfixer chaque option par `data-ec-crud-ajax-` +* Les options d'origine (en JavaScript) de `sendRequest` sont en camelCase. Pour leur écriture par des attributs HTML, remplacer chaque nouveau mot en majuscule par un tiret. + Exemple: L'équivalent de l'option `updateMode` est `data-ec-crud-ajax-update-mode` en attribut HTML. + +#### Mode automatique + +Le lien doit avoir comme classe CSS `ec-crud-ajax-link-auto`. + +Exemple : + +```html +Go ! +``` + +L'URL utilisée pour la requête Ajax est: +* La valeur de l'attribut `data-ec-crud-ajax-url` (si présent) +* Ou la valeur de `href` + +#### Mode manuel + +```html +Go ! +``` + +```js +import * as ajax from '@ecommit/crud-bundle/js/ajax'; + +//Argument n°1: Lien +//Argument n°2: Options de sendRequest +ajax.link($('#linkToTest'), { + //Options de sendRequest + method: 'GET', +}); +``` + +* L'URL utilisée pour la requête Ajax est: + * La valeur de l'attribut `data-ec-crud-ajax-url` (si présent) + * Ou la valeur de l'option `url` de la fonction `link` (si présent) + * Ou la valeur de `href` +* Les attributs `data-ec-crud-ajax-*` (si présents) écrasent les options de la fonction `link` (si présent) + +### sendForm + +Fonction permettant de faire une requête AJAX depuis l'envoi d'un formulaire. + +Toutes les options de la fonction `sendRequest` peuvent être utilisées en les passant par les attributs `data-`. Pour cela: +* Préfixer chaque option par `data-ec-crud-ajax-` +* Les options d'origine (en JavaScript) de `sendRequest` sont en camelCase. Pour leur écriture par des attributs HTML, remplacer chaque nouveau mot en majuscule par un tiret. + Exemple: L'équivalent de l'option `updateMode` est `data-ec-crud-ajax-update-mode` en attribut HTML. + +#### Mode automatique + +Le formulaire doit avoir comme classe CSS `ec-crud-ajax-form-auto`. + +Exemple : + +```html +
+``` + +* L'URL utilisée pour la requête Ajax est: + * La valeur de l'attribut `data-ec-crud-ajax-url` (si présent) + * Ou la valeur de `action` +* La méthode utilisée pour la requête Ajax est: + * La valeur de l'attribut `data-ec-crud-ajax-method` (si présent) + * Ou la valeur de `method` +* Les données envoyées lors de la requête Ajax sont: + * La valeur de l'attribut `data-ec-crud-ajax-data` (si présent) + * Ou les données du formulaire + +#### Mode manuel + +```html + +``` + +```js +import * as ajax from '@ecommit/crud-bundle/js/ajax'; + +//Argument n°1: Formulaire +//Argument n°2: Options de sendRequest +ajax.sendForm($('#formToTest'), { + //Options de sendRequest + cache: true, +}); +``` + +* L'URL utilisée pour la requête Ajax est: + * La valeur de l'attribut `data-ec-crud-ajax-url` (si présent) + * Ou la valeur de l'option `url` de la fonction `sendForm` (si présent) + * Ou la valeur de `action` +* La méthode utilisée pour la requête Ajax est: + * La valeur de l'attribut `data-ec-crud-ajax-method` (si présent) + * Ou la valeur de l'option `method` de la fonction `sendForm` (si présent) + * Ou la valeur de `method` +* Les données envoyées lors de la requête Ajax sont: + * La valeur de l'attribut `data-ec-crud-ajax-data` (si présent) + * Ou la valeur de l'option `data` de la fonction `sendForm` (si présent) + * Ou les données du formulaire +* Les attributs `data-ec-crud-ajax-*` (si présents) écrasent les options de la fonction `sendForm` (si présent) + + +### updateDom + +Permet la mise à jour du DOM. + +```js +import * as ajax from '@ecommit/crud-bundle/js/ajax'; + +//Argument n°1: Element à mettre à jour +//Argument n°2: Méthode de mise à jour +//Argument n°3: Contenu +ajax.updateDom($('#myDiv'), 'update', 'Hello world'); +``` + +Méthodes disponibles pour la mise à jour : + +| Méthode | Description | +| ------- | ----------- | +| update | Utilise le fonction [`html` de jQuery](https://api.jquery.com/html/) | +| before | Utilise le fonction [`before` de jQuery](https://api.jquery.com/before/) | +| after | Utilise le fonction [`after` de jQuery](https://api.jquery.com/after/) | +| prepend | Utilise le fonction [`prepend` de jQuery](https://api.jquery.com/prepend/) | +| append | Utilise le fonction [`append` de jQuery](https://api.jquery.com/append/) | diff --git a/doc/references/entity_ajax_type.md b/doc/references/entity_ajax_type.md new file mode 100644 index 0000000..1fcb753 --- /dev/null +++ b/doc/references/entity_ajax_type.md @@ -0,0 +1,22 @@ +# EntityAjaxType + +**Classe / Service** : `Ecommit\CrudBundle\Form\Type\EntityAjaxType` + +Type de formulaire qui permet de sélectionner une entité Doctrine dans une liste déroulante dont les résultats sont +fournis en Ajax (idéal pour les longues listes déroulantes). + +Peut être par exemple utilisé avec [Select2](https://select2.org/) + + +## Options + +| Option | Description | Requis | Valeur par défaut | +| ------ | ----------- | --------| ----------------- | +| class | Classe de l'entité Doctrine | Oui | | +| route_name | Route Symfony pour la requête Ajax | Oui | | +| multiple | Choix multiple nou non | Non | Non | +| em | Entity Manager Doctrine à utiliser | Non | Entity Manager par défaut en fonction de l'option `class` | +| query_builder | Query Builder à utiliser | Non | | +| choice_label | Rendu des labels | Non | | +| route_params | Paramètres de la route Symfony pour la requête Ajax | Non | [ ] | +| max_elements | Nombre d'éléments maxi pouvant être sélectionnés | Non | 10000 | diff --git a/doc/references/filters.md b/doc/references/filters.md new file mode 100644 index 0000000..a1ed941 --- /dev/null +++ b/doc/references/filters.md @@ -0,0 +1,174 @@ +# Filtres + +## Options générales pour tous les filtres + +| Option | Description | Requis | Valeur par défaut | +| ------ | ----------- | --------| ----------------- | +| column_id | ID de la colonne du CRUD associée | Non | Nom du filtre +| autovalidate | Si `true`, active la validation automatique (en fonction du filtre utilisé) | Non | [Valeur de autovalidate du formulaire](searcher.md) | +| validation_groups | Groupe de validation | Non | [Valeur de validation_groups du formulaire](searcher.md) | +| required | Si `true`, filtre obligatoire | Non | Non | +| type_options | Options du champs du formulaire Symfony généré | Non | [ ] | + + +## BooleanFilter + +**Description** : Case à cocher pour filtrer un booléen. + +**Classe** : `Ecommit\CrudBundle\Form\Filter\BooleanFilter` + +**Options** : + +| Option | Description | Requis | Valeur par défaut | +| ------ | ----------- | --------| ----------------- | +| value_true | Valeur du booléen (pour la recherche) dans sa valeur "vrai" | Non | 1 | +| value_false | Valeur du booléen (pour la recherche) dans sa valeur "faux" | Non | 0 | +| not_null_is_true | Si `true`, toute valeur non nulle (pour la recherche) est considérée comme "vrai" | Non | false | +| null_is_false | Si `true`, toute valeur nulle (pour la recherche) est considérée comme "faux" | Non | true | + +Voir aussi les [options générales](#options-générales-pour-tous-les-filtres) + + +## ChoiceFilter + +**Description** : Choix de valeur(s) parmi une liste + +**Classe** : `Ecommit\CrudBundle\Form\Filter\ChoiceFilter` + +**Options** : + +| Option | Description | Requis | Valeur par défaut | +| ------ | ----------- | --------| ----------------- | +| multiple | Si `true`, autorise le choix multiple | Non | false | +| min | Si défini et option multiple à `true`, nombre mini d'éléments à sélectionner | Non | null | +| max | Nombre mini d'éléments sélectionnables | Non | 1000 | +| choices | Valeurs à proposer | Non | [ ] | +| type | "Type" de formulaire Symfony à utiliser | Non | Symfony\Component\Form\Extension\Core\Type\ChoiceType | + +Voir aussi les [options générales](#options-générales-pour-tous-les-filtres) + + +## DateFilter + +**Description** : Filtre d'une date + +**Classe** : `Ecommit\CrudBundle\Form\Filter\DateFilter` + +**Options** : + +| Option | Description | Requis | Valeur par défaut | +| ------ | ----------- | --------| ----------------- | +| comparator | Signe de comparaison (>, >= <, <=, =) à utiliser pour filtrer la date. Utiliser une constante de `Ecommit\CrudBundle\Form\Filter\DateFilter` | Oui | | +| with_time | Si `true`, gère aussi l'heure | Non | false| + +Voir aussi les [options générales](#options-générales-pour-tous-les-filtres) + + +## EntityFilter + +**Description** : Filtre une entité Doctrine + +**Classe** : `Ecommit\CrudBundle\Form\Filter\EntityFilter` + +**Options** : + +| Option | Description | Requis | Valeur par défaut | +| ------ | ----------- | --------| ----------------- | +| multiple | Si `true`, autorise le choix multiple | Non | false | +| min | Si défini et option multiple à `true`, nombre mini d'éléments à sélectionner | Non | null | +| max | Nombre mini d'éléments sélectionnables | Non | 1000 | +| class | Classe de l'entité Doctrine | Oui | | + + +Voir aussi les [options générales](#options-générales-pour-tous-les-filtres) + + +## EntityAjaxFilter + +**Description** : Filtre une entité Doctrine en utilisant [`Ecommit\CrudBundle\Form\Type\EntityAjaxType`](entity_ajax_type.md) + +**Classe** : `Ecommit\CrudBundle\Form\Filter\EntityAjaxFilter` + +**Options** : + +| Option | Description | Requis | Valeur par défaut | +| ------ | ----------- | --------| ----------------- | +| multiple | Si `true`, autorise le choix multiple | Non | false | +| min | Si défini et option multiple à `true`, nombre mini d'éléments à sélectionner | Non | null | +| max | Nombre mini d'éléments sélectionnables | Non | 1000 | +| class | Classe de l'entité Doctrine | Oui | | +| route_name | Route à utiliser pour la recherche Ajax | Oui | | +| route_params | Paramètres à utiliser pour la recherche Ajax | Non | [ ] | + +Voir aussi les [options générales](#options-générales-pour-tous-les-filtres) + +Voir la [documentation](entity_ajax_type.md) de `Ecommit\CrudBundle\Form\Type\EntityAjaxType` + + +## IntegerFilter + +**Description** : Filtre un entier + +**Classe** : `Ecommit\CrudBundle\Form\Filter\IntegerFilter` + +**Options** : + +| Option | Description | Requis | Valeur par défaut | +| ------ | ----------- | --------| ----------------- | +| comparator | Signe de comparaison (>, >= <, <=, =) à utiliser pour filtrer la valeur. Utiliser une constante de `Ecommit\CrudBundle\Form\Filter\IntegerFilter` | Oui | | + +Voir aussi les [options générales](#options-générales-pour-tous-les-filtres) + + +## NotNullFilter + +**Description** : Filtre toute valeur non nulle + +**Classe** : `Ecommit\CrudBundle\Form\Filter\NotNullFilter` + +**Options** : [Options générales](#options-générales-pour-tous-les-filtres) + + +## NullFilter + +**Description** : Filtre toute valeur nulle + +**Classe** : `Ecommit\CrudBundle\Form\Filter\NullFilter` + +**Options** : [Options générales](#options-générales-pour-tous-les-filtres) + + +## NumberFilter + +**Description** : Filtre une valeur numérique + +**Classe** : `Ecommit\CrudBundle\Form\Filter\NumberFilter` + +**Options** : + +| Option | Description | Requis | Valeur par défaut | +| ------ | ----------- | --------| ----------------- | +| comparator | Signe de comparaison (>, >= <, <=, =) à utiliser pour filtrer la valeur. Utiliser une constante de `Ecommit\CrudBundle\Form\Filter\NumberFilter` | Oui | | + +Voir aussi les [options générales](#options-générales-pour-tous-les-filtres) + + + +## TextFilter + +**Description** : Filtre un texte + +**Classe** : `Ecommit\CrudBundle\Form\Filter\TextFilter` + +**Options** : + +| Option | Description | Requis | Valeur par défaut | +| ------ | ----------- | --------| ----------------- | +| must_begin | Si `true`, utilise le filtre par "qui commence par" | Non | false | +| must_end | Si `true`, utilise le filtre par "qui se termine par" | Non | false | +| min_length | Si définie, longueur minimale de la longueur du terme recherché | Non | null | +| max_length | Si définie, longueur maximale de la longueur du terme recherché | Non | 255 | +| type | "Type" de formulaire Symfony à utiliser | Non | Symfony\Component\Form\Extension\Core\Type\TextType | + + +Voir aussi les [options générales](#options-générales-pour-tous-les-filtres) diff --git a/doc/references/js-callbacks.md b/doc/references/js-callbacks.md new file mode 100644 index 0000000..0683180 --- /dev/null +++ b/doc/references/js-callbacks.md @@ -0,0 +1,66 @@ +# Callbacks JavaScript + +## Définition des callbacks + +```js +//Methode 1 +var callback = function (arg) { + alert('go'); + //... +}; + +//Methode 2 +var callback = 'alert("go")'; + +//Méthode 3 +var callback = 'function (arg) { alert("go"); }'; +``` + + +Gestion de plusieurs callbacks : + +```js +var callbacks = [ + //Callback 1 + function (arg) { + alert('go'); + //... + }, + + //Callback 2 + 'alert("go")' +]; + +//Gestion des priorités (priorité par défaut = 0) +var callbacks = [ + //Callback 1 + { + callback: function (arg) { + alert('go'); + //... + }, + priority: 10 + }, + + //Callback 2 + { + callback: 'alert("go")', + priority: 20 + } +]; +``` + + +## Appel des callbacks + +```js +import runCallback from '@ecommit/crud-bundle/js/callback'; + +var callbacks = [ + //... Voir paragraphe précédent +]; + +//1er argument: Callback ou liste de callbacks +//2ème argument: Argument passé à l'appel de chaque callback +runCallback(callbacks, '5'); +``` diff --git a/doc/references/modal.md b/doc/references/modal.md new file mode 100644 index 0000000..3b72a57 --- /dev/null +++ b/doc/references/modal.md @@ -0,0 +1,117 @@ +# Gestionnaire de fenêtre modale + +## Définition modale + +Avant d'utiliser une fenêtre modale, vous devez manuellement ajouter le code source HTML de celle-ci. + +Exemple avec Bootstrap: + +```html + +``` + +## Ouverture fenêtre modale + +### Ouverture manuelle + +```js +import * as modalManager from '@ecommit/crud-bundle/js/modal/modal-manager'; + +modalManager.openModal({ + //Options + element: '#main_modal' +}); +``` + +Options disponibles : + +| Option | Description | Requis | Valeur par défaut | +| ------ | ----------- | --------| ----------------- | +| element | Modal (élement du DOM) | Oui | | +| onOpen | [Callback(s)](js-callbacks.md#définition-des-callbacks) lancé(s) avant l'ouverture de la modale | Non | | +| onClose | [Callback(s)](js-callbacks.md#définition-des-callbacks) lancé(s) avant la fermeture de la modale | Non | | + + +### Ouverture automatique + +Ouverture de la fenêtre modale lors d'un clic sur un élément du DOM. +L'élément du DOM doit avoir comme classe CSS `ec-crud-modal-auto`. + +Toutes les options de la fonction `openModal` peuvent être utilisées en les passant par les attributs `data-`. Pour cela: +* Préfixer chaque option par `data-ec-crud-modal-` +* Les options d'origine (en JavaScript) de `openModal` sont en camelCase. Pour leur écriture par des attributs HTML, remplacer chaque nouveau mot en majuscule par un tiret. + Exemple: L'équivalent de l'option `onOpen` est `data-ec-crud-modal-on-open` en attribut HTML. + +Exemple : + +```html +Go ! +``` + +## Ouverture fenêtre modale avec Ajax + +### Ouverture manuelle + +```js +import * as modalManager from '@ecommit/crud-bundle/js/modal/modal-manager'; + +modalManager.openRemoteModal({ + //Options + url: '/goodRequest', + element: '#main_modal', + elementContent: '#main_modal .modal-content' +}); +``` + +Options disponibles : + +| Option | Description | Requis | Valeur par défaut | +| ------ | ----------- | --------| ----------------- | +| url | Url de l'action Ajax | Oui | | +| element | Modal (élement du DOM) | Oui | | +| elementContent | Element du DOM à mettre à jour | Oui | | +| onOpen | [Callback(s)](js-callbacks.md#définition-des-callbacks) lancé(s) avant l'ouverture de la modale | Non | | +| onClose | [Callback(s)](js-callbacks.md#définition-des-callbacks) lancé(s) avant la fermeture de la modale | Non | | +| method | Méthode HTTP | Non | POST | +| ajaxOptions | Options Ajax (voir [sendRequest](ajax.md#sendrequest) | Non | { } | + + +### Ouverture automatique + +Ouverture de la fenêtre modale lors d'un clic sur un bouton ou un lien. +Le bouton ou le lien doit avoir comme classe CSS `ec-crud-remote-modal-auto`. + +Toutes les options de la fonction `openRemoteModal` peuvent être utilisées en les passant par les attributs `data-`. Pour cela: +* Préfixer chaque option par `data-ec-crud-modal-` +* Les options d'origine (en JavaScript) de `openRemoteModal` sont en camelCase. Pour leur écriture par des attributs HTML, remplacer chaque nouveau mot en majuscule par un tiret. + Exemple: L'équivalent de l'option `onOpen` est `data-ec-crud-modal-on-open` en attribut HTML. + +Exemple avec un bouton : + +```html + +``` + +Exemple avec un lien : + +```html +Go ! +``` + +Avec un lien, l'URL utilisée pour la requête Ajax est: +* La valeur de l'attribut `data-ec-crud-modal-url` (si présent) +* Ou la valeur de `href` + + +## Fermeture fenêtre modale + +```js +import * as modalManager from '@ecommit/crud-bundle/js/modal/modal-manager'; + +modalManager.closeModal('#main_modal'); +``` diff --git a/doc/references/searcher.md b/doc/references/searcher.md new file mode 100644 index 0000000..4da2517 --- /dev/null +++ b/doc/references/searcher.md @@ -0,0 +1,16 @@ +# Options du formulaire de recherche + +## Options disponibles + +| Option | Description | Requis | Valeur par défaut | +| ------ | ----------- | --------| ----------------- | +| autovalidate | Si `true`, active la validation automatique (en fonction des filtres utilisés) | Non | true | +| validation_groups | Groupe de validation | Non | null | +| form_options | Options du formulaire Symfony généré | Non | [ ] | +| alias_search | Alias Doctrine à utiliser pour la recherche | Non | Valeur définie dans la déclaration de la colonne du CRUD associée | +| label | Label | Non | Valeur définie dans la déclaration de la colonne du CRUD associée | + + +## Définition des options + +Dans le contrôleur, passer les options dans le 3ème argument (tableau) de la méthode `createSearchForm`. diff --git a/doc/references/twig.md b/doc/references/twig.md new file mode 100644 index 0000000..5495112 --- /dev/null +++ b/doc/references/twig.md @@ -0,0 +1,19 @@ +# Fonctions Twig + +Ce bundle offre différentes fonctions Twig : + +| Fonction | Description | +| -------- | ----------- | +| paginator_links | Affiche les liens d'un paginator | +| crud_paginator_links | Affiche les liens d'un paginator d'un CRUD | +| crud_th | Affiche un tag TH d'un CRUD | +| crud_td | Affiche un tag TD d'un CRUD | +| crud_display_settings | Affiche le bouton de propriétés d'affichage d'un CRUD | +| crud_search_form_start | Affiche le tag d'ouverture du formulaire de recherche d'un CRUD | +| crud_search_form_submit | Affiche le boutton d'envoi du formulaire de recherche d'un CRUD | +| crud_search_form_reset | Affiche le boutton RESET du formulaire de recherche d'un CRUD | +| form_start_ajax | Affiche le tag d'ouverture d'un formulaire Ajax | +| ajax_attributes | Affiche les attributs Ajax | + + +La documentation de chacune de ces fonctions est disponible directement dans [le code source](../../src/Twig/CrudExtension.php) (dans l'entête de chaque fonction). diff --git a/package.json b/package.json index 05c4e3c..eef0373 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "jquery": "^3" }, "devDependencies": { + "@ecommit/crud-bundle": "link:src/Resources/assets", "@symfony/webpack-encore": "^0.29.1", "core-js": "^3.0.0", "eslint": "^6.8.0", diff --git a/src/Resources/public/js/ajax.js b/src/Resources/assets/js/ajax.js similarity index 100% rename from src/Resources/public/js/ajax.js rename to src/Resources/assets/js/ajax.js diff --git a/src/Resources/public/js/callback.js b/src/Resources/assets/js/callback.js similarity index 100% rename from src/Resources/public/js/callback.js rename to src/Resources/assets/js/callback.js diff --git a/src/Resources/public/js/crud.js b/src/Resources/assets/js/crud.js similarity index 100% rename from src/Resources/public/js/crud.js rename to src/Resources/assets/js/crud.js diff --git a/src/Resources/public/js/modal/engine/bootstrap3.js b/src/Resources/assets/js/modal/engine/bootstrap3.js similarity index 100% rename from src/Resources/public/js/modal/engine/bootstrap3.js rename to src/Resources/assets/js/modal/engine/bootstrap3.js diff --git a/src/Resources/public/js/modal/modal-manager.js b/src/Resources/assets/js/modal/modal-manager.js similarity index 100% rename from src/Resources/public/js/modal/modal-manager.js rename to src/Resources/assets/js/modal/modal-manager.js diff --git a/src/Resources/public/js/options-resolver.js b/src/Resources/assets/js/options-resolver.js similarity index 100% rename from src/Resources/public/js/options-resolver.js rename to src/Resources/assets/js/options-resolver.js diff --git a/src/Resources/public/js/scrollToFirstMessage.js b/src/Resources/assets/js/scrollToFirstMessage.js similarity index 100% rename from src/Resources/public/js/scrollToFirstMessage.js rename to src/Resources/assets/js/scrollToFirstMessage.js diff --git a/src/Resources/assets/package.json b/src/Resources/assets/package.json new file mode 100644 index 0000000..f50c0d2 --- /dev/null +++ b/src/Resources/assets/package.json @@ -0,0 +1,6 @@ +{ + "name": "@ecommit/crud-bundle", + "description": "ECOMMIT CRUD BUNDLE", + "license": "MIT", + "version": "3.0.0" +} diff --git a/tests/Functional/App/assets/js/app.js b/tests/Functional/App/assets/js/app.js index e08caa4..5d6a910 100644 --- a/tests/Functional/App/assets/js/app.js +++ b/tests/Functional/App/assets/js/app.js @@ -1,5 +1,5 @@ -import '../../../../../src/Resources/public/js/crud'; -import * as modalManager from '../../../../../src/Resources/public/js/modal/modal-manager'; -var modalEngine = require('../../../../../src/Resources/public/js/modal/engine/bootstrap3'); +import '@ecommit/crud-bundle/js/crud'; +import * as modalManager from '@ecommit/crud-bundle/js/modal/modal-manager'; +var modalEngine = require('@ecommit/crud-bundle/js/modal/engine/bootstrap3'); modalManager.defineEngine(modalEngine); diff --git a/tests/Functional/App/templates/user/list.html.twig b/tests/Functional/App/templates/user/list.html.twig index d96426d..e4ad185 100644 --- a/tests/Functional/App/templates/user/list.html.twig +++ b/tests/Functional/App/templates/user/list.html.twig @@ -1,27 +1,27 @@ -
- - - - {{ crud_th('username', crud) }} - {{ crud_th('firstName', crud) }} - {{ crud_th('lastName', crud) }} - - - - {% for user in crud.paginator %} - - {{ crud_td('username', crud, user.username|lower) }} - {{ crud_td('firstName', crud, user.firstName|capitalize) }} - {{ crud_td('lastName', crud, user.lastName|capitalize) }} +{% if crud.paginator %} +
+
+ + + {{ crud_th('username', crud) }} + {{ crud_th('firstName', crud) }} + {{ crud_th('lastName', crud) }} - {% endfor %} - -
-
+ + + {% for user in crud.paginator %} + + {{ crud_td('username', crud, user.username|lower) }} + {{ crud_td('firstName', crud, user.firstName|capitalize) }} + {{ crud_td('lastName', crud, user.lastName|capitalize) }} + + {% endfor %} + + + -{% if test_before_after_build_query is defined %}
TEST BEFORE AFTER BUILD QUERY {{ test_before_after_build_query }}
{% endif %} + {% if test_before_after_build_query is defined %}
TEST BEFORE AFTER BUILD QUERY {{ test_before_after_build_query }}
{% endif %} -{% if crud.paginator %}
{% trans with {'%first%': crud.paginator.firstIndice, '%last%' : crud.paginator.lastIndice} %}Results %first%-%last%{% endtrans %} - {% trans with {'%page%': crud.paginator.page, '%lastPage%' : crud.paginator.lastPage} %}Page %page%/%lastPage%{% endtrans %} diff --git a/tests/Resources/public/js/ajax.spec.js b/tests/Resources/public/js/ajax.spec.js index 1ccff75..54af1d3 100644 --- a/tests/Resources/public/js/ajax.spec.js +++ b/tests/Resources/public/js/ajax.spec.js @@ -7,7 +7,7 @@ * file that was distributed with this source code. */ -import * as ajax from '../../../../src/Resources/public/js/ajax'; +import * as ajax from '@ecommit/crud-bundle/js/ajax'; import $ from 'jquery'; describe('Test Ajax.sendRequest', function () { diff --git a/tests/Resources/public/js/callback.spec.js b/tests/Resources/public/js/callback.spec.js index 78e3e03..df17aa2 100644 --- a/tests/Resources/public/js/callback.spec.js +++ b/tests/Resources/public/js/callback.spec.js @@ -7,7 +7,7 @@ * file that was distributed with this source code. */ -import runCallback from '../../../../src/Resources/public/js/callback'; +import runCallback from '@ecommit/crud-bundle/js/callback'; describe('Test callback', function () { it('Test callback with function', function () { diff --git a/tests/Resources/public/js/modal/engine/test.js b/tests/Resources/public/js/modal/engine/test.js index c2660f3..f9071af 100644 --- a/tests/Resources/public/js/modal/engine/test.js +++ b/tests/Resources/public/js/modal/engine/test.js @@ -7,7 +7,7 @@ * file that was distributed with this source code. */ -import runCallback from '../../../../../../src/Resources/public/js/callback'; +import runCallback from '@ecommit/crud-bundle/js/callback'; import $ from 'jquery'; export function openModal (options) { diff --git a/tests/Resources/public/js/modal/modal-manager.spec.js b/tests/Resources/public/js/modal/modal-manager.spec.js index 28af865..3b3f582 100644 --- a/tests/Resources/public/js/modal/modal-manager.spec.js +++ b/tests/Resources/public/js/modal/modal-manager.spec.js @@ -7,10 +7,10 @@ * file that was distributed with this source code. */ -import * as modalManager from '../../../../../src/Resources/public/js/modal/modal-manager'; +import * as modalManager from '@ecommit/crud-bundle/js/modal/modal-manager'; import $ from 'jquery'; const testEngine = require('./engine/test'); -const bootstrap3Engine = require('../../../../../src/Resources/public/js/modal/engine/bootstrap3'); +const bootstrap3Engine = require('@ecommit/crud-bundle/js/modal/engine/bootstrap3'); it('Get engine when not defined', function () { modalManager.defineEngine(null); diff --git a/tests/Resources/public/js/options-resolver.spec.js b/tests/Resources/public/js/options-resolver.spec.js index 5a38f2e..823f3ac 100644 --- a/tests/Resources/public/js/options-resolver.spec.js +++ b/tests/Resources/public/js/options-resolver.spec.js @@ -7,7 +7,7 @@ * file that was distributed with this source code. */ -import * as optionsRevolser from '../../../../src/Resources/public/js/options-resolver'; +import * as optionsRevolser from '@ecommit/crud-bundle/js/options-resolver'; import $ from 'jquery'; describe('Test options-resolver.resolve', function () { From d0555f50b1302fecd0b86bb87b142dca5e36a30b Mon Sep 17 00:00:00 2001 From: hlecorche Date: Thu, 12 Aug 2021 19:57:03 +0200 Subject: [PATCH 067/264] Fix Crud::changeFilterValues when sessionValues is null Can be null after creating the form --- src/Crud/Crud.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Crud/Crud.php b/src/Crud/Crud.php index 951a780..4d904db 100644 --- a/src/Crud/Crud.php +++ b/src/Crud/Crud.php @@ -588,7 +588,7 @@ protected function changeFilterValues($value): void if (!$this->searchForm) { return; } - if (\get_class($value) === \get_class($this->searchForm->getDefaultData())) { + if (null !== $value && \get_class($value) === \get_class($this->searchForm->getDefaultData())) { $this->sessionValues->searchFormData = $value; } else { $this->sessionValues->searchFormData = clone $this->searchForm->getDefaultData(); From 94c6d2322d173e73022e62bf0140063e86b20290 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Thu, 12 Aug 2021 19:59:05 +0200 Subject: [PATCH 068/264] Update php-cs-fixer config file --- .php_cs.dist => .php-cs-fixer.dist.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename .php_cs.dist => .php-cs-fixer.dist.php (94%) diff --git a/.php_cs.dist b/.php-cs-fixer.dist.php similarity index 94% rename from .php_cs.dist rename to .php-cs-fixer.dist.php index 0fb6c04..1069e36 100644 --- a/.php_cs.dist +++ b/.php-cs-fixer.dist.php @@ -13,9 +13,9 @@ ->in(__DIR__) ->exclude('tests/Functional/App/var') ; +$config = new PhpCsFixer\Config(); -return PhpCsFixer\Config::create() - ->setRiskyAllowed(true) +return $config->setRiskyAllowed(true) ->setRules([ '@Symfony' => true, '@Symfony:risky' => true, From ae59659372f6aee3910c363d14964a690ad3211a Mon Sep 17 00:00:00 2001 From: hlecorche Date: Thu, 12 Aug 2021 20:11:17 +0200 Subject: [PATCH 069/264] Renaming RestQueryBuilder classes Ecommit\CrudBundle\Crud\Rest\RestQueryBuilder -> Ecommit\CrudBundle\Crud\Http\QueryBuilder Ecommit\CrudBundle\Crud\Rest\RestQueryBuilderParameter -> Ecommit\CrudBundle\Crud\Http\QueryBuilderParameter --- .../{Rest/RestQueryBuilder.php => Http/QueryBuilder.php} | 6 +++--- .../QueryBuilderParameter.php} | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) rename src/Crud/{Rest/RestQueryBuilder.php => Http/QueryBuilder.php} (96%) rename src/Crud/{Rest/RestQueryBuilderParameter.php => Http/QueryBuilderParameter.php} (82%) diff --git a/src/Crud/Rest/RestQueryBuilder.php b/src/Crud/Http/QueryBuilder.php similarity index 96% rename from src/Crud/Rest/RestQueryBuilder.php rename to src/Crud/Http/QueryBuilder.php index cad820c..d550866 100644 --- a/src/Crud/Rest/RestQueryBuilder.php +++ b/src/Crud/Http/QueryBuilder.php @@ -11,14 +11,14 @@ * file that was distributed with this source code. */ -namespace Ecommit\CrudBundle\Crud\Rest; +namespace Ecommit\CrudBundle\Crud\Http; use Ecommit\CrudBundle\Crud\QueryBuilderInterface; use Ecommit\CrudBundle\Crud\QueryBuilderParameterInterface; use Symfony\Component\HttpClient\HttpClient; use Symfony\Contracts\HttpClient\ResponseInterface; -class RestQueryBuilder implements QueryBuilderInterface +class QueryBuilder implements QueryBuilderInterface { /** * @var string @@ -85,7 +85,7 @@ public function setPaginationBuilder(\Closure $paginationBuilder): self public function addParameter(QueryBuilderParameterInterface $parameter): self { - if (!($parameter instanceof RestQueryBuilderParameter)) { + if (!($parameter instanceof QueryBuilderParameter)) { throw new \Exception('Bad class'); } diff --git a/src/Crud/Rest/RestQueryBuilderParameter.php b/src/Crud/Http/QueryBuilderParameter.php similarity index 82% rename from src/Crud/Rest/RestQueryBuilderParameter.php rename to src/Crud/Http/QueryBuilderParameter.php index 444977e..e53c047 100644 --- a/src/Crud/Rest/RestQueryBuilderParameter.php +++ b/src/Crud/Http/QueryBuilderParameter.php @@ -11,11 +11,11 @@ * file that was distributed with this source code. */ -namespace Ecommit\CrudBundle\Crud\Rest; +namespace Ecommit\CrudBundle\Crud\Http; use Ecommit\CrudBundle\Crud\QueryBuilderParameterInterface; -class RestQueryBuilderParameter implements QueryBuilderParameterInterface +class QueryBuilderParameter implements QueryBuilderParameterInterface { public $name; From e7d9275f05cfa1711d1bee82f2cf1aec5902cf0a Mon Sep 17 00:00:00 2001 From: hlecorche Date: Thu, 12 Aug 2021 20:13:50 +0200 Subject: [PATCH 070/264] [CrudHttp] Upercase method --- src/Crud/Http/QueryBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Crud/Http/QueryBuilder.php b/src/Crud/Http/QueryBuilder.php index d550866..9d7949b 100644 --- a/src/Crud/Http/QueryBuilder.php +++ b/src/Crud/Http/QueryBuilder.php @@ -152,6 +152,6 @@ public function getResponse(int $page, int $resultsPerPage, array $options = []) $options['body'] = $this->bodyParameter; } - return $client->request($this->method, $this->url, $options); + return $client->request(mb_strtoupper($this->method), $this->url, $options); } } From 6c50b79ed8931878f21aad53bf1c3d770b7e2c39 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Thu, 12 Aug 2021 20:18:10 +0200 Subject: [PATCH 071/264] [Http\QueryBuilder] Add client property --- src/Crud/Http/QueryBuilder.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Crud/Http/QueryBuilder.php b/src/Crud/Http/QueryBuilder.php index 9d7949b..c9d6b92 100644 --- a/src/Crud/Http/QueryBuilder.php +++ b/src/Crud/Http/QueryBuilder.php @@ -16,6 +16,7 @@ use Ecommit\CrudBundle\Crud\QueryBuilderInterface; use Ecommit\CrudBundle\Crud\QueryBuilderParameterInterface; use Symfony\Component\HttpClient\HttpClient; +use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Contracts\HttpClient\ResponseInterface; class QueryBuilder implements QueryBuilderInterface @@ -60,13 +61,17 @@ class QueryBuilder implements QueryBuilderInterface */ protected $orders = []; - public function __construct(string $url, string $method, array $defaultParameters = []) + public function __construct(string $url, string $method, array $defaultParameters = [], ?HttpClientInterface $client = null) { $this->url = $url; $this->method = $method; foreach ($defaultParameters as $defaultParameter) { $this->addParameter($defaultParameter); } + $this->client = $client; + if (null === $this->client) { + $this->client = HttpClient::create(); + } } public function setOrderBuilder(\Closure $orderBuilder): self @@ -123,8 +128,6 @@ public function orderBy($sort, $sense): self public function getResponse(int $page, int $resultsPerPage, array $options = []): ResponseInterface { - $client = HttpClient::create(); - //Add paginator parameters if ($this->paginationBuilder && $this->paginationBuilder instanceof \Closure) { $parameters = $this->paginationBuilder->__invoke($page, $resultsPerPage); @@ -152,6 +155,6 @@ public function getResponse(int $page, int $resultsPerPage, array $options = []) $options['body'] = $this->bodyParameter; } - return $client->request(mb_strtoupper($this->method), $this->url, $options); + return $this->client->request(mb_strtoupper($this->method), $this->url, $options); } } From e852e2d4e83305db493deef7a4ec7e50feb87db3 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Thu, 12 Aug 2021 20:20:17 +0200 Subject: [PATCH 072/264] [Http\QueryBuilder] Add missing client property --- src/Crud/Http/QueryBuilder.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Crud/Http/QueryBuilder.php b/src/Crud/Http/QueryBuilder.php index c9d6b92..dda22dc 100644 --- a/src/Crud/Http/QueryBuilder.php +++ b/src/Crud/Http/QueryBuilder.php @@ -61,6 +61,11 @@ class QueryBuilder implements QueryBuilderInterface */ protected $orders = []; + /** + * @var HttpClientInterface + */ + protected $client; + public function __construct(string $url, string $method, array $defaultParameters = [], ?HttpClientInterface $client = null) { $this->url = $url; From d62f3968da28ed29547eadc9f4f324d995568f31 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Fri, 13 Aug 2021 12:16:56 +0200 Subject: [PATCH 073/264] [Http\QueryBuilder] Old sytem (CrudHttp) is deleted and replaced by a new system (BC break) --- doc/cookbook/http.md | 171 +++++++++++ doc/cookbook/rest.md | 3 - doc/index.md | 2 +- .../Http/AbstractQueryBuilderParameter.php | 21 ++ src/Crud/Http/QueryBuilder.php | 99 ++++--- src/Crud/Http/QueryBuilderBody.php | 22 ++ src/Crud/Http/QueryBuilderBodyParameter.php | 19 ++ src/Crud/Http/QueryBuilderHeaderParameter.php | 19 ++ ...hp => QueryBuilderNamedParameterTrait.php} | 11 +- src/Crud/Http/QueryBuilderQueryParameter.php | 19 ++ tests/Crud/Http/QueryBuilderTest.php | 265 ++++++++++++++++++ 11 files changed, 598 insertions(+), 53 deletions(-) create mode 100644 doc/cookbook/http.md delete mode 100644 doc/cookbook/rest.md create mode 100644 src/Crud/Http/AbstractQueryBuilderParameter.php create mode 100644 src/Crud/Http/QueryBuilderBody.php create mode 100644 src/Crud/Http/QueryBuilderBodyParameter.php create mode 100644 src/Crud/Http/QueryBuilderHeaderParameter.php rename src/Crud/Http/{QueryBuilderParameter.php => QueryBuilderNamedParameterTrait.php} (60%) create mode 100644 src/Crud/Http/QueryBuilderQueryParameter.php create mode 100644 tests/Crud/Http/QueryBuilderTest.php diff --git a/doc/cookbook/http.md b/doc/cookbook/http.md new file mode 100644 index 0000000..4e1b059 --- /dev/null +++ b/doc/cookbook/http.md @@ -0,0 +1,171 @@ +# Utilisation d'un CRUD avec une API HTTP + +Exemple avec [l'API Opendatasoft Correspondance Code INSEE - Code Postal](https://public.opendatasoft.com/explore/dataset/correspondance-code-insee-code-postal/api/) : + +```php +get(HttpClientInterface::class)); + + /* + * Définition du paramètre "dataset" ajouté en GET à la requête. + * + * A différents moments (dans cette méthode et dans la classe du formulaire de recherche), on peut modifier l'objet $queryBuilder : + * + * Pour ajouter des éléments à la requête HTTP, on peut appeler la méthode "addParameter" en lui passant en paramètre + * une instance de l'une des classes suivantes : + * Ecommit\CrudBundle\Crud\Http\QueryBuilderQueryParameter : Paramètre passé en GET (query) + * Ecommit\CrudBundle\Crud\Http\QueryBuilderBodyParameter : Paramètre passé dans le body + * Ecommit\CrudBundle\Crud\Http\QueryBuilderBody : Paramètre unique (sans nom) passé dans le body + * Ecommit\CrudBundle\Crud\Http\QueryBuilderHeaderParameter : Paramètre passé dans l'entête + * + * La méthode "setBodyIsJson" peut être appelée pour définir une requête au format JSON. + */ + $queryBuilder->addParameter(new QueryBuilderQueryParameter('dataset', 'correspondance-code-insee-code-postal')); + + //FACULTATIF - Ajout dans la requête HTTP la gestion de la pagination + $queryBuilder->setPaginationBuilder(function (QueryBuilder $queryBuilder, $page, $resultsPerPage): void { + $start = ($page - 1) * $resultsPerPage; + + $queryBuilder->addParameter(new QueryBuilderQueryParameter('rows', $resultsPerPage)); + $queryBuilder->addParameter(new QueryBuilderQueryParameter('start', $start)); + }); + + //FACULTATIF - Ajout dans la requête HTTP la gestion du tri + $queryBuilder->setOrderBuilder(function (QueryBuilder $queryBuilder, $orders): void { + foreach ($orders as $sort => $sense) { + $senseParameter = ($sense === Crud::ASC)? '-' : ''; + $queryBuilder->addParameter(new QueryBuilderQueryParameter('sort', $senseParameter.$sort)); + } + }); + + $crud = $this->createCrud('my_http_crud'); + $crud->addColumn('name', 'fields.nom_comm', 'Nom', ['sortable' => false]) + ->addColumn('postal_code', 'fields.postal_code', 'Postal code', ['sortable' => false]) + ->addColumn('population', 'fields.population', 'Population', ['alias_sort' => 'population']) + ->addColumn('timestamp', 'record_timestamp', 'Timestamp', ['sortable' => false]) + ->setQueryBuilder($queryBuilder) + ->setAvailableResultsPerPage([2, 5, 10], 5) + ->setDefaultSort('population', Crud::DESC) + ->setRoute('my_http_crud_ajax') + ->setBuildPaginator(function (QueryBuilder $queryBuilder, $page, $resultsPerPage) { + //Appel HTTP + décode du JSON de la réponse + $response = json_decode($queryBuilder->getResponse($page, $resultsPerPage)->getContent()); + + //Création du paginator + $paginator = new ArrayPaginator($resultsPerPage); + $paginator->setDataWithoutSlice($response->records, $response->nhits); + $paginator->setPage($page); + $paginator->init(); + + return $paginator; + }) + ->createSearchForm(new CitySearcher()) + ->setPersistentSettings(true) + ->init(); + + return $crud; + } + + protected function getTemplateName(string $action): string + { + return sprintf('my_http_crud/%s.html.twig', $action); + } + + /** + * @Route("/my-http-crud", name="my_http_crud") + */ + public function crudAction() + { + return $this->getCrudResponse(); + } + + /** + * @Route("/my-http-crud/ajax", name="my_http_crud_ajax") + */ + public function ajaxCrudAction() + { + return $this->getAjaxCrudResponse(); + } + + public static function getSubscribedServices() + { + return array_merge(parent::getSubscribedServices(), [ + HttpClientInterface::class, + ]); + } +} +``` + + +```php +addField('text', TextType::class, [ + 'label' => 'Query', + 'required' => false, + ]); + } + + public function updateQueryBuilder($queryBuilder, array $options): void + { + //Traitement de la recherche + + if (null !== $this->text && is_scalar($this->text)) { + $queryBuilder->addParameter(new QueryBuilderQueryParameter('q', $this->text)); + } + } +} +``` diff --git a/doc/cookbook/rest.md b/doc/cookbook/rest.md deleted file mode 100644 index 8787031..0000000 --- a/doc/cookbook/rest.md +++ /dev/null @@ -1,3 +0,0 @@ -# Utilisation d'un CRUD avec REST - -Documentation disponible prochainement. diff --git a/doc/index.md b/doc/index.md index a179a65..585415d 100644 --- a/doc/index.md +++ b/doc/index.md @@ -22,7 +22,7 @@ * [Affichage des résultats uniquement si recherche envoyée](cookbook/display_results.md) * [Personnaliser les IDs des Divs](cookbook/div_ids.md) * [Création manuelle du paginator](cookbook/paginator.md) -* [Utilisation d'un CRUD avec REST](cookbook/rest.md) +* [Utilisation d'un CRUD avec une API HTTP](cookbook/http.md) * [Tri par défaut personnalisé](cookbook/sort.md) * [Configuration par défaut des templates des CRUD](cookbook/template_configuration.md) * [Colonnes virtuelles](cookbook/virtual_columns.md) diff --git a/src/Crud/Http/AbstractQueryBuilderParameter.php b/src/Crud/Http/AbstractQueryBuilderParameter.php new file mode 100644 index 0000000..79ab450 --- /dev/null +++ b/src/Crud/Http/AbstractQueryBuilderParameter.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Ecommit\CrudBundle\Crud\Http; + +use Ecommit\CrudBundle\Crud\QueryBuilderParameterInterface; + +abstract class AbstractQueryBuilderParameter implements QueryBuilderParameterInterface +{ + public $value; +} diff --git a/src/Crud/Http/QueryBuilder.php b/src/Crud/Http/QueryBuilder.php index dda22dc..e65a66a 100644 --- a/src/Crud/Http/QueryBuilder.php +++ b/src/Crud/Http/QueryBuilder.php @@ -29,7 +29,7 @@ class QueryBuilder implements QueryBuilderInterface /** * @var string */ - protected $method; + protected $httpMethod; /** * @var array @@ -39,12 +39,19 @@ class QueryBuilder implements QueryBuilderInterface /** * @var array */ - protected $formParameters = []; + protected $bodyParameters = []; /** - * @var string + * @var QueryBuilderBody + */ + protected $body; + + protected $bodyIsJson = false; + + /** + * @var array */ - protected $bodyParameter; + protected $headerParameters = []; /** * @var \Closure @@ -66,19 +73,19 @@ class QueryBuilder implements QueryBuilderInterface */ protected $client; - public function __construct(string $url, string $method, array $defaultParameters = [], ?HttpClientInterface $client = null) + public function __construct(string $url, string $httpMethod, ?HttpClientInterface $client = null) { $this->url = $url; - $this->method = $method; - foreach ($defaultParameters as $defaultParameter) { - $this->addParameter($defaultParameter); - } + $this->httpMethod = $httpMethod; $this->client = $client; if (null === $this->client) { $this->client = HttpClient::create(); } } + /** + * @param \Closure $orderBuilder Closure aguments: $queryBuilder, $orders + */ public function setOrderBuilder(\Closure $orderBuilder): self { $this->orderBuilder = $orderBuilder; @@ -86,6 +93,9 @@ public function setOrderBuilder(\Closure $orderBuilder): self return $this; } + /** + * @param \Closure $orderBuilder Closure aguments: $queryBuilder, $page, $resultsPerPage + */ public function setPaginationBuilder(\Closure $paginationBuilder): self { $this->paginationBuilder = $paginationBuilder; @@ -95,23 +105,30 @@ public function setPaginationBuilder(\Closure $paginationBuilder): self public function addParameter(QueryBuilderParameterInterface $parameter): self { - if (!($parameter instanceof QueryBuilderParameter)) { + if ($parameter instanceof QueryBuilderQueryParameter) { + $this->queryParameters[] = $parameter; + } elseif ($parameter instanceof QueryBuilderBodyParameter) { + if (null !== $this->body) { + throw new \Exception('Use QueryBuilderBodyParameter and QueryBuilderBody classes is not supported.'); + } + $this->bodyParameters[] = $parameter; + } elseif ($parameter instanceof QueryBuilderBody) { + if (\count($this->bodyParameters) > 0) { + throw new \Exception('Use QueryBuilderBodyParameter and QueryBuilderBody classes is not supported.'); + } + $this->body = $parameter; + } elseif ($parameter instanceof QueryBuilderHeaderParameter) { + $this->headerParameters[] = $parameter; + } else { throw new \Exception('Bad class'); } - switch ($parameter->method) { - case 'get': - $this->queryParameters[$parameter->name] = $parameter->value; - break; - case 'post': - $this->formParameters[$parameter->name] = $parameter->value; - break; - case 'body': - $this->bodyParameter = $parameter->value; - break; - default: - throw new \Exception('Bad parameter method'); - } + return $this; + } + + public function setBodyIsJson(bool $bodyIsJson): self + { + $this->bodyIsJson = $bodyIsJson; return $this; } @@ -134,32 +151,34 @@ public function orderBy($sort, $sense): self public function getResponse(int $page, int $resultsPerPage, array $options = []): ResponseInterface { //Add paginator parameters - if ($this->paginationBuilder && $this->paginationBuilder instanceof \Closure) { - $parameters = $this->paginationBuilder->__invoke($page, $resultsPerPage); - foreach ($parameters as $parameter) { - $this->addParameter($parameter); - } + if ($this->paginationBuilder) { + $this->paginationBuilder->__invoke($this, $page, $resultsPerPage); } //Add sort parameters - if (\count($this->orders) > 0 && $this->orderBuilder && $this->orderBuilder instanceof \Closure) { - $parameters = $this->orderBuilder->__invoke($this->orders); - foreach ($parameters as $parameter) { - $this->addParameter($parameter); - } + if (\count($this->orders) > 0 && $this->orderBuilder) { + $this->orderBuilder->__invoke($this, $this->orders); } //Add parameters to client options - foreach ($this->queryParameters as $parameterName => $parameterValue) { - $options['query'][$parameterName] = $parameterValue; + /** @var QueryBuilderQueryParameter $parameter */ + foreach ($this->queryParameters as $parameter) { + $options['query'][$parameter->name] = $parameter->value; + } + /** @var QueryBuilderBodyParameter $parameter */ + foreach ($this->bodyParameters as $parameter) { + $bodyType = ($this->bodyIsJson) ? 'json' : 'body'; + $options[$bodyType][$parameter->name] = $parameter->value; } - foreach ($this->formParameters as $parameterName => $parameterValue) { - $options['body'][$parameterName] = $parameterValue; + if ($this->body) { + $bodyType = ($this->bodyIsJson) ? 'json' : 'body'; + $options[$bodyType] = $this->body->value; } - if ($this->bodyParameter) { - $options['body'] = $this->bodyParameter; + /** @var QueryBuilderHeaderParameter $parameter */ + foreach ($this->headerParameters as $parameter) { + $options['headers'][$parameter->name] = $parameter->value; } - return $this->client->request(mb_strtoupper($this->method), $this->url, $options); + return $this->client->request(mb_strtoupper($this->httpMethod), $this->url, $options); } } diff --git a/src/Crud/Http/QueryBuilderBody.php b/src/Crud/Http/QueryBuilderBody.php new file mode 100644 index 0000000..baf1b63 --- /dev/null +++ b/src/Crud/Http/QueryBuilderBody.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Ecommit\CrudBundle\Crud\Http; + +class QueryBuilderBody extends AbstractQueryBuilderParameter +{ + public function __construct($value) + { + $this->value = $value; + } +} diff --git a/src/Crud/Http/QueryBuilderBodyParameter.php b/src/Crud/Http/QueryBuilderBodyParameter.php new file mode 100644 index 0000000..a7fc21f --- /dev/null +++ b/src/Crud/Http/QueryBuilderBodyParameter.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Ecommit\CrudBundle\Crud\Http; + +class QueryBuilderBodyParameter extends AbstractQueryBuilderParameter +{ + use QueryBuilderNamedParameterTrait; +} diff --git a/src/Crud/Http/QueryBuilderHeaderParameter.php b/src/Crud/Http/QueryBuilderHeaderParameter.php new file mode 100644 index 0000000..992c07f --- /dev/null +++ b/src/Crud/Http/QueryBuilderHeaderParameter.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Ecommit\CrudBundle\Crud\Http; + +class QueryBuilderHeaderParameter extends AbstractQueryBuilderParameter +{ + use QueryBuilderNamedParameterTrait; +} diff --git a/src/Crud/Http/QueryBuilderParameter.php b/src/Crud/Http/QueryBuilderNamedParameterTrait.php similarity index 60% rename from src/Crud/Http/QueryBuilderParameter.php rename to src/Crud/Http/QueryBuilderNamedParameterTrait.php index e53c047..6d589e0 100644 --- a/src/Crud/Http/QueryBuilderParameter.php +++ b/src/Crud/Http/QueryBuilderNamedParameterTrait.php @@ -13,20 +13,13 @@ namespace Ecommit\CrudBundle\Crud\Http; -use Ecommit\CrudBundle\Crud\QueryBuilderParameterInterface; - -class QueryBuilderParameter implements QueryBuilderParameterInterface +trait QueryBuilderNamedParameterTrait { public $name; - public $value; - - public $method; - - public function __construct($name, $value, $method) + public function __construct(string $name, $value) { $this->name = $name; $this->value = $value; - $this->method = $method; } } diff --git a/src/Crud/Http/QueryBuilderQueryParameter.php b/src/Crud/Http/QueryBuilderQueryParameter.php new file mode 100644 index 0000000..883e1a1 --- /dev/null +++ b/src/Crud/Http/QueryBuilderQueryParameter.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Ecommit\CrudBundle\Crud\Http; + +class QueryBuilderQueryParameter extends AbstractQueryBuilderParameter +{ + use QueryBuilderNamedParameterTrait; +} diff --git a/tests/Crud/Http/QueryBuilderTest.php b/tests/Crud/Http/QueryBuilderTest.php new file mode 100644 index 0000000..35eca7a --- /dev/null +++ b/tests/Crud/Http/QueryBuilderTest.php @@ -0,0 +1,265 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Ecommit\CrudBundle\Tests\Crud\Http; + +use Ecommit\CrudBundle\Crud\Crud; +use Ecommit\CrudBundle\Crud\Http\QueryBuilder; +use Ecommit\CrudBundle\Crud\Http\QueryBuilderBody; +use Ecommit\CrudBundle\Crud\Http\QueryBuilderBodyParameter; +use Ecommit\CrudBundle\Crud\Http\QueryBuilderHeaderParameter; +use Ecommit\CrudBundle\Crud\Http\QueryBuilderQueryParameter; +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\HttpClient\Response\MockResponse; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Contracts\HttpClient\ResponseInterface; + +class QueryBuilderTest extends TestCase +{ + public function testEmptyQueryBuilder(): void + { + $queryBuilder = $this->createQueryBuider(); + $response = $queryBuilder->getResponse(1, 1); + + $this->checkRequest($response, []); + } + + public function testMethod(): void + { + $queryBuilder = $this->createQueryBuider('POST'); + $response = $queryBuilder->getResponse(1, 1); + + $this->checkRequest($response, [ + 'method' => 'POST', + ]); + } + + public function testMethodQLowerCase(): void + { + $queryBuilder = $this->createQueryBuider('post'); + $response = $queryBuilder->getResponse(1, 1); + + $this->checkRequest($response, [ + 'method' => 'POST', + ]); + } + + public function testAddQueryParameter(): void + { + $queryBuilder = $this->createQueryBuider(); + $queryBuilder->addParameter(new QueryBuilderQueryParameter('param1', 'val1')); + $response = $queryBuilder->getResponse(1, 1); + + $this->checkRequest($response, [ + 'url' => 'http://test/url?param1=val1', + ]); + } + + public function testAddBodyParameter(): void + { + $queryBuilder = $this->createQueryBuider(); + $queryBuilder->addParameter(new QueryBuilderBodyParameter('param1', 'val1')); + $response = $queryBuilder->getResponse(1, 1); + + $this->checkRequest($response, [ + 'body' => 'param1=val1', + ]); + } + + public function testAddBodyParameterNotAllowed(): void + { + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Use QueryBuilderBodyParameter and QueryBuilderBody classes is not supported.'); + + $queryBuilder = $this->createQueryBuider(); + $queryBuilder->addParameter(new QueryBuilderBody('val1')); + $queryBuilder->addParameter(new QueryBuilderBodyParameter('param1', 'val1')); + } + + public function testAddBody(): void + { + $queryBuilder = $this->createQueryBuider(); + $queryBuilder->addParameter(new QueryBuilderBody('val1')); + $response = $queryBuilder->getResponse(1, 1); + + $this->checkRequest($response, [ + 'body' => 'val1', + ]); + } + + public function testAddBodyNotAllowed(): void + { + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Use QueryBuilderBodyParameter and QueryBuilderBody classes is not supported.'); + + $queryBuilder = $this->createQueryBuider(); + $queryBuilder->addParameter(new QueryBuilderBodyParameter('param1', 'val1')); + $queryBuilder->addParameter(new QueryBuilderBody('val1')); + } + + public function testAddHeaderParameter(): void + { + $queryBuilder = $this->createQueryBuider(); + $queryBuilder->addParameter(new QueryBuilderHeaderParameter('param1', 'val1')); + $response = $queryBuilder->getResponse(1, 1); + + $this->checkRequest($response, [ + 'headers' => ['param1: val1'], + ]); + } + + public function testSetBodyIsJson(): void + { + $queryBuilder = $this->createQueryBuider(); + $queryBuilder->addParameter(new QueryBuilderBodyParameter('param1', 'val1')); + $queryBuilder->setBodyIsJson(true); + $response = $queryBuilder->getResponse(1, 1); + + $this->checkRequest($response, [ + 'content_type_json' => true, + 'body' => '{"param1":"val1"}', + ]); + } + + public function testOrderBy(): void + { + $queryBuilder = $this->createQueryBuider(); + $queryBuilder->setOrderBuilder(function (QueryBuilder $queryBuilder, $orders): void { + foreach ($orders as $sort => $sense) { + $queryBuilder->addParameter(new QueryBuilderBodyParameter('sort', $sort)); + $queryBuilder->addParameter(new QueryBuilderBodyParameter('sense', $sense)); + } + }); + $queryBuilder->orderBy('col1', Crud::ASC); + $response = $queryBuilder->getResponse(1, 1); + + $this->checkRequest($response, [ + 'body' => 'sort=col1&sense=ASC', + ]); + } + + public function testAddOrderBy(): void + { + $queryBuilder = $this->createQueryBuider(); + $queryBuilder->setOrderBuilder(function (QueryBuilder $queryBuilder, $orders): void { + $paramSort = []; + $paramSense = []; + foreach ($orders as $sort => $sense) { + $paramSort[] = $sort; + $paramSense[] = $sense; + } + $queryBuilder->addParameter(new QueryBuilderBodyParameter('sort', implode(',', $paramSort))); + $queryBuilder->addParameter(new QueryBuilderBodyParameter('sense', implode(',', $paramSense))); + }); + $queryBuilder->addOrderBy('col1', Crud::ASC); + $queryBuilder->addOrderBy('col2', Crud::DESC); + $response = $queryBuilder->getResponse(1, 1); + + $this->checkRequest($response, [ + 'body' => 'sort=col1%2Ccol2&sense=ASC%2CDESC', + ]); + } + + public function testPagination(): void + { + $queryBuilder = $this->createQueryBuider(); + $queryBuilder->setPaginationBuilder(function (QueryBuilder $queryBuilder, $page, $resultsPerPage): void { + $queryBuilder->addParameter(new QueryBuilderBodyParameter('page', $page)); + $queryBuilder->addParameter(new QueryBuilderBodyParameter('per_page', $resultsPerPage)); + }); + $response = $queryBuilder->getResponse(10, 100); + + $this->checkRequest($response, [ + 'body' => 'page=10&per_page=100', + ]); + } + + public function testFull(): void + { + $queryBuilder = $this->createQueryBuider('POST'); + $queryBuilder->addParameter(new QueryBuilderQueryParameter('queryparam1', 'queryparamval1')); + $queryBuilder->addParameter(new QueryBuilderBodyParameter('bodyparam1', 'bodyparamval1')); + $queryBuilder->addParameter(new QueryBuilderHeaderParameter('headerparam1', 'headerparamval1')); + $queryBuilder->setOrderBuilder(function (QueryBuilder $queryBuilder, $orders): void { + foreach ($orders as $sort => $sense) { + $queryBuilder->addParameter(new QueryBuilderQueryParameter('sort', $sort)); + $queryBuilder->addParameter(new QueryBuilderQueryParameter('sense', $sense)); + } + }); + $queryBuilder->setPaginationBuilder(function (QueryBuilder $queryBuilder, $page, $resultsPerPage): void { + $queryBuilder->addParameter(new QueryBuilderQueryParameter('page', $page)); + $queryBuilder->addParameter(new QueryBuilderQueryParameter('per_page', $resultsPerPage)); + }); + + $queryBuilder->orderBy('col1', Crud::ASC); + $response = $queryBuilder->getResponse(10, 100); + + $this->checkRequest($response, [ + 'method' => 'POST', + 'url' => 'http://test/url?queryparam1=queryparamval1&page=10&per_page=100&sort=col1&sense=ASC', + 'body' => 'bodyparam1=bodyparamval1', + 'headers' => ['headerparam1: headerparamval1'], + ]); + } + + protected function createQueryBuider(string $method = 'GET'): QueryBuilder + { + $callback = function ($method, $url, $options): MockResponse { + //Result : Returns request options + $result = [ + 'method' => $method, + 'url' => $url, + 'content_type_json' => false, + 'body' => null, + 'headers' => [], + ]; + if (isset($options['body']) && null !== $options['body']) { + $result['body'] = $options['body']; + } + if (isset($options['headers'])) { + foreach ($options['headers'] as $header) { + if ('Content-Type: application/json' === $header) { + $result['content_type_json'] = true; + } elseif ('Accept: */*' !== $header) { + $result['headers'][] = $header; + } + } + } + + return new MockResponse(json_encode($result)); + }; + $client = new MockHttpClient($callback); + + return new QueryBuilder('http://test/url', $method, $client); + } + + protected function checkRequest(ResponseInterface $response, array $options): void + { + $resolver = new OptionsResolver(); + $resolver->setDefaults([ + 'method' => 'GET', + 'url' => 'http://test/url', + 'content_type_json' => false, + 'body' => null, + 'headers' => [], + ]); + $expectedOptions = $resolver->resolve($options); + $options = json_decode($response->getContent(), true); + + ksort($expectedOptions); + ksort($options); + + $this->assertSame($expectedOptions, $options); + } +} From 1e42042faa2d7d2d807241a1b6ffa9b349c36e78 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Fri, 13 Aug 2021 12:25:19 +0200 Subject: [PATCH 074/264] Add Security section --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 54bac68..3c8a1e7 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,11 @@ [Read the Documentation (French documentation)](doc/index.md) +## Security ## + +If you think that you have found a security issue, do **NOT** use the bug tracker. Instead, please email +contact@e-commit.fr with information about the issue. + ## License ## This bundle is available under the MIT license. See the complete license in the *LICENSE* file. From 4ab5f1484a8c399c98e7ba0e8c265f8be6f2067f Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sat, 14 Aug 2021 15:43:09 +0200 Subject: [PATCH 075/264] Removing unused arguments --- src/Twig/CrudExtension.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Twig/CrudExtension.php b/src/Twig/CrudExtension.php index ea1d718..7e96da4 100644 --- a/src/Twig/CrudExtension.php +++ b/src/Twig/CrudExtension.php @@ -514,7 +514,7 @@ public function searchFormStart(Environment $environment, Crud $crud, array $opt * * theme: Theme used. If null, default theme is used * * block: Twig block used. Default: search_form_submit */ - public function searchFormSubmit(Environment $environment, Crud $crud, $options = [], $ajaxOptions = [], $htmlOptions = []) + public function searchFormSubmit(Environment $environment, Crud $crud, $options = []) { $resolver = new OptionsResolver(); $resolver->setDefaults([ @@ -549,7 +549,7 @@ public function searchFormSubmit(Environment $environment, Crud $crud, $options * * theme: Theme used. If null, default theme is used * * block: Twig block used. Default: search_form_reset */ - public function searchFormReset(Environment $environment, Crud $crud, $options = [], $ajaxOptions = [], $htmlOptions = []) + public function searchFormReset(Environment $environment, Crud $crud, $options = []) { $resolver = new OptionsResolver(); $resolver->setDefaults([ From 4bcce37685643076a81ad52b44cf597c1dfb73b8 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sun, 15 Aug 2021 13:44:35 +0200 Subject: [PATCH 076/264] Add upgrade guide --- UPGRADE.md | 3 + doc/index.md | 4 + doc/install.md | 2 + doc/upgrade/3.0.md | 286 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 295 insertions(+) create mode 100644 UPGRADE.md create mode 100644 doc/upgrade/3.0.md diff --git a/UPGRADE.md b/UPGRADE.md new file mode 100644 index 0000000..b3ebb23 --- /dev/null +++ b/UPGRADE.md @@ -0,0 +1,3 @@ +# Upgrade + +[Read the Documentation (French documentation)](doc/index.md#migrations) diff --git a/doc/index.md b/doc/index.md index 585415d..0645db1 100644 --- a/doc/index.md +++ b/doc/index.md @@ -26,3 +26,7 @@ * [Tri par défaut personnalisé](cookbook/sort.md) * [Configuration par défaut des templates des CRUD](cookbook/template_configuration.md) * [Colonnes virtuelles](cookbook/virtual_columns.md) + +## Migrations + +* [Mise à jour de 2.6 vers 3.0](upgrade/3.0.md) diff --git a/doc/install.md b/doc/install.md index a748ebe..a01d617 100644 --- a/doc/install.md +++ b/doc/install.md @@ -38,12 +38,14 @@ ecommit_crud: #Themes disponibles : #@EcommitCrud/Theme/base.html.twig #@EcommitCrud/Theme/bootstrap3.html.twig (boostrap3 requis) + #Ou faire son propre terme (doit hériter de l'un des thèmes précédents) theme: '@EcommitCrud/Theme/bootstrap3.html.twig' #Theme pour les icones #Themes disponibles : #@EcommitCrud/IconTheme/base.html.twig #@EcommitCrud/IconTheme/fontawesome4.html.twig (fontawesome4 requis) + #Ou faire son propre terme (doit hériter de l'un des thèmes précédents) icon_theme: '@EcommitCrud/IconTheme/fontawesome4.html.twig' ``` diff --git a/doc/upgrade/3.0.md b/doc/upgrade/3.0.md new file mode 100644 index 0000000..2f0ec99 --- /dev/null +++ b/doc/upgrade/3.0.md @@ -0,0 +1,286 @@ +# Mise à jour de 2.6 vers 3.0 + +## Généralités + +* Les prérequis et configuration du Bundle sont modifiés. Suivre [la procédure d'installation](../install.md) pour adapter votre projet. +* Les dépendances suivantes sont supprimées: + * `doctrine/common` + * `ecommit/javascript-bundle` + * `ecommit/util-bundle` + * `twig/extensions` +* La signature des méthodes a été modifiée en ajoutant le type des arguments et de retour. + + + +## Configuration + +* Les nouvelles options `theme` et `icon_theme` sont obligatoires. Voir [la procédure d'installation](../install.md) pour les valeurs disponibles. +* L'option `template_configuration` est supprimée. Voir l'alternative avec [twig_functions_configuration](../cookbook/template_configuration.md). +* L'option `images` est supprimée. +* Les paramètres suivants sont supprimés: + * `ecommit_crud.template_configuration` + * `ecommit_crud.images` + + + +## Contrôleur + +**Namespace: Ecommit\CrudBundle\Controller** + +* La classe `AbstractCrudLegacyController` est supprimée. Utiliser à la place la classe `AbstractCrudController`. +* La classe `AbstractCrudController` n'hérite plus de `Symfony\Component\DependencyInjection\ContainerAwareTrait` mais + de `Symfony\Bundle\FrameworkBundle\Controller\AbstractController`. Adapter l'utilisation des services en conséquence. +* La méthode `getPathView` est renommée en `getTemplateName` dans `CrudControllerTrait`. Sa signature est maintenant + `getTemplateName(string $action): string` +* La méthode `getTemplateName` (anciennement `getPathView`) dans `CrudControllerTrait` est maintenant abstraite. Celle-ci doit être rajouter + dans vos contrôleurs CRUD. +* La méthode `createCrud` de `CrudControllerTrait` est maintenant `final`. +* La méthode `autoListAction` de `CrudControllerTrait` est supprimée. Utiliser à la place la méthode `getCrudResponse` (signature différente). +* La méthode `autoAjaxListAction` de `CrudControllerTrait` est supprimée. Utiliser à la place la méthode `getAjaxCrudResponse` (signature différente). +* La méthode `autoAjaxSearchAction` de `CrudControllerTrait` est supprimée. Il n'existe plus d'action spécifique à la recherche. +* Les méthodes `prepareList`, `processSearch`, `renderCrudView` et `renderCrud` de `CrudControllerTrait` sont supprimées. +* La méthode `configCrud` de `CrudControllerTrait` est supprimée. Utiliser à la place la méthode `getCrud` (signature différente). +* La méthode `addDataAfterBuildQuery` de `CrudControllerTrait` est supprimée. Voir l'alternative avec [le nouveau système](../cookbook/data-template.md). +* La signature de la méthode `beforeBuildQuery` de `CrudControllerTrait` est modifiée: + * Avant: `beforeBuildQuery()` + * Maintenant: `beforeBuildQuery(Crud $crud, array $data): array` +* La signature de la méthode `afterBuildQuery` de `CrudControllerTrait` est modifiée: + * Avant: `afterBuildQuery()` + * Maintenant: `afterBuildQuery(Crud $crud, array $data): array` +* La méthode `getCrudRequiredServices` de `CrudControllerTrait` ne retourne plus les services `twig` et `request_stack`. Si + les contrôleurs n'hérite pas de `AbstractCrudController` ou `Symfony\Bundle\FrameworkBundle\Controller\AbstractController`, + ces services ne seront plus injectés au locator du contrôleur. +* La donnée membre `$cm`de `CrudControllerTrait` est supprimée. + + + +## Crud + +**Namespace: Ecommit\CrudBundle\Crud** + +* La signature du constructeur de `Crud` est modifiée: + * Avant: `__construct($sessionName, RouterInterface $router, FormFactoryInterface $formFactory, Request $request, Registry $registry, $user)` + * Maintenant: `__construct($sessionName, ContainerInterface $container)` + * Utilisation d'un locator +* Les données membres suivantes de `Crud` sont supprimées: + * `$formSearcher`: Elle est remplacée par la donnée membre `$searchForm` qui est une instance de `SearchFormBuilder` + ou `Symfony\Component\Form\FormView` ou nulle. + * `$defaultFormSearcherData` + * `$router`, `formFactory`, `$request`, `$registry`: Un locator est utilisé à la place. + * `$user` +* Les méthodes suivantes de `Crud` sont supprimées: + * `getSearcherForm`. Elle est remplacée par la méthode `getSearchForm` + * `setUseDbal` + * `configureTemplate`, `getTemplateConfiguration` et `validateConfigureTemplateFunctionName`: Voir l'alternative avec + [le nouveau système](../cookbook/template_configuration.md). + * `setSearchRoute`: Il n'existe plus de route spécifique à la recherche. + * `initializeFieldsFilter` +* La méthode `createSearcherForm` de `Crud` est renommée en `createSearchForm`. La signature est modifiée: + * Avant: `createSearcherForm(AbstractFormSearcher $defaultFormSearcherData, $type = null, $options = [])` + * Après: `createSearchForm(SearcherInterface $defaultData, ?string $type = null, array $options = [])` +* La signature du constructeur de `CrudFactory` est modifiée: + * Avant: `__construct(RouterInterface $router, FormFactoryInterface $formFactory, RequestStack $requestStack, ManagerRegistry $registry, TokenStorageInterface $tokenStorage, array $templateConfiguration)` + * Maintenant: `__construct(ContainerInterface $container)` + * Utilisation d'un locator +* Toutes les données membres de `CrudFactory` sont supprimées. Un locator est utilisé à la place. +* La donnée membre `$formSearcherData` de `CrudSession` est supprimée. Elle est remplacée par `$searchFormData` + qui est une instance de `SearcherInterface` ou nulle. + + + +## Crud REST + +**Namespace: Ecommit\CrudBundle\Crud\Rest** + +* Les classes `Ecommit\CrudBundle\Crud\Rest\*` n'existent plus. Voir l'alternative avec [le nouveau système](../cookbook/http.md). + + + +## Formulaire de recherche + +**Namespace: Ecommit\CrudBundle\Form\Searcher** + +* La classe `AbstractFormSearcher` est supprimée et remplacée par `AbstractSearcher`, avec un fonctionnement et une + API totalement différents. Vos formulaires de recherche doivent maintenant hériter de `AbstractSearcher` + ou implémenter `SearcherInterface`. Voir les liens suivants: + * [Création d'un formulaire de recherche](../crud.md#ajout-formulaire-de-recherche-facultatif) + * [Options du formulaire de recherche](../references/searcher.md) + * [Personnalisation avancée de la classe Searcher](../cookbook/advanced-searcher.md) + + + +## Filtres du formulaire de recherches + +**Namespace: Ecommit\CrudBundle\Form\Filter** + +* Les classes `Ecommit\CrudBundle\Form\Filter\FieldFillter*` sont supprimées et remplacées par de nouvelles + classes `Ecommit\CrudBundle\Form\Filter\*Filter` (dans le même namespace) mais avec un fonctionnement et une + API totalement différents. [Voir les nouveaux filtres](../references/filters.md) +* La classe `AbstractFieldFilter` est supprimée et remplacée par `AbstractFilter`, avec un fonctionnement et une + API totalement différents. Si vous avez réalisé vos propres filtres, vous devez les adapter avec la nouvelle API. +* Les filtres sont maintenant des services ayant le tag `ecommit_crud.filter`, hériter de `AbstractFilter` + ou implémenter `FilterInterface`. + + + +## DoctrineExtension + +**Namespace: Ecommit\CrudBundle\DoctrineExtension** + +* La méthode `count` de `Paginate` est supprimée. Utiliser à la place les méthodes `Doctrine\ORM\Tools\Pagination\Paginator::count` + ou `Paginate::countQueryBuilder`. + + +## Paginator + +**Namespace: Ecommit\CrudBundle\Paginator** + +* La méthode `setResults` de `ArrayPaginator` est supprimée. Utiliser à la place la méthode `setData`. +* La méthode `setResultsWithoutSlice` de `ArrayPaginator` est supprimée. Utiliser à la place la méthode `setDataWithoutSlice`. +* La méthode `setDbalQueryBuilder` et `DoctrineDBALPaginator` est supprimée. Utiliser à la place la méthode `setQueryBuilder`. + + + +## Form + +**Namespace: Ecommit\CrudBundle\Form\Type** + +* Modifications apportées sur `DisplaySettingsType`: + * Ajout de la validation. + * Ajout des champs `resultsPerPage`, `displayedColumns`, `reset`, et `save`. + * Ajout de l'option requise `reset_settings_url`. + * L'option `resultsPerPageChoices` est renommée en `results_per_page_choices`. + * L'option `columnsChoices` est renommée en `columns_choices`. + + + +## Twig + +* Les options suivantes des fonctions Twig `paginator_links` et `crud_paginator_links` sont supprimées. Surcharger le thème Twig si besoin: + * `buttons` + * `image_first` + * `image_previous` + * `image_next` + * `image_last` + * `text_first` + * `text_previous` + * `text_next` + * `text_last` + * `use_bootstrap` + * `bootstrap_size` +* La signature de la fonction Twig `crud_paginator_links` est modifiée: + * Avant: `crud_paginator_links(Crud $crud, $options = [], $ajaxOptions = [])` + * Maintenant: `crud_paginator_links(Crud $crud, array $options = [])` + * Passer les options Ajax dans l'option `ajax_options` de `$options` +* Pour une utilisation des fonctions Twig `paginator_links` et `crud_paginator_links`, les classes CSS ont été modifiées. Voici les nouvelles classes CSS, **quelque soit l'utilisation de BootStrap ou non** (les classes CSS propres à Bootstrap ne sont pas précisées): + * Classe CSS associée au tag `nav`: + * Avant: `pagination_nobootstrap` (uniquement pour non BootStrap) + * Maintenant: `ec-crud-pagination` + * Classes CSS associées au tag `li` pour la 1ère page: + * Avant: [`text` ou `image`] + `first` + * Maintenant: `first` + * Classes CSS associées au tag `li` pour la page précédente: + * Avant: [`text` ou `image`] + `previous` + * Maintenant: `previous` + * Classes CSS associées au tag `li` pour la page suivante: + * Avant: [`text` ou `image`] + `next` + * Maintenant: `next` + * Classes CSS associées au tag `li` pour la dernière page: + * Avant: [`text` ou `image`] + `last` + * Maintenant: `last` + * Classes CSS associées au tag `li` pour la page courante: + * Avant: `pagination_current` (uniquement pour non BootStrap) + * Maintenant: `current` + * Classes CSS associées au tag `li` pour les autres pages: + * Avant: `pagination_no_current` (uniquement pour non BootStrap) + * Maintenant: Aucune +* Les options suivantes de la fonction Twig `crud_th` sont supprimées. Surcharger le thème Twig si besoin: + * `image_up` + * `image_down` +* La signature de la fonction Twig `crud_th` est modifiée: + * Avant: `crud_th($columnId, Crud $crud, $options = [], $thOptions = [], $ajaxOptions = [])` + * Maintenant: `crud_th(string $columnId, Crud $crud, array $options = [])` + * Passer les attributs th dans l'option `th_attr` de `$options` + * Passer les options Ajax dans l'option `ajax_options` de `$options` +* L'option `repeated_values_add_title` de la fonction Twig `crud_td` est supprimée. Surcharger le thème Twig si besoin. +* La signature de la fonction Twig `crud_td` est modifiée: + * Avant: `crud_td($columnId, Crud $crud, $value, $options = [], $tdOptions = [])` + * Après: `crud_td(string $columnId, Crud $crud, $value, $options = [])` + * Passer les attributs td dans l'option `td_attr` de `$options` +* La fonction Twig `crud_search_form` est renommée en `crud_search_form_start`. La signature est modifiée: + * Avant: `crud_search_form(Crud $crud, $ajaxOptions = [], $htmlOptions = [])` + * Après: `crud_search_form_start(Crud $crud, array $options = [])` + * Passer les options Ajax dans l'option `ajax_options` de `$options` + * Passer les attributs HTML du tag dans l'option `form_attr` de `$options` +* La fonction Twig `crud_search_reset`est renommée en `crud_search_form_reset`. La signature est modifiée: + * Avant: `crud_search_reset(Crud $crud, $options = [], $ajaxOptions = [], $htmlOptions = [])` + * Après `crud_search_form_reset(Crud $crud, $options = [])` + * Passer les options Ajax dans l'option `ajax_options` de `$options` + * Passer les attributs HTML du tag dans l'option `button_attr` de `$options` +* La fonction Twig `crud_search_form_submit` doit être appelée pour l'ajout du bouton d'envoi du formulaire de recherche. +* La signature de la fonction Twig `crud_display_settings` est modifiée: + * Avant: `crud_display_settings(Crud $crud, $options = [], $ajaxOptions = [])` + * Maintenant: `crud_display_settings(Crud $crud, array $options = [])` + * Passer les options Ajax dans l'option `ajax_options` de `$options` +* Les options suivantes de la fonction Twig `crud_display_settings` sont supprimées. Surcharger le thème Twig si besoin: + * `image_url` + * `use_bootstrap` + * `modal_close_div_class` +* L'option `template` des fonctions Twig suivantes est renommée en `render`: + * `paginator_links` + * `crud_paginator_links` + * `crud_th` + * `crud_td` + * `crud_display_settings` + * `crud_search_form_reset` (anciennement `crud_search_reset`) +* Les fonctions Twig suivantes sont supprimées. Voir l'alternative avec [le nouveau système](../references/modal.md): + * `crud_declare_modal` + * `crud_remote_modal` + * `crud_form_modal` +* La constructeur de la classe `CrudExtension` est modifié. +* Les templates suivants sont supprimés: + * `EcommitCrud/Crud/double_search.html.twig` + * `EcommitCrud/Crud/form_settings_*` + + + +## Crud Helper + +**Namespace: Ecommit\CrudBundle\Helper** + +La classe `CrudHelper` est supprimée. Utiliser à la places les fonctions Twig. + + + +## Traductions + +* Toutes les traductions de la version 2.x (dans le domaine `messages`) sont supprimées. Certaines de ces traductions + ont été migrées vers un nouveau domaine `EcommitCrudBundle`: + +| ID des traductions dans 2.x (domaine `messages`) | ID équivalent dans 3.0 (domaine `EcommitCrudBundle`) | +| --- | --- | +| Display Settings | display_settings.title | +| Number of results per page | display_settings.results_per_page | +| Columns to be shown | display_settings.displayed_columns | +| You need to select at least one column to display | *Aucun équivalent* | +| Save | display_settings.save | +| Search | search.submit | +| Reset | search.reset | +| {0} No results\|{1} 1 result found\|]1,Inf] %count% results found | *Aucun équivalent* | +| Page %firstPage%/%lastPage% | *Aucun équivalent* | +| Results %first%-%last% | *Aucun équivalent* | +| Page %page%/%lastPage% | *Aucun équivalent* | +| filter.true | filter.true | +| filter.false | filter.false | +| filter.choices.placeholder | *Aucun équivalent* | +| picker.add | *Aucun équivalent* | +| picker.list | *Aucun équivalent* | +| reset_display_settings | display_settings.reset_display_settings | +| check_all | display_settings.check_all | +| uncheck_all | display_settings.uncheck_all | + + +## Assets + +* Les images `images/i16/*.png` sont supprimées. +* Le fichier JavaScript `js/scrollToFirstMessage.js` est supprimé. From abc1a522628ba470c7dfcfcfb1d9bbd1b7be0731 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Mon, 16 Aug 2021 14:40:21 +0200 Subject: [PATCH 077/264] Update doc --- doc/cookbook/advanced-searcher.md | 68 ++++++++++++++++++++++++++++++- doc/cookbook/create_filter.md | 17 ++++++++ doc/index.md | 1 + doc/references/filters.md | 5 +++ doc/upgrade/3.0.md | 2 +- 5 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 doc/cookbook/create_filter.md diff --git a/doc/cookbook/advanced-searcher.md b/doc/cookbook/advanced-searcher.md index 3b0fe4d..e594fb8 100644 --- a/doc/cookbook/advanced-searcher.md +++ b/doc/cookbook/advanced-searcher.md @@ -1,6 +1,8 @@ # Personnalisation avancée de la classe Searcher -## Solution 1: Utilisation de addField et updateQueryBuilder +## Personnalisation des filtres de recherche + +### Solution 1: Utilisation de addField et updateQueryBuilder ```php setDefaults([ + 'my_option' => null, + ]); + } +} +``` + +Ces options doivent être passées au 3ème argument (`$options`) de la méthode `createSearchForm` de `Crud` : + +```diff +createCrud('my_crud'); //Passé en argument: Nom du CRUD + $crud->addColumn('id', 'c1.id', 'Id') + //... + ->setRoute('my_crud_ajax') +- ->createSearchForm(new CarSearcher()) ++ ->createSearchForm(new CarSearcher(), null , [ ++ 'my_option' => 'my_value', ++ ]) + //... + ->init(); + + return $crud; + } +} +``` + +Ces options peuvent être enfin récupérées dans les arguments `$options` des méthodes `buildForm` et `updateQueryBuilder` de `SearcherInterface`. diff --git a/doc/cookbook/create_filter.md b/doc/cookbook/create_filter.md new file mode 100644 index 0000000..97a1cd2 --- /dev/null +++ b/doc/cookbook/create_filter.md @@ -0,0 +1,17 @@ +# Création d'un filtre de recherche + +> **_ALTERNATIVES :_** +> +> * [Utilisation d'un filtre existant](../references/filters.md) +> * [Personnalisation avancée de la classe Searcher](../cookbook/advanced-searcher.md) + + +Un filtre doit être une classe qui hérite de `AbstractFilter` ou implémente `FilterInterface` : +* La méthode `buildForm` ajoute le filtre dans le fromulaire de recherche (utiliser `$builder->addField()`). +* La méthode `updateQueryBuilder` modifie le QueryBuilder en fonction de la valeur saisie dans le filtre. +* La méthode `configureOptions` définie les éventuelles options. + +> **_REMARQUE :_** Il est conseillé de regarder le code source des filtres existants. + + +La classe doit être déclarée comme service ayant le [tag](https://symfony.com/doc/current/service_container/tags.html) `ecommit_crud.filter`. diff --git a/doc/index.md b/doc/index.md index 0645db1..d429ba4 100644 --- a/doc/index.md +++ b/doc/index.md @@ -18,6 +18,7 @@ ## Fonctionnalités avancées * [Personnalisation avancée de la classe Searcher](cookbook/advanced-searcher.md) +* [Création d'un filtre de recherche](cookbook/create_filter.md) * [Ajout de données aux templates](cookbook/data-template.md) * [Affichage des résultats uniquement si recherche envoyée](cookbook/display_results.md) * [Personnaliser les IDs des Divs](cookbook/div_ids.md) diff --git a/doc/references/filters.md b/doc/references/filters.md index a1ed941..8899c01 100644 --- a/doc/references/filters.md +++ b/doc/references/filters.md @@ -1,5 +1,10 @@ # Filtres +> **_A VOIR AUSSI :_** +> +> * [Personnalisation avancée de la classe Searcher](../cookbook/advanced-searcher.md) +> * [Création d'un filtre de recherche](create_filter.md) + ## Options générales pour tous les filtres | Option | Description | Requis | Valeur par défaut | diff --git a/doc/upgrade/3.0.md b/doc/upgrade/3.0.md index 2f0ec99..3508a6b 100644 --- a/doc/upgrade/3.0.md +++ b/doc/upgrade/3.0.md @@ -117,7 +117,7 @@ classes `Ecommit\CrudBundle\Form\Filter\*Filter` (dans le même namespace) mais avec un fonctionnement et une API totalement différents. [Voir les nouveaux filtres](../references/filters.md) * La classe `AbstractFieldFilter` est supprimée et remplacée par `AbstractFilter`, avec un fonctionnement et une - API totalement différents. Si vous avez réalisé vos propres filtres, vous devez les adapter avec la nouvelle API. + API totalement différents. Si vous avez réalisé vos propres filtres, vous devez les adapter avec la [nouvelle API](../cookbook/create_filter.md). * Les filtres sont maintenant des services ayant le tag `ecommit_crud.filter`, hériter de `AbstractFilter` ou implémenter `FilterInterface`. From 9697a3a5d4a60350117f562e9e7d286c092bf6b8 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Mon, 16 Aug 2021 16:03:24 +0200 Subject: [PATCH 078/264] Register for autoconfiguration ("ecommit_crud.filter" tag) --- doc/cookbook/create_filter.md | 3 ++ doc/upgrade/3.0.md | 5 +-- .../EcommitCrudExtension.php | 3 ++ .../EcommitCrudExtensionTest.php | 33 +++++++++++++++++++ tests/Functional/App/Form/Filter/MyFilter.php | 20 +++++++++++ tests/Functional/App/config/services.yaml | 4 +++ 6 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 tests/DependencyInjection/EcommitCrudExtensionTest.php create mode 100644 tests/Functional/App/Form/Filter/MyFilter.php diff --git a/doc/cookbook/create_filter.md b/doc/cookbook/create_filter.md index 97a1cd2..0a815ae 100644 --- a/doc/cookbook/create_filter.md +++ b/doc/cookbook/create_filter.md @@ -15,3 +15,6 @@ Un filtre doit être une classe qui hérite de `AbstractFilter` ou implémente ` La classe doit être déclarée comme service ayant le [tag](https://symfony.com/doc/current/service_container/tags.html) `ecommit_crud.filter`. + +> **_REMARQUE :_** Avec l'option [autoconfigure](https://symfony.com/doc/current/service_container.html#services-autoconfigure) +> de Symfony, le tag est automatiquement ajouté aux services. diff --git a/doc/upgrade/3.0.md b/doc/upgrade/3.0.md index 3508a6b..db3a17c 100644 --- a/doc/upgrade/3.0.md +++ b/doc/upgrade/3.0.md @@ -118,8 +118,9 @@ API totalement différents. [Voir les nouveaux filtres](../references/filters.md) * La classe `AbstractFieldFilter` est supprimée et remplacée par `AbstractFilter`, avec un fonctionnement et une API totalement différents. Si vous avez réalisé vos propres filtres, vous devez les adapter avec la [nouvelle API](../cookbook/create_filter.md). -* Les filtres sont maintenant des services ayant le tag `ecommit_crud.filter`, hériter de `AbstractFilter` - ou implémenter `FilterInterface`. +* Les filtres sont maintenant des services ayant le tag `ecommit_crud.filter`, héritant de `AbstractFilter` + ou implémentant `FilterInterface`. (Remarque: Avec l'option [autoconfigure](https://symfony.com/doc/current/service_container.html#services-autoconfigure) + de Symfony, le tag est automatiquement ajouté aux services). diff --git a/src/DependencyInjection/EcommitCrudExtension.php b/src/DependencyInjection/EcommitCrudExtension.php index af8da93..8673a5d 100644 --- a/src/DependencyInjection/EcommitCrudExtension.php +++ b/src/DependencyInjection/EcommitCrudExtension.php @@ -13,6 +13,7 @@ namespace Ecommit\CrudBundle\DependencyInjection; +use Ecommit\CrudBundle\Form\Filter\FilterInterface; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; @@ -40,5 +41,7 @@ public function load(array $config, ContainerBuilder $container): void $container->setParameter('ecommit_crud.theme', $config['theme']); $container->setParameter('ecommit_crud.icon_theme', $config['icon_theme']); $container->setParameter('ecommit_crud.twig_functions_configuration', $config['twig_functions_configuration']); + + $container->registerForAutoconfiguration(FilterInterface::class)->addTag('ecommit_crud.filter'); } } diff --git a/tests/DependencyInjection/EcommitCrudExtensionTest.php b/tests/DependencyInjection/EcommitCrudExtensionTest.php new file mode 100644 index 0000000..d6c772f --- /dev/null +++ b/tests/DependencyInjection/EcommitCrudExtensionTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Ecommit\CrudBundle\Tests\DependencyInjection; + +use Ecommit\CrudBundle\Crud\CrudFilters; +use Ecommit\CrudBundle\Tests\Functional\App\Form\Filter\MyFilter; +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; + +class EcommitCrudExtensionTest extends KernelTestCase +{ + protected function setUp(): void + { + static::bootKernel(); + } + + public function testAutoconfigureTag(): void + { + $crudFilters = self::$container->get(CrudFilters::class); + + $this->assertTrue($crudFilters->has(MyFilter::class)); + } +} diff --git a/tests/Functional/App/Form/Filter/MyFilter.php b/tests/Functional/App/Form/Filter/MyFilter.php new file mode 100644 index 0000000..07ebde5 --- /dev/null +++ b/tests/Functional/App/Form/Filter/MyFilter.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Ecommit\CrudBundle\Tests\Functional\App\Form\Filter; + +use Ecommit\CrudBundle\Form\Filter\AbstractFilter; + +class MyFilter extends AbstractFilter +{ +} diff --git a/tests/Functional/App/config/services.yaml b/tests/Functional/App/config/services.yaml index 62c19f6..d6ae84d 100644 --- a/tests/Functional/App/config/services.yaml +++ b/tests/Functional/App/config/services.yaml @@ -12,3 +12,7 @@ services: Ecommit\CrudBundle\Tests\Functional\App\Controller\: resource: '../Controller' tags: ['controller.service_arguments'] + + Ecommit\CrudBundle\Tests\Functional\App\Form\Filter\: + resource: '../Form/Filter' + #Do not add the tag - Autoconfigure is tested by EcommitCrudExtensionTest From 26752688492235f47bcecd581c0760597612451c Mon Sep 17 00:00:00 2001 From: hlecorche Date: Fri, 20 Aug 2021 10:56:36 +0200 Subject: [PATCH 079/264] [Callback] Add registerCallback --- .eslintrc.js | 1 - doc/references/js-callbacks.md | 41 +++++--- src/Resources/assets/js/callback-manager.js | 47 +++++++++ src/Resources/assets/js/callback.js | 20 +--- tests/Resources/public/js/ajax.spec.js | 81 +++++++++------ .../public/js/callback-manager.spec.js | 98 +++++++++++++++++++ tests/Resources/public/js/callback.spec.js | 67 +++++++------ 7 files changed, 267 insertions(+), 88 deletions(-) create mode 100644 src/Resources/assets/js/callback-manager.js create mode 100644 tests/Resources/public/js/callback-manager.spec.js diff --git a/.eslintrc.js b/.eslintrc.js index 76d8e29..994c02b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -20,7 +20,6 @@ module.exports = { rules: { 'indent': ['error', 4], 'linebreak-style': ['error', 'unix'], - 'no-eval': 'off', 'semi': 'off' } } diff --git a/doc/references/js-callbacks.md b/doc/references/js-callbacks.md index 0683180..3f661a5 100644 --- a/doc/references/js-callbacks.md +++ b/doc/references/js-callbacks.md @@ -2,22 +2,27 @@ ## Définition des callbacks +**Méthode 1 :** + ```js -//Methode 1 var callback = function (arg) { alert('go'); //... }; +``` + +**Méthode 2 :** -//Methode 2 -var callback = 'alert("go")'; +```js +import * as callbackManager from '@ecommit/crud-bundle/js/callback-manager'; -//Méthode 3 -var callback = 'function (arg) { alert("go"); }'; +callbackManager.registerCallback('my_callback_name', function (arg) { + alert('go'); +}); ``` -Gestion de plusieurs callbacks : +**Gestion de plusieurs callbacks :** ```js var callbacks = [ @@ -28,7 +33,7 @@ var callbacks = [ }, //Callback 2 - 'alert("go")' + 'my_callback_name' //Callback enregistré par nom (voir méthode 2 plus haut) ]; //Gestion des priorités (priorité par défaut = 0) @@ -44,7 +49,7 @@ var callbacks = [ //Callback 2 { - callback: 'alert("go")', + callback: 'my_callback_name', //Callback enregistré par nom (voir méthode 2 plus haut) priority: 20 } ]; @@ -53,14 +58,28 @@ var callbacks = [ ## Appel des callbacks +La fonction `runCallback` doit être appelée avec comme arguments : +* Callback ou liste de callbacks +* Argument passé à l'appel de chaque callback + + +Le premier argument de la fonction `runCallback` est un callback ou liste de callbacks. Un callback (unique ou parmi le tableau) peut être : +* 1 callback JavaScript (voir méthode 1 plus haut) +* 1 nom de callback enregistré par par la méthode `registerCallback` (voir méthode 2 plus haut) +* 1 objet avec les propriétés `callback` et `priority` (voir plus haut) + + ```js import runCallback from '@ecommit/crud-bundle/js/callback'; +//Exemple avec un unique callback +//Voir paragraphe précédent +//var callback = +runCallback(callback, '5'); + +//Exemple avec un tableau de callbacks var callbacks = [ //... Voir paragraphe précédent ]; - -//1er argument: Callback ou liste de callbacks -//2ème argument: Argument passé à l'appel de chaque callback runCallback(callbacks, '5'); ``` diff --git a/src/Resources/assets/js/callback-manager.js b/src/Resources/assets/js/callback-manager.js new file mode 100644 index 0000000..6caae07 --- /dev/null +++ b/src/Resources/assets/js/callback-manager.js @@ -0,0 +1,47 @@ +/* + * This file is part of the EcommitCrudBundle package. + * + * (c) E-commit + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +const ENGINE_KEY = Symbol.for('ecommit.crudbundle.callbackengine'); +const globalSymbols = Object.getOwnPropertySymbols(global); +if (globalSymbols.indexOf(ENGINE_KEY) === -1) { + global[ENGINE_KEY] = []; +} + +export function registerCallback (name, callback) { + if (typeof name !== 'string' && !(name instanceof String)) { + console.error('Bad name'); + } + if (!(callback instanceof Function)) { + console.error('Invalid callback ' + name); + } + + global[ENGINE_KEY][name] = callback; +} + +export function callbackIsRegistred (name) { + if (typeof name !== 'string' && !(name instanceof String)) { + console.error('Bad name'); + } + + return (undefined !== global[ENGINE_KEY][name]); +} + +export function getRegistredCallback (name) { + if (!callbackIsRegistred(name)) { + console.error('Callback not found: ' + name); + + return null; + } + + return global[ENGINE_KEY][name]; +} + +export function clear () { + global[ENGINE_KEY] = []; +} diff --git a/src/Resources/assets/js/callback.js b/src/Resources/assets/js/callback.js index b1b7f0e..8969d3e 100644 --- a/src/Resources/assets/js/callback.js +++ b/src/Resources/assets/js/callback.js @@ -8,6 +8,7 @@ */ import $ from 'jquery'; +import * as callbackManager from './callback-manager'; export default function (callbacks, arg) { if (undefined === callbacks || callbacks === null) { @@ -69,23 +70,8 @@ function processCallback (callback, arg) { return; } - const patternFunction = /^function\s*\(/i; - if (patternFunction.test(callback)) { - callback = eval('(' + callback + ')'); + callback = callbackManager.getRegistredCallback(callback); + if (callback) { callback(arg); - - return; - } - - const patternFunctionContent = /^\/(.+)\/(.+)$/i; - const groups = callback.match(patternFunctionContent); - if (groups !== null && groups.length > 0) { - console.debug(groups); - callback = eval('(function(' + groups[1] + ') {' + groups[2] + '})'); - callback(arg); - - return; } - - eval(callback); } diff --git a/tests/Resources/public/js/ajax.spec.js b/tests/Resources/public/js/ajax.spec.js index 54af1d3..1abd8de 100644 --- a/tests/Resources/public/js/ajax.spec.js +++ b/tests/Resources/public/js/ajax.spec.js @@ -8,6 +8,7 @@ */ import * as ajax from '@ecommit/crud-bundle/js/ajax'; +import * as callbackManager from '@ecommit/crud-bundle/js/callback-manager'; import $ from 'jquery'; describe('Test Ajax.sendRequest', function () { @@ -33,6 +34,7 @@ describe('Test Ajax.sendRequest', function () { afterEach(function () { jasmine.Ajax.uninstall(); $('.html-test').remove(); + callbackManager.clear(); }); it('Send request', function () { @@ -268,6 +270,7 @@ describe('Test Ajax.click', function () { afterEach(function () { jasmine.Ajax.uninstall(); $('.html-test').remove(); + callbackManager.clear(); }); it('Send request with button', function () { @@ -287,23 +290,31 @@ describe('Test Ajax.click', function () { }); it('Send request with button and data-*', function () { - $('body').append(''); + $('body').append(''); - global.callbackSuccess = jasmine.createSpy('success'); + const callbackSuccess = jasmine.createSpy('success'); + + callbackManager.registerCallback('my_callback_on_success', function (args) { + callbackSuccess(args); + }); ajax.click($('#buttonToTest')); expect(jasmine.Ajax.requests.mostRecent().url).toBe('/goodRequest'); - expect(global.callbackSuccess).toHaveBeenCalled(); + expect(callbackSuccess).toHaveBeenCalled(); }); it('Send request with button and data-* and options', function () { - $('body').append(''); + it('Send auto-request canceled by onBeforeSend option', function () { + $('body').append(''); - $('#clickToTest').click(); - - expect(jasmine.Ajax.requests.mostRecent()).toBeUndefined(); - - $(document).off('ec-crud-ajax-click-auto-before', '#clickToTest'); - }); - - it('Send auto-request canceled by onBeforeSend option (function content)', function () { - $('body').append(''); + callbackManager.registerCallback('my_callback_on_before_send', function (args) { + args.stop = true; + }); $('#clickToTest').click(); @@ -377,6 +382,7 @@ describe('Test Ajax.link', function () { afterEach(function () { jasmine.Ajax.uninstall(); $('.html-test').remove(); + callbackManager.clear(); }); it('Send request with link', function () { @@ -395,24 +401,32 @@ describe('Test Ajax.link', function () { }); it('Send request with link and data-*', function () { - $('body').append('Go !'); + $('body').append('Go !'); - global.callbackSuccess = jasmine.createSpy('success'); + const callbackSuccess = jasmine.createSpy('success'); + + callbackManager.registerCallback('my_callback_on_success', function (args) { + callbackSuccess(args); + }); ajax.link($('#linkToTest')); expect(jasmine.Ajax.requests.mostRecent().url).toBe('/goodRequest'); - expect(global.callbackSuccess).toHaveBeenCalled(); + expect(callbackSuccess).toHaveBeenCalled(); }); it('Send request with link and data-* and options', function () { - $('body').append('Go !'); + $('body').append('Go !'); // href is overridden by url option - global.callbackSuccess1 = jasmine.createSpy('success1'); + const callbackSuccess1 = jasmine.createSpy('success1'); const callbackSuccess2 = jasmine.createSpy('success2'); const callbackComplete = jasmine.createSpy('complete'); + callbackManager.registerCallback('my_callback_on_success_1', function (args) { + callbackSuccess1(args); + }); + ajax.link($('#linkToTest'), { url: '/badRequest', // overridden by data-ec-crud-ajax-url method: 'GET', // overridden by data-ec-crud-ajax-method @@ -426,7 +440,7 @@ describe('Test Ajax.link', function () { expect(jasmine.Ajax.requests.mostRecent().url).toBe('/goodRequest'); expect(jasmine.Ajax.requests.mostRecent().method).toBe('PUT'); - expect(global.callbackSuccess1).toHaveBeenCalled(); + expect(callbackSuccess1).toHaveBeenCalled(); expect(callbackSuccess2).not.toHaveBeenCalled(); expect(callbackComplete).toHaveBeenCalled(); }); @@ -466,6 +480,7 @@ describe('Test Ajax.form', function () { afterEach(function () { jasmine.Ajax.uninstall(); $('.html-test').remove(); + callbackManager.clear(); }); it('Send request with form', function () { @@ -511,11 +526,14 @@ describe('Test Ajax.form', function () { }); it('Send request with form and data-*', function () { - $('body').append(''); + $('body').append('
'); $('#formToTest input[name=var1]').val('My value 1'); $('#formToTest input[name=var2]').val('My value 2'); - global.callbackSuccess = jasmine.createSpy('success'); + const callbackSuccess = jasmine.createSpy('success'); + callbackManager.registerCallback('my_callback_on_success', function (args) { + callbackSuccess(args); + }); ajax.sendForm($('#formToTest')); @@ -524,19 +542,23 @@ describe('Test Ajax.form', function () { expect(jasmine.Ajax.requests.mostRecent().data()).toEqual({ var1: ['My value 1'], var2: ['My value 2'] }); - expect(global.callbackSuccess).toHaveBeenCalled(); + expect(callbackSuccess).toHaveBeenCalled(); }); it('Send request with form and data-* and options', function () { - $('body').append('
'); + $('body').append('
'); // action is overridden by url option $('#formToTest input[name=var1]').val('My value 1'); $('#formToTest input[name=var2]').val('My value 2'); - global.callbackSuccess1 = jasmine.createSpy('success1'); + const callbackSuccess1 = jasmine.createSpy('success1'); const callbackSuccess2 = jasmine.createSpy('success2'); const callbackComplete = jasmine.createSpy('complete'); + callbackManager.registerCallback('my_callback_on_success_1', function (args) { + callbackSuccess1(); + }); + ajax.sendForm($('#formToTest'), { url: '/badRequest', // overridden by data-ec-crud-ajax-url method: 'GET', // overridden by data-ec-crud-ajax-method @@ -554,7 +576,7 @@ describe('Test Ajax.form', function () { var1: ['My value 1'], var2: ['My value 2'] }); - expect(global.callbackSuccess1).toHaveBeenCalled(); + expect(callbackSuccess1).toHaveBeenCalled(); expect(callbackSuccess2).not.toHaveBeenCalled(); expect(callbackComplete).toHaveBeenCalled(); }); @@ -599,6 +621,7 @@ describe('Test Ajax.updateDom', function () { $('.html-test').remove(); $(document).off('ec-crud-ajax-update-dom-before'); $(document).off('ec-crud-ajax-update-dom-after'); + callbackManager.clear(); }); it('Update with "update" mode', function () { diff --git a/tests/Resources/public/js/callback-manager.spec.js b/tests/Resources/public/js/callback-manager.spec.js new file mode 100644 index 0000000..c8d716f --- /dev/null +++ b/tests/Resources/public/js/callback-manager.spec.js @@ -0,0 +1,98 @@ +/* + * This file is part of the EcommitCrudBundle package. + * + * (c) E-commit + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import * as callbackManager from '@ecommit/crud-bundle/js/callback-manager'; + +describe('Test callback manager', function () { + afterEach(function () { + callbackManager.clear(); + }); + + it('Test registerCallback with invalid name', function () { + spyOn(window.console, 'error'); + callbackManager.registerCallback(function () {}, function () {}); + expect(window.console.error).toHaveBeenCalledWith('Bad name'); + }); + + it('Test registerCallback with null name', function () { + spyOn(window.console, 'error'); + callbackManager.registerCallback(null, function () {}); + expect(window.console.error).toHaveBeenCalledWith('Bad name'); + }); + + it('Test registerCallback with invalid callback', function () { + spyOn(window.console, 'error'); + callbackManager.registerCallback('my_callback', 'callback'); + expect(window.console.error).toHaveBeenCalledWith('Invalid callback my_callback'); + }); + + it('Test registerCallback with null callback', function () { + spyOn(window.console, 'error'); + callbackManager.registerCallback('my_callback', null); + expect(window.console.error).toHaveBeenCalledWith('Invalid callback my_callback'); + }); + + it('Test callbackIsRegistred with invalid name', function () { + spyOn(window.console, 'error'); + callbackManager.callbackIsRegistred(function () {}); + expect(window.console.error).toHaveBeenCalledWith('Bad name'); + }); + + it('Test callbackIsRegistred with null name', function () { + spyOn(window.console, 'error'); + callbackManager.callbackIsRegistred(null); + expect(window.console.error).toHaveBeenCalledWith('Bad name'); + }); + + it('Test callbackIsRegistred with found callback', function () { + callbackManager.registerCallback('my_callback', function () {}); + expect(callbackManager.callbackIsRegistred('my_callback')).toBeTrue(); + }); + + it('Test callbackIsRegistred with not found callback', function () { + expect(callbackManager.callbackIsRegistred('my_callback')).toBeFalse(); + }); + + it('Test getRegistredCallback with invalid name', function () { + spyOn(window.console, 'error'); + callbackManager.getRegistredCallback(function () {}); + expect(window.console.error).toHaveBeenCalledWith('Bad name'); + }); + + it('Test getRegistredCallback with null name', function () { + spyOn(window.console, 'error'); + callbackManager.getRegistredCallback(null); + expect(window.console.error).toHaveBeenCalledWith('Bad name'); + }); + + it('Test getRegistredCallback with found callback', function () { + callbackManager.registerCallback('my_callback', function () {}); + const callback = callbackManager.getRegistredCallback('my_callback'); + expect(callback).toBeInstanceOf(Function); + }); + + it('Test getRegistredCallback with not found callback', function () { + spyOn(window.console, 'error'); + const callback = callbackManager.getRegistredCallback('my_callback'); + expect(window.console.error).toHaveBeenCalledWith('Callback not found: my_callback'); + expect(callback).toBeNull(); + }); + + it('Test clear', function () { + callbackManager.registerCallback('my_callback', function () {}); + expect(callbackManager.getRegistredCallback('my_callback')).toBeInstanceOf(Function); + + callbackManager.clear(); + + spyOn(window.console, 'error'); + const callback = callbackManager.getRegistredCallback('my_callback'); + expect(window.console.error).toHaveBeenCalledWith('Callback not found: my_callback'); + expect(callback).toBeNull(); + }); +}); diff --git a/tests/Resources/public/js/callback.spec.js b/tests/Resources/public/js/callback.spec.js index df17aa2..b20601e 100644 --- a/tests/Resources/public/js/callback.spec.js +++ b/tests/Resources/public/js/callback.spec.js @@ -8,6 +8,7 @@ */ import runCallback from '@ecommit/crud-bundle/js/callback'; +import * as callbackManager from '@ecommit/crud-bundle/js/callback-manager'; describe('Test callback', function () { it('Test callback with function', function () { @@ -21,27 +22,21 @@ describe('Test callback', function () { }); it('Test callback with string', function () { - global.myCallback = jasmine.createSpy('callback'); + const myCallback = jasmine.createSpy('callback'); + callbackManager.registerCallback('my_callback', function (arg) { + myCallback(arg); + }); - runCallback('function (arg) { myCallback(arg); }', 3); + runCallback('my_callback', 3); - expect(global.myCallback).toHaveBeenCalledWith(3); + expect(myCallback).toHaveBeenCalledWith(3); + callbackManager.clear(); }); - it('Test callback with function content with argument', function () { - global.myCallback = jasmine.createSpy('callback'); - - runCallback('/arg/myCallback(arg)', 5); - - expect(global.myCallback).toHaveBeenCalledWith(5); - }); - - it('Test callback with function content without argument', function () { - global.myCallback = jasmine.createSpy('callback'); - - runCallback('myCallback(5)', 4); - - expect(global.myCallback).toHaveBeenCalledWith(5); + it('Test callback with string - Callback not registred', function () { + spyOn(window.console, 'error'); + runCallback('my_callback_never_registred', 3); + expect(window.console.error).toHaveBeenCalledWith('Callback not found: my_callback_never_registred'); }); it('Test callback with sub array', function () { @@ -83,9 +78,19 @@ describe('Test callback', function () { it('Test callback with priorities', function () { const callback1 = jasmine.createSpy('callback1'); const callback2 = jasmine.createSpy('callback2'); - global.callback3 = jasmine.createSpy('callback3'); - global.callback4 = jasmine.createSpy('callback4'); - global.callback5 = jasmine.createSpy('callback5'); + const callback3 = jasmine.createSpy('callback3'); + const callback4 = jasmine.createSpy('callback4'); + const callback5 = jasmine.createSpy('callback5'); + + callbackManager.registerCallback('my_callback3', function (arg) { + callback3(arg); + }); + callbackManager.registerCallback('my_callback4', function (arg) { + callback4(arg); + }); + callbackManager.registerCallback('my_callback5', function (arg) { + callback5(arg); + }); runCallback([ // Called third @@ -102,28 +107,30 @@ describe('Test callback', function () { // Called second { priority: 10, - callback: 'callback3()' + callback: 'my_callback3' }, // Called fourth { - callback: 'callback4()' + callback: 'my_callback4' }, // Called in fifth - 'callback5()' + 'my_callback5' ], 'myValue'); expect(callback1).toHaveBeenCalledTimes(1); expect(callback2).toHaveBeenCalledTimes(1); - expect(global.callback3).toHaveBeenCalledTimes(1); - expect(global.callback4).toHaveBeenCalledTimes(1); - expect(global.callback5).toHaveBeenCalledTimes(1); + expect(callback3).toHaveBeenCalledTimes(1); + expect(callback4).toHaveBeenCalledTimes(1); + expect(callback5).toHaveBeenCalledTimes(1); expect(callback1).toHaveBeenCalledWith('myValue'); expect(callback2).toHaveBeenCalledWith('myValue'); - expect(callback2).toHaveBeenCalledBefore(global.callback3); - expect(global.callback3).toHaveBeenCalledBefore(callback1); - expect(callback1).toHaveBeenCalledBefore(global.callback4); - expect(global.callback4).toHaveBeenCalledBefore(global.callback5); + expect(callback2).toHaveBeenCalledBefore(callback3); + expect(callback3).toHaveBeenCalledBefore(callback1); + expect(callback1).toHaveBeenCalledBefore(callback4); + expect(callback4).toHaveBeenCalledBefore(callback5); + + callbackManager.clear(); }); }); From 333c220b9db3052a2d1c8fa727bd2e5bc0f1e493 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Fri, 20 Aug 2021 11:00:29 +0200 Subject: [PATCH 080/264] Delete scrollToFirstMessage.js --- .eslintrc.js | 1 - .../assets/js/scrollToFirstMessage.js | 83 ------------------- 2 files changed, 84 deletions(-) delete mode 100644 src/Resources/assets/js/scrollToFirstMessage.js diff --git a/.eslintrc.js b/.eslintrc.js index 994c02b..cc7768f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -12,7 +12,6 @@ module.exports = { Atomics: 'readonly', SharedArrayBuffer: 'readonly' }, - ignorePatterns: ['/src/Resources/assets/js/scrollToFirstMessage.js'], parserOptions: { ecmaVersion: 2018, sourceType: 'module' diff --git a/src/Resources/assets/js/scrollToFirstMessage.js b/src/Resources/assets/js/scrollToFirstMessage.js deleted file mode 100644 index f3e37bb..0000000 --- a/src/Resources/assets/js/scrollToFirstMessage.js +++ /dev/null @@ -1,83 +0,0 @@ -function scrollToFirstMessage(scrollToError, scrollToFlashMessage, openTableIfMessage) -{ - if( typeof(scrollToError) == 'undefined' ){ - scrollToError = true; - } - if( typeof(scrollToFlashMessage) == 'undefined' ){ - scrollToFlashMessage = true; - } - if( typeof(openTableIfMessage) == 'undefined' ){ - openTableIfMessage = true; - } - - $(document).ready(function() { - if (scrollToFlashMessage) { - var cible = $('.flash-message:first'); - if(cible.length > 0) { - if (openTableIfMessage) { - openTable(cible); - } - var popupWrapper = $(cible).parents('.popup_wrapper_visible:first'); - if (popupWrapper.length > 0) { - height = getPositionTocenter(getCiblePositionInModal(cible)); - $(popupWrapper).animate({scrollTop:height}); - } else { - height = getPositionTocenter(cible.offset().top); - $('html,body').animate({scrollTop:height}); - } - return; - } - } - - if (scrollToError) { - var cible = $('li.form_error_message:first'); - if(cible.length > 0) { - if (openTableIfMessage) { - openTable(cible); - } - - var popupWrapper = $(cible).parents('.popup_wrapper_visible:first'); - if (popupWrapper.length > 0) { - height = getPositionTocenter(getCiblePositionInModal(cible)); - $(popupWrapper).animate({scrollTop:height}); - } else { - height = getPositionTocenter(cible.offset().top); - $('html,body').animate({scrollTop:height}); - } - return; - } - } - }); -} - -function openTable(cible) -{ - pane = cible.parents('div.pane-auto-display-first-message'); - table = cible.parents('div.tab-auto-display-first-message'); - if(pane.length > 0 && table.length > 0) { - idTable = table.attr('id'); - indexPane = pane.prevAll("#"+idTable+" div.pane-auto-display-first-message").length; - api = $("#"+idTable).tabs(); - api.click(indexPane); - } -} - -function getCiblePositionInModal(cible) -{ - var divModal = $(cible).parents('.crud_modal:first'); - var cibleOffset = parseInt(cible.offset().top); - var divModalOffset = parseInt(divModal.offset().top); - var divModalMarginTop = parseInt(divModal.css('margin-top')); - - return cibleOffset - divModalOffset + divModalMarginTop; -} - -function getPositionTocenter(errorPosition) -{ - var positionToCenter = errorPosition - ($(window).height() / 2); - if (positionToCenter < 0) { - positionToCenter = 0; - } - - return positionToCenter; -} From 91084464b90f63659ea6fc5f117300d8f1f2784b Mon Sep 17 00:00:00 2001 From: hlecorche Date: Fri, 20 Aug 2021 11:07:24 +0200 Subject: [PATCH 081/264] Move tests/Resources/assets from tests/Resources/public --- .github/workflows/tests.yml | 2 +- karma.conf.js | 4 ++-- tests/Resources/{public => assets}/js/ajax.spec.js | 0 .../Resources/{public => assets}/js/callback-manager.spec.js | 0 tests/Resources/{public => assets}/js/callback.spec.js | 0 tests/Resources/{public => assets}/js/main.js | 0 tests/Resources/{public => assets}/js/modal/engine/test.js | 0 .../{public => assets}/js/modal/modal-manager.spec.js | 0 .../Resources/{public => assets}/js/options-resolver.spec.js | 0 9 files changed, 3 insertions(+), 3 deletions(-) rename tests/Resources/{public => assets}/js/ajax.spec.js (100%) rename tests/Resources/{public => assets}/js/callback-manager.spec.js (100%) rename tests/Resources/{public => assets}/js/callback.spec.js (100%) rename tests/Resources/{public => assets}/js/main.js (100%) rename tests/Resources/{public => assets}/js/modal/engine/test.js (100%) rename tests/Resources/{public => assets}/js/modal/modal-manager.spec.js (100%) rename tests/Resources/{public => assets}/js/options-resolver.spec.js (100%) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bf6d29c..cf5534c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -106,4 +106,4 @@ jobs: - name: Run ESLint if: matrix.coding-standards - run: ./node_modules/.bin/eslint src/Resources/assets/js/* tests/Resources/public/js/* + run: ./node_modules/.bin/eslint src/Resources/assets/js/* tests/Resources/assets/js/* diff --git a/karma.conf.js b/karma.conf.js index df4f15f..3dd0689 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -28,10 +28,10 @@ module.exports = function(config) { terminal: false //Remove console.* logs }, files: [ - 'tests/Resources/public/js/main.js' + 'tests/Resources/assets/js/main.js' ], preprocessors: { - 'tests/Resources/public/js/main.js': ['webpack'] + 'tests/Resources/assets/js/main.js': ['webpack'] }, webpackMiddleware: { stats: 'errors-only', diff --git a/tests/Resources/public/js/ajax.spec.js b/tests/Resources/assets/js/ajax.spec.js similarity index 100% rename from tests/Resources/public/js/ajax.spec.js rename to tests/Resources/assets/js/ajax.spec.js diff --git a/tests/Resources/public/js/callback-manager.spec.js b/tests/Resources/assets/js/callback-manager.spec.js similarity index 100% rename from tests/Resources/public/js/callback-manager.spec.js rename to tests/Resources/assets/js/callback-manager.spec.js diff --git a/tests/Resources/public/js/callback.spec.js b/tests/Resources/assets/js/callback.spec.js similarity index 100% rename from tests/Resources/public/js/callback.spec.js rename to tests/Resources/assets/js/callback.spec.js diff --git a/tests/Resources/public/js/main.js b/tests/Resources/assets/js/main.js similarity index 100% rename from tests/Resources/public/js/main.js rename to tests/Resources/assets/js/main.js diff --git a/tests/Resources/public/js/modal/engine/test.js b/tests/Resources/assets/js/modal/engine/test.js similarity index 100% rename from tests/Resources/public/js/modal/engine/test.js rename to tests/Resources/assets/js/modal/engine/test.js diff --git a/tests/Resources/public/js/modal/modal-manager.spec.js b/tests/Resources/assets/js/modal/modal-manager.spec.js similarity index 100% rename from tests/Resources/public/js/modal/modal-manager.spec.js rename to tests/Resources/assets/js/modal/modal-manager.spec.js diff --git a/tests/Resources/public/js/options-resolver.spec.js b/tests/Resources/assets/js/options-resolver.spec.js similarity index 100% rename from tests/Resources/public/js/options-resolver.spec.js rename to tests/Resources/assets/js/options-resolver.spec.js From 47864cb936c593d02134f48ea33abbdd4bb0911a Mon Sep 17 00:00:00 2001 From: hlecorche Date: Fri, 20 Aug 2021 13:27:47 +0200 Subject: [PATCH 082/264] Callback with multiple arguments --- doc/references/ajax.md | 8 +- doc/references/js-callbacks.md | 12 +++ src/Resources/assets/js/ajax.js | 21 ++--- src/Resources/assets/js/callback.js | 18 ++-- src/Resources/assets/js/crud.js | 12 +-- .../assets/js/modal/modal-manager.js | 2 +- tests/Resources/assets/js/ajax.spec.js | 86 +++++++++---------- tests/Resources/assets/js/callback.spec.js | 10 +++ .../assets/js/modal/modal-manager.spec.js | 4 +- 9 files changed, 92 insertions(+), 81 deletions(-) diff --git a/doc/references/ajax.md b/doc/references/ajax.md index a44ac01..372d676 100644 --- a/doc/references/ajax.md +++ b/doc/references/ajax.md @@ -22,10 +22,10 @@ Options : | url | Url de l'action Ajax | Oui | | | update | Si défini, mise à jour du DOM avec le résultat. (voir doc de la fonction `updateDom` plus bas) | Non | | | updateMode | Méthode à utiliser pour la mise à jour du DOM (voir doc de la fonction `updateDom` plus bas) | Non | update | -| onBeforeSend | [Callback(s)](js-callbacks.md#définition-des-callbacks) lancé(s) avant l'envoi de la requête | Non | | -| onSuccess | [Callback(s)](js-callbacks.md#définition-des-callbacks) lancé(s) en cas de succès de la réponse | Non | | -| onError | [Callback(s)](js-callbacks.md#définition-des-callbacks) lancé(s) en cas d'erreur lors de la réponse | Non | | -| onComplete | [Callback(s)](js-callbacks.md#définition-des-callbacks) lancé(s) après réponse | Non | | +| onBeforeSend | [Callback(s)](js-callbacks.md#définition-des-callbacks) lancé(s) avant l'envoi de la requête. `Function(options)`. Dans le callback peut passer `options.stop = false` pour annuler la requête. | Non | | +| onSuccess | [Callback(s)](js-callbacks.md#définition-des-callbacks) lancé(s) en cas de succès de la réponse. `Function(data, textStatus, jqXHR)` | Non | | +| onError | [Callback(s)](js-callbacks.md#définition-des-callbacks) lancé(s) en cas d'erreur lors de la réponse. `Function(jqXHR, textStatus, errorThrown)` | Non | | +| onComplete | [Callback(s)](js-callbacks.md#définition-des-callbacks) lancé(s) après réponse. `Function(jqXHR, textStatus)` | Non | | | dataType | Type de données attendu (html, xml, json, jsonp, text) | Non | html | | method | Méthode HTTP | Non | POST | | data | Données envoyées dans la requête | Non | | diff --git a/doc/references/js-callbacks.md b/doc/references/js-callbacks.md index 3f661a5..78d305b 100644 --- a/doc/references/js-callbacks.md +++ b/doc/references/js-callbacks.md @@ -83,3 +83,15 @@ var callbacks = [ ]; runCallback(callbacks, '5'); ``` + +Exemple avec un callback à plusieurs aguments : + +```js +import runCallback from '@ecommit/crud-bundle/js/callback'; + +var callback = function (arg1, arg2) { + var sum = arg1 + arg2; + alert(sum); +}; +runCallback(callback, 10, 5); //Display "15" +``` diff --git a/src/Resources/assets/js/ajax.js b/src/Resources/assets/js/ajax.js index f5c4d0b..df97ea4 100644 --- a/src/Resources/assets/js/ajax.js +++ b/src/Resources/assets/js/ajax.js @@ -84,8 +84,8 @@ export function sendRequest (options) { if (optionsResolver.isNotBlank(options.update)) { callbacksSuccess.push({ priority: 10, - callback: function (args) { - updateDom(options.update, options.updateMode, args.data); + callback: function (data, textStatus, jqXHR) { + updateDom(options.update, options.updateMode, data); } }) } @@ -100,24 +100,13 @@ export function sendRequest (options) { cache: options.cache, data: options.data, success: function (data, textStatus, jqXHR) { - runCallback(callbacksSuccess, { - data: data, - textStatus: textStatus, - jqXHR: jqXHR - }); + runCallback(callbacksSuccess, data, textStatus, jqXHR); }, error: function (jqXHR, textStatus, errorThrown) { - runCallback(options.onError, { - jqXHR: jqXHR, - textStatus: textStatus, - errorThrown: errorThrown - }); + runCallback(options.onError, jqXHR, textStatus, errorThrown); }, complete: function (jqXHR, textStatus) { - runCallback(options.onComplete, { - jqXHR: jqXHR, - textStatus: textStatus - }); + runCallback(options.onComplete, jqXHR, textStatus); } }); } diff --git a/src/Resources/assets/js/callback.js b/src/Resources/assets/js/callback.js index 8969d3e..6be0177 100644 --- a/src/Resources/assets/js/callback.js +++ b/src/Resources/assets/js/callback.js @@ -10,7 +10,7 @@ import $ from 'jquery'; import * as callbackManager from './callback-manager'; -export default function (callbacks, arg) { +export default function (callbacks, ...args) { if (undefined === callbacks || callbacks === null) { return; } @@ -37,7 +37,7 @@ export default function (callbacks, arg) { }); $.each(newCallbacks, function (key, value) { - processCallback(value.callback, arg); + processCallback(value.callback, args); }); } @@ -59,19 +59,19 @@ function addCallbacksToStack (value, stack) { } } -function processCallback (callback, arg) { - if (callback instanceof Function) { - callback(arg); +function processCallback (subject, args) { + if (subject instanceof Function) { + subject(...args); return; } - if (typeof callback !== 'string' && !(callback instanceof String)) { + if (typeof subject !== 'string' && !(subject instanceof String)) { return; } - callback = callbackManager.getRegistredCallback(callback); - if (callback) { - callback(arg); + subject = callbackManager.getRegistredCallback(subject); + if (subject) { + subject(...args); } } diff --git a/src/Resources/assets/js/crud.js b/src/Resources/assets/js/crud.js index aca584a..14e9f5e 100644 --- a/src/Resources/assets/js/crud.js +++ b/src/Resources/assets/js/crud.js @@ -18,8 +18,8 @@ $(document).on('submit', 'form.ec-crud-search-form', function (event) { const listId = $(this).attr('data-crud-list-id'); sendForm(this, { - onSuccess: function (args) { - const json = $.parseJSON(args.data); + onSuccess: function (data, textStatus, jqXHR) { + const json = $.parseJSON(data); updateDom($('#' + searchId), 'update', json.render_search); updateDom($('#' + listId), 'update', json.render_list); @@ -32,8 +32,8 @@ $(document).on('click', 'button.ec-crud-search-reset', function (event) { const listId = $(this).attr('data-crud-list-id'); click(this, { - onSuccess: function (args) { - const json = $.parseJSON(args.data); + onSuccess: function (data, textStatus, jqXHR) { + const json = $.parseJSON(data); updateDom($('#' + searchId), 'update', json.render_search); updateDom($('#' + listId), 'update', json.render_list); @@ -93,8 +93,8 @@ $(document).on('submit', '.ec-crud-display-settings form', function (event) { closeDisplaySettings($('#' + displaySettingsContainerId)); sendForm(this, { - onSuccess: function (args) { - const json = $.parseJSON(args.data); + onSuccess: function (data, textStatus, jqXHR) { + const json = $.parseJSON(data); updateDom($('#' + listId), 'update', json.render_list); if (!json.form_is_valid) { diff --git a/src/Resources/assets/js/modal/modal-manager.js b/src/Resources/assets/js/modal/modal-manager.js index caa60c0..d40d42a 100644 --- a/src/Resources/assets/js/modal/modal-manager.js +++ b/src/Resources/assets/js/modal/modal-manager.js @@ -130,7 +130,7 @@ export function openRemoteModal (options) { const callbacksSuccess = [ { priority: 1, - callback: function (args) { + callback: function (data, textStatus, jqXHR) { openModal(options); } } diff --git a/tests/Resources/assets/js/ajax.spec.js b/tests/Resources/assets/js/ajax.spec.js index 1abd8de..ff77bb2 100644 --- a/tests/Resources/assets/js/ajax.spec.js +++ b/tests/Resources/assets/js/ajax.spec.js @@ -44,14 +44,14 @@ describe('Test Ajax.sendRequest', function () { ajax.sendRequest({ url: '/goodRequest', - onComplete: function (args) { + onComplete: function (jqXHR, textStatus) { callbackComplete(); }, - onSuccess: function (args) { - callbackSuccess(args.data); + onSuccess: function (data, textStatus, jqXHR) { + callbackSuccess(data); }, - onError: function (args) { - callbackError(args.jqXHR.responseText); + onError: function (jqXHR, textStatus, errorThrown) { + callbackError(jqXHR.responseText); } }); @@ -69,14 +69,14 @@ describe('Test Ajax.sendRequest', function () { ajax.sendRequest({ url: '/error404', - onComplete: function (args) { + onComplete: function (jqXHR, textStatus) { callbackComplete(); }, - onSuccess: function (args) { - callbackSuccess(args.data); + onSuccess: function (data, textStatus, jqXHR) { + callbackSuccess(data); }, - onError: function (args) { - callbackError(args.jqXHR.responseText); + onError: function (jqXHR, textStatus, errorThrown) { + callbackError(jqXHR.responseText); } }); @@ -93,12 +93,12 @@ describe('Test Ajax.sendRequest', function () { ajax.sendRequest({ url: '/goodRequest', onSuccess: [ - function (args) { + function (data, textStatus, jqXHR) { callbackSuccess1(); }, { priority: 99, - callback: function (args) { + callback: function (data, textStatus, jqXHR) { callbackSuccess2(); } } @@ -122,7 +122,7 @@ describe('Test Ajax.sendRequest', function () { ajax.sendRequest({ url: '/goodRequest', onSuccess: { - callback: function (args) { + callback: function (data, textStatus, jqXHR) { callbackSuccess($('#ajax-result').html()); }, priority: -99 @@ -174,10 +174,10 @@ describe('Test Ajax.sendRequest', function () { ajax.sendRequest({ url: '/goodRequest', - onBeforeSend: function (args) { + onBeforeSend: function (options) { callbackBeforeSend(); }, - onSuccess: function (args) { + onSuccess: function (data, textStatus, jqXHR) { callbackSuccess(); } }); @@ -192,11 +192,11 @@ describe('Test Ajax.sendRequest', function () { ajax.sendRequest({ url: '/goodRequest', - onBeforeSend: function (args) { + onBeforeSend: function (options) { callbackBeforeSend(); - args.stop = true; + options.stop = true; }, - onSuccess: function (args) { + onSuccess: function (data, textStatus, jqXHR) { callbackSuccess(); } }); @@ -226,7 +226,7 @@ describe('Test Ajax.sendRequest', function () { ajax.sendRequest({ url: '/resultJS', onSuccess: { - callback: function (args) { + callback: function (data, textStatus, jqXHR) { callbackSuccess($('#ajax-result').html()); }, priority: -99 @@ -244,7 +244,7 @@ describe('Test Ajax.sendRequest', function () { ajax.sendRequest({ url: '/goodRequest', onSuccess: { - callback: function (args) { + callback: function (data, textStatus, jqXHR) { callbackSuccess($('#ajax-result').html()); }, priority: -99 @@ -280,7 +280,7 @@ describe('Test Ajax.click', function () { ajax.click($('#buttonToTest'), { url: '/goodRequest', - onSuccess: function (args) { + onSuccess: function (data, textStatus, jqXHR) { callbackSuccess(); } }); @@ -294,8 +294,8 @@ describe('Test Ajax.click', function () { const callbackSuccess = jasmine.createSpy('success'); - callbackManager.registerCallback('my_callback_on_success', function (args) { - callbackSuccess(args); + callbackManager.registerCallback('my_callback_on_success', function (data, textStatus, jqXHR) { + callbackSuccess(); }); ajax.click($('#buttonToTest')); @@ -311,17 +311,17 @@ describe('Test Ajax.click', function () { const callbackSuccess2 = jasmine.createSpy('success2'); const callbackComplete = jasmine.createSpy('complete'); - callbackManager.registerCallback('my_callback_on_success_1', function (args) { - callbackSuccess1(args); + callbackManager.registerCallback('my_callback_on_success_1', function (data, textStatus, jqXHR) { + callbackSuccess1(); }); ajax.click($('#buttonToTest'), { url: '/badRequest', // overridden by data-ec-crud-ajax-url method: 'GET', // overridden by data-ec-crud-ajax-method - onSuccess: function (args) { // overridden by data-ec-crud-ajax-on-success + onSuccess: function (data, textStatus, jqXHR) { // overridden by data-ec-crud-ajax-on-success callbackSuccess2(); }, - onComplete: function (args) { + onComplete: function (jqXHR, textStatus) { callbackComplete(); } }); @@ -357,8 +357,8 @@ describe('Test Ajax.click', function () { it('Send auto-request canceled by onBeforeSend option', function () { $('body').append(''); - callbackManager.registerCallback('my_callback_on_before_send', function (args) { - args.stop = true; + callbackManager.registerCallback('my_callback_on_before_send', function (options) { + options.stop = true; }); $('#clickToTest').click(); @@ -391,7 +391,7 @@ describe('Test Ajax.link', function () { const callbackSuccess = jasmine.createSpy('success'); ajax.link($('#linkToTest'), { - onSuccess: function (args) { + onSuccess: function (data, textStatus, jqXHR) { callbackSuccess(); } }); @@ -405,8 +405,8 @@ describe('Test Ajax.link', function () { const callbackSuccess = jasmine.createSpy('success'); - callbackManager.registerCallback('my_callback_on_success', function (args) { - callbackSuccess(args); + callbackManager.registerCallback('my_callback_on_success', function (data, textStatus, jqXHR) { + callbackSuccess(); }); ajax.link($('#linkToTest')); @@ -423,17 +423,17 @@ describe('Test Ajax.link', function () { const callbackSuccess2 = jasmine.createSpy('success2'); const callbackComplete = jasmine.createSpy('complete'); - callbackManager.registerCallback('my_callback_on_success_1', function (args) { - callbackSuccess1(args); + callbackManager.registerCallback('my_callback_on_success_1', function (data, textStatus, jqXHR) { + callbackSuccess1(); }); ajax.link($('#linkToTest'), { url: '/badRequest', // overridden by data-ec-crud-ajax-url method: 'GET', // overridden by data-ec-crud-ajax-method - onSuccess: function (args) { // overridden by data-ec-crud-ajax-on-success + onSuccess: function (data, textStatus, jqXHR) { // overridden by data-ec-crud-ajax-on-success callbackSuccess2(); }, - onComplete: function (args) { + onComplete: function (jqXHR, textStatus) { callbackComplete(); } }); @@ -491,7 +491,7 @@ describe('Test Ajax.form', function () { const callbackSuccess = jasmine.createSpy('success'); ajax.sendForm($('#formToTest'), { - onSuccess: function (args) { + onSuccess: function (data, textStatus, jqXHR) { callbackSuccess(); } }); @@ -512,7 +512,7 @@ describe('Test Ajax.form', function () { const callbackSuccess = jasmine.createSpy('success'); ajax.sendForm($('#formToTest'), { - onSuccess: function (args) { + onSuccess: function (data, textStatus, jqXHR) { callbackSuccess(); } }); @@ -531,8 +531,8 @@ describe('Test Ajax.form', function () { $('#formToTest input[name=var2]').val('My value 2'); const callbackSuccess = jasmine.createSpy('success'); - callbackManager.registerCallback('my_callback_on_success', function (args) { - callbackSuccess(args); + callbackManager.registerCallback('my_callback_on_success', function (data, textStatus, jqXHR) { + callbackSuccess(); }); ajax.sendForm($('#formToTest')); @@ -555,17 +555,17 @@ describe('Test Ajax.form', function () { const callbackSuccess2 = jasmine.createSpy('success2'); const callbackComplete = jasmine.createSpy('complete'); - callbackManager.registerCallback('my_callback_on_success_1', function (args) { + callbackManager.registerCallback('my_callback_on_success_1', function (data, textStatus, jqXHR) { callbackSuccess1(); }); ajax.sendForm($('#formToTest'), { url: '/badRequest', // overridden by data-ec-crud-ajax-url method: 'GET', // overridden by data-ec-crud-ajax-method - onSuccess: function (args) { // overridden by data-ec-crud-ajax-on-success + onSuccess: function (data, textStatus, jqXHR) { // overridden by data-ec-crud-ajax-on-success callbackSuccess2(); }, - onComplete: function (args) { + onComplete: function (jqXHR, textStatus) { callbackComplete(); } }); diff --git a/tests/Resources/assets/js/callback.spec.js b/tests/Resources/assets/js/callback.spec.js index b20601e..6a8bba4 100644 --- a/tests/Resources/assets/js/callback.spec.js +++ b/tests/Resources/assets/js/callback.spec.js @@ -133,4 +133,14 @@ describe('Test callback', function () { callbackManager.clear(); }); + + it('Test callback with many arguments', function () { + const callback = jasmine.createSpy('callback'); + + runCallback(function (arg1, arg2) { + callback(arg1, arg2); + }, 2, 4); + + expect(callback).toHaveBeenCalledWith(2, 4); + }); }); diff --git a/tests/Resources/assets/js/modal/modal-manager.spec.js b/tests/Resources/assets/js/modal/modal-manager.spec.js index 3b3f582..7a6e87d 100644 --- a/tests/Resources/assets/js/modal/modal-manager.spec.js +++ b/tests/Resources/assets/js/modal/modal-manager.spec.js @@ -297,13 +297,13 @@ describe('Test Modal-manager with test engine', function () { onSuccess: [ { priority: 6, - callback: function (args) { + callback: function (data, textStatus, jqXHR) { callbackSuccess1(); } }, { priority: -2, - callback: function (args) { + callback: function (data, textStatus, jqXHR) { callbackSuccess2(); } } From 8c2986d04cbb4a56d569c20c26c4c552d7901c7e Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sun, 5 Sep 2021 16:08:18 +0200 Subject: [PATCH 083/264] Remove DoctrineExtension and Paginator --- composer.json | 2 + doc/crud.md | 2 +- doc/upgrade/3.0.md | 60 ++++- src/Crud/Crud.php | 5 +- src/DoctrineExtension/Paginate.php | 256 ------------------ src/DoctrineExtension/QueryBuilderFilter.php | 246 ----------------- src/Form/Filter/CollectionFilterTrait.php | 2 +- src/Paginator/AbstractDoctrinePaginator.php | 121 --------- src/Paginator/AbstractPaginator.php | 268 ------------------- src/Paginator/ArrayPaginator.php | 92 ------- src/Paginator/DoctrineDBALPaginator.php | 54 ---- src/Paginator/DoctrineORMPaginator.php | 113 -------- src/Twig/CrudExtension.php | 4 +- tests/Twig/CrudExtensionTest.php | 115 ++++---- 14 files changed, 127 insertions(+), 1213 deletions(-) delete mode 100644 src/DoctrineExtension/Paginate.php delete mode 100644 src/DoctrineExtension/QueryBuilderFilter.php delete mode 100644 src/Paginator/AbstractDoctrinePaginator.php delete mode 100644 src/Paginator/AbstractPaginator.php delete mode 100644 src/Paginator/ArrayPaginator.php delete mode 100644 src/Paginator/DoctrineDBALPaginator.php delete mode 100644 src/Paginator/DoctrineORMPaginator.php diff --git a/composer.json b/composer.json index 9ef6e6c..451b825 100644 --- a/composer.json +++ b/composer.json @@ -15,6 +15,8 @@ "doctrine/doctrine-bundle": "^1.12.3|^2.0.3", "doctrine/orm": "^2.6.4", "doctrine/persistence": "^1.3|^2.0", + "ecommit/doctrine-utils": "1.0.x-dev", + "ecommit/paginator": "1.0.x-dev", "ecommit/scalar-values": "^1.0", "symfony/asset": "^4.4|^5.0", "symfony/config": "^4.4.12|^5.1.4", diff --git a/doc/crud.md b/doc/crud.md index c255320..8667d63 100644 --- a/doc/crud.md +++ b/doc/crud.md @@ -256,7 +256,7 @@ La classe Searcher représente les champs du formulaire de recherche. Si vous ne désirez pas activer le formulaire de recherche sur le CRUD, passez ce paragraphe. -Note classe doit hériter de `Ecommit\CrudBundle\Form\Searcher\AbstractSearcher` (ou implémenter `Ecommit\CrudBundle\Form\Searcher\SearcherInterface`) : +Notre classe doit hériter de `Ecommit\CrudBundle\Form\Searcher\AbstractSearcher` (ou implémenter `Ecommit\CrudBundle\Form\Searcher\SearcherInterface`) : ```php **_REMARQUE:_** Ce bundle a comme dépendances cette librairie. + +Si vous utilisez l'une de ces classes, voici la migration à effectuer : +* Méthode `Paginate::count` : Pas d'équivalent +* Méthode `Paginate::countQueryBuilder` : Utiliser `Ecommit\DoctrineUtils\Paginator\DoctrinePaginatorBuilder::countQueryBuilder` + avec les adaptations suivantes : + * La méthode contient un unique argument : Un tableau d'options (le nom des options reste inchangé). L'objet QueryBuilder + est à passer dans l'option `query_builder` de ce tableau +* Méthode `Paginate::createDoctrinePaginator` : Utiliser `Ecommit\DoctrineUtils\Paginator\DoctrinePaginatorBuilder::createDoctrinePaginator` + avec les adaptations suivantes : + * Retourne un objet de type `Ecommit\DoctrineUtils\Paginator\AbstractDoctrinePaginator` + * L'option `behavior` n'existe plus. Le choix du comportement est réalisé en fonction de la nouvelle option `by_identifier` (si nulle ou non). + * L'option `identifier` est renommée en `by_identifier`. + * Les options `count_manual_value` et `count_options` sont fusionnées en une unique option `count`. ## Paginator **Namespace: Ecommit\CrudBundle\Paginator** -* La méthode `setResults` de `ArrayPaginator` est supprimée. Utiliser à la place la méthode `setData`. -* La méthode `setResultsWithoutSlice` de `ArrayPaginator` est supprimée. Utiliser à la place la méthode `setDataWithoutSlice`. -* La méthode `setDbalQueryBuilder` et `DoctrineDBALPaginator` est supprimée. Utiliser à la place la méthode `setQueryBuilder`. +Les classes `Ecommit\CrudBundle\Paginator\*` sont supprimées. Utiliser à la place [ecommit/paginator](https://github.com/e-commit/paginator) +et [ecommit/doctrine-utils](https://github.com/e-commit/doctrine-utils) : + +> **_REMARQUE:_** Ce bundle a comme dépendances ces 2 librairies. + +Si vous utilisez l'une de ces classes, voici la migration à effectuer : +* Pour `AbstractPaginator` (et donc `ArrayPaginator`, `AbstractDoctrinePaginator`, `DoctrineDBALPaginator` et `DoctrineORMPaginator`) : + * L'unique valeur du constructeur est un tableau d'options. + * Toutes les méthodes publiques `set*` n'existent plus. Utiliser le tableau d'options du constructeur. + * Ne plus appeler la fonction `init` qui est supprimée. + * Passez le numéro de page et le nombre résultats par page dans les options du constructeurs: options `page` (oligatoire) et `max_per_page` (facultative). + * Méthodes publiques ajoutées : `pageExists`, `isInitialized`, `getOptions`, `getOption`. + * Méthodes abstraites protégées ajoutées : `buildCount`, `buildIterator`. + * Méthodes publiques supprimées : `getCountResults` (utiliser méthode `count`), `setPage` (utiliser l'option `page` du constructeur), + `getMaxPerPage` (utiliser l'option `max_per_page`) du constructeur. + * Méthodes publiques abstraites supprimées : `init`, `getResults` (utiliser la méthode `getIterator`). + * Méthodes protégées supprimées : `setCountResults` (utiliser l'option `count` du constructeur), `initLastPage`, `isIteratorInitialized`. + * Données membres protégées supprimées: `page`, `maxPerPage`, `lastPage`, `countResults`, `results`. + * L'équivalent de `AbstractPaginator` est `Ecommit\Paginator\AbstractPaginator`. +* Pour `ArrayPaginator`, utilisez à la pace `Ecommit\Paginator\ArrayPaginator` avec les modifications suivantes : + * Les méthodes `setData` et `setResults` sont supprimées. Utiliser à la place l'option `data` du constructeur. + * Les méthodes `setResultsWithoutSlice` et `setDataWithoutSlice` sont supprimées. Utiliser à la place les 2 options + `data` et `count` du constructeur. + * Données membres protégées supprimées : `initialObjects`, `manualCountResults`. +* Pour `AbstractDoctrinePaginator` (et donc `DoctrineDBALPaginator` et `DoctrineORMPaginator`) : + * L'objet QueryBuilder est à passer dans l'option `query_builder` du constructeur. + * Méthodes abstraites protégées supprimées : `getQueryBuilderClass`, `initPaginator`. + * Méthodes publiques supprimées : `getQueryBuilder`, `setQueryBuilder`, `getManualCountResults`, `setManualCountResults` + `getCountOptions`, `setCountOptions`. + * Données membres protégées supprimées : `query`, `manualCountResults`, `countOptions`. + * L'équivalent de `AbstractDoctrinePaginator` est `Ecommit\Plaginator\AbstractDoctrinePaginator`. +* Pour `DoctrineDBALPaginator`, utilisez à la place `Ecommit\DoctrineUtils\Paginator\DoctrineDBALPaginator`. +* Pour `DoctrineORMPaginator`, utilisez à la place `Ecommit\DoctrineUtils\Paginator\DoctrineORMPaginator` avec les modifications suivantes : + * La méthode `setSimplifiedRequest` est supprimée. Utiliser à la place l'option `simplified_request` du constructeur. + * La méthode `setFetchJoinCollection` est supprimée. Utiliser à la place l'option `fetch_join_collection` du constructeur. + * Méthodes publiques supprimées : `isSimplifiedRequest`, `isFetchJoinCollection`. @@ -169,6 +216,9 @@ * `text_last` * `use_bootstrap` * `bootstrap_size` +* La signature de la fonction Twig `paginator_links` est modifiée: + * Avant `paginator_links(AbstractPaginator $paginator, $routeName, $routeParams, $options)` + * Maintenance: `paginator_links(PaginatorInterface $paginator, string $routeName, array $routeParams = [], array $options = [])` * La signature de la fonction Twig `crud_paginator_links` est modifiée: * Avant: `crud_paginator_links(Crud $crud, $options = [], $ajaxOptions = [])` * Maintenant: `crud_paginator_links(Crud $crud, array $options = [])` diff --git a/src/Crud/Crud.php b/src/Crud/Crud.php index 4d904db..16f795b 100644 --- a/src/Crud/Crud.php +++ b/src/Crud/Crud.php @@ -13,10 +13,10 @@ namespace Ecommit\CrudBundle\Crud; -use Ecommit\CrudBundle\DoctrineExtension\Paginate; use Ecommit\CrudBundle\Entity\UserCrudSettings; use Ecommit\CrudBundle\Form\Searcher\SearcherInterface; use Ecommit\CrudBundle\Form\Type\DisplaySettingsType; +use Ecommit\DoctrineUtils\Paginator\DoctrinePaginatorBuilder; use Psr\Container\ContainerInterface; use Symfony\Component\Form\Exception\TransformationFailedException; use Symfony\Component\Form\Form; @@ -777,13 +777,12 @@ public function buildQuery(): void $paginatorOptions = $this->buildPaginator; } - $this->paginator = Paginate::createDoctrinePaginator( + $this->paginator = DoctrinePaginatorBuilder::createDoctrinePaginator( $this->queryBuilder, $this->sessionValues->page, $this->sessionValues->resultsPerPage, $paginatorOptions ); - $this->paginator->init(); } } } diff --git a/src/DoctrineExtension/Paginate.php b/src/DoctrineExtension/Paginate.php deleted file mode 100644 index c8f53b4..0000000 --- a/src/DoctrineExtension/Paginate.php +++ /dev/null @@ -1,256 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Ecommit\CrudBundle\DoctrineExtension; - -use Doctrine\ORM\NativeQuery; -use Doctrine\ORM\Query\Parameter; -use Doctrine\ORM\Query\ResultSetMapping; -use Doctrine\ORM\Tools\Pagination\Paginator; -use Ecommit\CrudBundle\Paginator\AbstractPaginator; -use Ecommit\CrudBundle\Paginator\ArrayPaginator; -use Ecommit\CrudBundle\Paginator\DoctrineDBALPaginator; -use Ecommit\CrudBundle\Paginator\DoctrineORMPaginator; -use Symfony\Component\OptionsResolver\Exception\MissingOptionsException; -use Symfony\Component\OptionsResolver\OptionsResolver; - -class Paginate -{ - /** - * @param \Doctrine\ORM\QueryBuilder|\Doctrine\DBAL\Query\QueryBuilder $queryBuilder - * - * @throws \Exception - * - * @return int - */ - public static function countQueryBuilder($queryBuilder, array $options = []) - { - if ($queryBuilder instanceof \Doctrine\ORM\QueryBuilder) { - $useORM = true; - } elseif ($queryBuilder instanceof \Doctrine\DBAL\Query\QueryBuilder) { - $useORM = false; - } else { - throw new \Exception('Bad QueryBuilder'); - } - - $resolver = new OptionsResolver(); - $resolver->setDefaults([ - //Behavior. Availabled values: - // - count_by_alias: Use alias. Option "alias" is required - // - count_by_sub_request: Use sub request - // - orm: Use Doctrine Paginator - 'behavior' => self::getDefaultCountBehavior($queryBuilder), - //Used when behavior=count_by_alias - 'alias' => null, - //Used when behavior=count_by_alias - 'distinct_alias' => true, - ]); - $resolver->setAllowedTypes('distinct_alias', ['boolean']); - if ($useORM) { - $resolver->setAllowedValues('behavior', ['count_by_alias', 'count_by_sub_request', 'orm']); - //Use only when ORM and behavior=orm - $resolver->setDefault('simplified_request', true); - } else { - $resolver->setAllowedValues('behavior', ['count_by_alias', 'count_by_sub_request']); - } - $options = $resolver->resolve($options); - if ('count_by_alias' === $options['behavior'] && null === $options['alias']) { - throw new MissingOptionsException('Option "alias" is required'); - } - - if ($useORM) { - if ('orm' === $options['behavior']) { - $cloneQueryBuilder = clone $queryBuilder; - $doctrinePaginator = new Paginator($cloneQueryBuilder->getQuery()); - $doctrinePaginator->setUseOutputWalkers(!$options['simplified_request']); - - return (int) $doctrinePaginator->count(); - } elseif ('count_by_alias' === $options['behavior']) { - /** @var \Doctrine\ORM\QueryBuilder $countQueryBuilder */ - $countQueryBuilder = clone $queryBuilder; - $distinct = ($options['distinct_alias']) ? 'DISTINCT ' : ''; - $countQueryBuilder->select(sprintf('count(%s%s)', $distinct, $options['alias'])); - $countQueryBuilder->resetDQLPart('orderBy'); - - return (int) $countQueryBuilder->getQuery()->getSingleScalarResult(); - } elseif ('count_by_sub_request' === $options['behavior']) { - /** @var \Doctrine\ORM\QueryBuilder $cloneQueryBuilder */ - $cloneQueryBuilder = clone $queryBuilder; - $cloneQueryBuilder->resetDQLPart('orderBy'); - $rsm = new ResultSetMapping(); - $rsm->addScalarResult('cnt', 'cnt'); - $countSql = sprintf('SELECT count(*) as cnt FROM (%s) mainquery', $cloneQueryBuilder->getQuery()->getSQL()); - /** @var NativeQuery $countQuery */ - $countQuery = $queryBuilder->getEntityManager()->createNativeQuery($countSql, $rsm); - $i = 0; - /** @var Parameter $parameter */ - foreach ($queryBuilder->getParameters() as $parameter) { - ++$i; - $countQuery->setParameter($i, $parameter->getValue(), $parameter->getType()); - } - - return (int) $countQuery->getSingleScalarResult(); - } - } else { - if ('count_by_alias' === $options['behavior']) { - /** @var \Doctrine\DBAL\Query\QueryBuilder $countQueryBuilder */ - $countQueryBuilder = clone $queryBuilder; - $distinct = ($options['distinct_alias']) ? 'DISTINCT ' : ''; - $countQueryBuilder->select(sprintf('count(%s%s)', $distinct, $options['alias'])); - $countQueryBuilder->resetQueryPart('orderBy'); - - return (int) $countQueryBuilder->execute()->fetchColumn(0); - } elseif ('count_by_sub_request' === $options['behavior']) { - $queryBuilderCount = clone $queryBuilder; - $queryBuilderClone = clone $queryBuilder; - - $queryBuilderClone->resetQueryPart('orderBy'); //Disable sort (> performance) - - $queryBuilderCount->resetQueryParts(); //Remove Query Parts - $queryBuilderCount->select('count(*)') - ->from('('.$queryBuilderClone->getSql().')', 'mainquery'); - - return (int) $queryBuilderCount->execute()->fetchColumn(0); - } - } - } - - /** - * @param \Doctrine\ORM\QueryBuilde|\Doctrine\DBAL\Query\QueryBuilder $queryBuilder - * - * @return string - */ - public static function getDefaultCountBehavior($queryBuilder) - { - if ($queryBuilder instanceof \Doctrine\ORM\QueryBuilder) { - return 'orm'; - } elseif ($queryBuilder instanceof \Doctrine\DBAL\Query\QueryBuilder) { - return 'count_by_sub_request'; - } - - return null; - } - - /** - * @param \Doctrine\ORM\QueryBuilde|\Doctrine\DBAL\Query\QueryBuilder $queryBuilder - * @param int $page Page number to display - * @param int $perPage Results per page - * - * @throws \Exception - * - * @return AbstractPaginator - */ - public static function createDoctrinePaginator($queryBuilder, $page, $perPage, array $options = []) - { - if ($queryBuilder instanceof \Doctrine\ORM\QueryBuilder) { - $useORM = true; - } elseif ($queryBuilder instanceof \Doctrine\DBAL\Query\QueryBuilder) { - $useORM = false; - } else { - throw new \Exception('Bad QueryBuilder'); - } - - $resolver = new OptionsResolver(); - $resolver->setDefaults([ - //Behavior for create paginator. Availabled values : - // - doctrine_paginator: Return DoctrineORMPaginator or DoctrineDBALPaginator object - // - identifier_by_sub_request: Primary keys are found by sub request. Return ArrayPaginator. Option "identifier" is required - 'behavior' => 'doctrine_paginator', - //Manual value for the number of results - 'count_manual_value' => null, - //Count options. See countQueryBuilder. Used only if count_manual_value = null - 'count_options' => [], - //Identifier used when behavior=identifier_by_sub_request - 'identifier' => null, - //Used only when ORM and behavior=doctrine_paginator - 'simplified_request' => true, - 'fetch_join_collection' => false, - ]); - $resolver->setAllowedValues('behavior', ['doctrine_paginator', 'identifier_by_sub_request']); - $options = $resolver->resolve($options); - - if ('identifier_by_sub_request' === $options['behavior']) { - if (null === $options['identifier']) { - throw new MissingOptionsException('Option "identifier" is required'); - } - } - - if ('doctrine_paginator' === $options['behavior']) { - if ($useORM) { - $paginator = new DoctrineORMPaginator($perPage); - $paginator->setSimplifiedRequest($options['simplified_request']); - $paginator->setFetchJoinCollection($options['fetch_join_collection']); - } else { - $paginator = new DoctrineDBALPaginator($perPage); - } - $paginator->setQueryBuilder($queryBuilder); - $paginator->setPage($page); - if (null === $options['count_manual_value']) { - $paginator->setCountOptions($options['count_options']); - } else { - $paginator->setManualCountResults($options['count_manual_value']); - } - - return $paginator; - } elseif ('identifier_by_sub_request' === $options['behavior']) { - $result = []; - - if (null === $options['count_manual_value']) { - $countResults = self::countQueryBuilder($queryBuilder, $options['count_options']); - } else { - $countResults = $options['count_manual_value']; - } - - if ($countResults) { - $idsQueryBuilder = clone $queryBuilder; - $idsQueryBuilder->select(sprintf('DISTINCT %s as pk', $options['identifier'])); - - if ($useORM) { - $tmpPaginator = new DoctrineORMPaginator($perPage); - $tmpPaginator->setSimplifiedRequest(false); - $tmpPaginator->setFetchJoinCollection(false); - } else { - $tmpPaginator = new DoctrineDBALPaginator($perPage); - } - $tmpPaginator->setQueryBuilder($idsQueryBuilder); - $tmpPaginator->setPage($page); - $tmpPaginator->setManualCountResults($countResults); - $tmpPaginator->init(); - - $ids = []; - foreach ($tmpPaginator->getResults() as $line) { - $ids[] = $line['pk']; - } - - $finalQueryBuilder = clone $queryBuilder; - if ($useORM) { - $finalQueryBuilder->resetDQLPart('where'); - $finalQueryBuilder->setParameters([]); - QueryBuilderFilter::addMultiFilter($finalQueryBuilder, QueryBuilderFilter::SELECT_IN, $ids, $options['identifier'], 'paginate_pks'); - $result = $finalQueryBuilder->getQuery()->getResult(); - } else { - $finalQueryBuilder->resetQueryPart('where'); - $finalQueryBuilder->setParameters([]); - QueryBuilderFilter::addMultiFilter($finalQueryBuilder, QueryBuilderFilter::SELECT_IN, $ids, $options['identifier'], 'paginate_pks'); - $result = $finalQueryBuilder->execute()->fetchAll(); - } - } - - $paginator = new ArrayPaginator($perPage); - $paginator->setPage($page); - $paginator->setDataWithoutSlice($result, $countResults); - - return $paginator; - } - } -} diff --git a/src/DoctrineExtension/QueryBuilderFilter.php b/src/DoctrineExtension/QueryBuilderFilter.php deleted file mode 100644 index 02bb53a..0000000 --- a/src/DoctrineExtension/QueryBuilderFilter.php +++ /dev/null @@ -1,246 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Ecommit\CrudBundle\DoctrineExtension; - -class QueryBuilderFilter -{ - public const SELECT_IN = 'IN'; //WHERE IN - public const SELECT_NOT_IN = 'NIN'; //WHERE NOT IN - public const SELECT_ALL = 'ALL'; //No Filter (all values) - public const SELECT_AUTO = 'AUT'; //WHERE IN. If filter values are empty, no filter (all values) - public const SELECT_NO = 'NO'; //Must return no result - - /** - * Add SQL WHERE IN or WHERE NOT IN filter. - * - * @param \Doctrine\DBAL\Query\QueryBuilder|\Doctrine\ORM\QueryBuilder $queryBuilder - * @param string $filterSign ALL (no filter), IN (WHERE IN), NIN (WHERE NOT IN), AUT (WHERE IN if $filterValues is not empty. No filter else), NO (no result) - * @param array $filterValues Values - * @param string $sqlField SQL field name - * @param string $paramName SQL parameter name - * - * @return \Doctrine\DBAL\Query\QueryBuilder|\Doctrine\ORM\QueryBuilder - */ - public static function addMultiFilter($queryBuilder, $filterSign, $filterValues, $sqlField, $paramName) - { - if (self::SELECT_NO == $filterSign) { - //Must return no result - $queryBuilder->andWhere('0 = 1'); - - return $queryBuilder; - } - if (self::SELECT_IN != $filterSign && self::SELECT_NOT_IN != $filterSign && self::SELECT_AUTO != $filterSign) { - return $queryBuilder; - } - if (null === $filterValues || 0 === \count($filterValues)) { - if (self::SELECT_NOT_IN == $filterSign || self::SELECT_AUTO == $filterSign) { - return $queryBuilder; - } - - //Must return no result - $queryBuilder->andWhere('0 = 1'); - - return $queryBuilder; - } - - if (\count($filterValues) > 1000) { - return self::addGroupMultiFilter($queryBuilder, $filterSign, $filterValues, $sqlField, $paramName); - } - - return self::addSimpleMultiFilter($queryBuilder, $filterSign, $filterValues, $sqlField, $paramName); - } - - /** - * Add SQL WHERE IN or WHERE NOT IN filter without group. - * - * @param \Doctrine\DBAL\Query\QueryBuilder|\Doctrine\ORM\QueryBuilder $queryBuilder - * @param string $filterSign ALL (no filter), IN (WHERE IN), NIN (WHERE NOT IN), AUT (WHERE IN if $filterValues is not empty. No filter else), NO (no result) - * @param array $filterValues Values - * @param string $sqlField SQL field name - * @param string $paramName SQL parameter name - * - * @return \Doctrine\DBAL\Query\QueryBuilder|\Doctrine\ORM\QueryBuilder - */ - protected static function addSimpleMultiFilter($queryBuilder, $filterSign, $filterValues, $sqlField, $paramName) - { - $clauseSql = (self::SELECT_IN == $filterSign || self::SELECT_AUTO == $filterSign) ? 'IN' : 'NOT IN'; - - $queryBuilder->andWhere(sprintf('%s %s (:%s)', $sqlField, $clauseSql, $paramName)); - $queryBuilder->setParameter($paramName, $filterValues, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY); - - return $queryBuilder; - } - - /** - * Add SQL WHERE IN or WHERE NOT IN filter with group. - * - * @param \Doctrine\DBAL\Query\QueryBuilder|\Doctrine\ORM\QueryBuilder $queryBuilder - * @param string $filterSign ALL (no filter), IN (WHERE IN), NIN (WHERE NOT IN), AUT (WHERE IN if $filterValues is not empty. No filter else), NO (no result) - * @param array $filterValues Values - * @param string $sqlField SQL field name - * @param string $paramName SQL parameter name - * - * @return \Doctrine\DBAL\Query\QueryBuilder|\Doctrine\ORM\QueryBuilder - */ - protected static function addGroupMultiFilter($queryBuilder, $filterSign, $filterValues, $sqlField, $paramName) - { - $clauseSql = (self::SELECT_IN == $filterSign || self::SELECT_AUTO == $filterSign) ? 'IN' : 'NOT IN'; - $separatorClauseSql = (self::SELECT_IN == $filterSign || self::SELECT_AUTO == $filterSign) ? 'OR' : 'AND'; - - $groupNumber = 0; - $groups = []; - foreach (array_chunk($filterValues, 1000) as $filterValuesGroup) { - ++$groupNumber; - $groups[] = sprintf('%s %s (:%s%s)', $sqlField, $clauseSql, $paramName, $groupNumber); - $queryBuilder->setParameter($paramName.$groupNumber, $filterValuesGroup, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY); - } - - $queryBuilder->andWhere(implode(' '.$separatorClauseSql.' ', $groups)); - - return $queryBuilder; - } - - /** - * Add SQL WHERE IN or WHERE NOT IN filter. And result MUST BE in the whitelist (if $restrictSign=IN) or MUST NOT BE in the blacklist (if $restrictSign=NIN). - * - * @param \Doctrine\DBAL\Query\QueryBuilder|\Doctrine\ORM\QueryBuilder $queryBuilder - * @param string $filterSign ALL (no filter), IN (WHERE IN), NIN (WHERE NOT IN), AUT (WHERE IN if $filterValues is not empty. No filter else), NO (no result) - * @param array $filterValues Values - * @param string $sqlField SQL field name - * @param string $paramName SQL parameter name - * @param string $restrictSign IN (WHERE IN), NIN (WHERE NOT IN), AUT (WHERE IN if $restrictValues is not empty. No filter else), NO (no result) - * @param array $restrictValues Whitelist (if $restrictSign=IN or AUT) or blacklist (if $restrictSign=NIN) - * - * @return \Doctrine\DBAL\Query\QueryBuilder|\Doctrine\ORM\QueryBuilder - */ - public static function addMultiFilterWithRestrictValues($queryBuilder, $filterSign, $filterValues, $sqlField, $paramName, $restrictSign, $restrictValues) - { - if (self::SELECT_NO == $filterSign || self::SELECT_NO == $restrictSign) { - //Must return no result - $queryBuilder->andWhere('0 = 1'); - - return $queryBuilder; - } - - if (\in_array($restrictSign, [self::SELECT_IN, self::SELECT_AUTO]) && \in_array($filterSign, [self::SELECT_IN, self::SELECT_AUTO]) && \count($filterValues) > 0 && \count($restrictValues) > 0) { - //We can simplify the query - - //Data cleaning - $cleanValues = []; - foreach ($filterValues as $value) { - if (\in_array($value, $restrictValues)) { - $cleanValues[] = $value; - } - } - - $queryBuilder = self::addMultiFilter($queryBuilder, self::SELECT_IN, $cleanValues, $sqlField, $paramName); - } elseif (self::SELECT_NOT_IN === $restrictSign && self::SELECT_NOT_IN === $filterSign && \count($filterValues) > 0 && \count($restrictValues) > 0) { - //We can simplify the query - - //Data fusion - $cleanValues = $restrictValues; - foreach ($filterValues as $value) { - if (!\in_array($value, $restrictValues)) { - $cleanValues[] = $value; - } - } - - $queryBuilder = self::addMultiFilter($queryBuilder, self::SELECT_NOT_IN, $cleanValues, $sqlField, $paramName); - } else { - //Two filters - self::addMultiFilter($queryBuilder, $filterSign, $filterValues, $sqlField, $paramName); - self::addMultiFilter($queryBuilder, $restrictSign, $restrictValues, $sqlField, $paramName.'Restrict'); - } - - return $queryBuilder; - } - - /** - * Add SQL "equal" or "not equal" filter. - * - * @param \Doctrine\DBAL\Query\QueryBuilder|\Doctrine\ORM\QueryBuilder $queryBuilder - * @param bool $equal Equal or not - * @param string $filterValue Value - * @param string $sqlField SQL field name - * @param string $paramName SQL parameter name - * - * @return \Doctrine\DBAL\Query\QueryBuilder|\Doctrine\ORM\QueryBuilder - */ - public static function addEqualFilter($queryBuilder, $equal, $filterValue, $sqlField, $paramName) - { - if (null === $filterValue || '' === $filterValue) { - return $queryBuilder; - } - - if ($equal) { - $queryBuilder->andWhere($sqlField.' = :'.$paramName); - } else { - $queryBuilder->andWhere($sqlField.' != :'.$paramName); - } - $queryBuilder->setParameter($paramName, $filterValue); - - return $queryBuilder; - } - - /** - * Add SQL comparator filter. - * - * @param \Doctrine\DBAL\Query\QueryBuilder|\Doctrine\ORM\QueryBuilder $queryBuilder - * @param string $sign Comparator sign (< > <= >=) - * @param string $filterValue Value - * @param string $sqlField SQL field name - * @param string $paramName SQL parameter name - * - * @return \Doctrine\DBAL\Query\QueryBuilder|\Doctrine\ORM\QueryBuilder - */ - public static function addComparatorFilter($queryBuilder, $sign, $filterValue, $sqlField, $paramName) - { - if (null === $filterValue || '' === $filterValue) { - return $queryBuilder; - } - - $queryBuilder->andWhere(sprintf('%s %s :%s', $sqlField, $sign, $paramName)); - $queryBuilder->setParameter($paramName, $filterValue); - - return $queryBuilder; - } - - /** - * Add SQL "LIKE" or "NOT LIKE" filter. - * - * @param \Doctrine\DBAL\Query\QueryBuilder|\Doctrine\ORM\QueryBuilder $queryBuilder - * @param bool $contain Contain or not - * @param string $filterValue Value - * @param string $sqlField SQL field name - * @param string $paramName SQL parameter name - * - * @return \Doctrine\DBAL\Query\QueryBuilder|\Doctrine\ORM\QueryBuilder - */ - public static function addContainFilter($queryBuilder, $contain, $filterValue, $sqlField, $paramName) - { - if (null === $filterValue || '' === $filterValue) { - return $queryBuilder; - } - - $filterValue = addcslashes($filterValue, '%_'); - if ($contain) { - $queryBuilder->andWhere($queryBuilder->expr()->like($sqlField, ':'.$paramName)); - } else { - $queryBuilder->andWhere($queryBuilder->expr()->notLike($sqlField, ':'.$paramName)); - } - $queryBuilder->setParameter($paramName, '%'.$filterValue.'%'); - - return $queryBuilder; - } -} diff --git a/src/Form/Filter/CollectionFilterTrait.php b/src/Form/Filter/CollectionFilterTrait.php index 45e87bf..4359e91 100644 --- a/src/Form/Filter/CollectionFilterTrait.php +++ b/src/Form/Filter/CollectionFilterTrait.php @@ -13,7 +13,7 @@ namespace Ecommit\CrudBundle\Form\Filter; -use Ecommit\CrudBundle\DoctrineExtension\QueryBuilderFilter; +use Ecommit\DoctrineUtils\QueryBuilderFilter; use Ecommit\ScalarValues\ScalarValues; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Validator\Constraints as Assert; diff --git a/src/Paginator/AbstractDoctrinePaginator.php b/src/Paginator/AbstractDoctrinePaginator.php deleted file mode 100644 index 93c0be2..0000000 --- a/src/Paginator/AbstractDoctrinePaginator.php +++ /dev/null @@ -1,121 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Ecommit\CrudBundle\Paginator; - -abstract class AbstractDoctrinePaginator extends AbstractPaginator -{ - protected $query = null; - protected $manualCountResults = null; - protected $countOptions = []; - - /** - * @return string - */ - abstract protected function getQueryBuilderClass(); - - abstract protected function initPaginator(); - - public function init(): void - { - if (null === $this->query) { - throw new \Exception('QueryBuilder must be defined.'); - } - - $this->initPaginator(); - - $this->initLastPage(); - $this->query->setFirstResult(0); - $this->query->setMaxResults(0); - - if ($this->getCountResults() > 0) { - $offset = ($this->getPage() - 1) * $this->getMaxPerPage(); - $this->query->setFirstResult($offset); - $this->query->setMaxResults($this->getMaxPerPage()); - } - } - - /** - * Returns QueryBuilder. - * - * @return mixed - */ - public function getQueryBuilder() - { - return $this->query; - } - - /** - * Sets the QueryBuilder. - * - * @param mixed $query - * - * @return AbstractDoctrinePaginator - */ - public function setQueryBuilder($query) - { - $queryBuilderClass = $this->getQueryBuilderClass(); - if (!($query instanceof $queryBuilderClass)) { - throw new \Exception('QueryBuilder must be an instance of '.$queryBuilderClass); - } - $this->query = $query; - - return $this; - } - - /** - * Returns manual total results. - * - * @return int - */ - public function getManualCountResults() - { - return $this->manualCountResults; - } - - /** - * Sets manual total results. - * - * @param int $manualCountResults - * - * @return AbstractDoctrinePaginator - */ - public function setManualCountResults($manualCountResults) - { - $this->manualCountResults = $manualCountResults; - - return $this; - } - - /** - * Returns count options. - * - * @return array - */ - public function getCountOptions() - { - return $this->countOptions; - } - - /** - * Sets count options. - * - * @return AbstractDoctrinePaginator - */ - public function setCountOptions(array $countOptions) - { - $this->countOptions = $countOptions; - - return $this; - } -} diff --git a/src/Paginator/AbstractPaginator.php b/src/Paginator/AbstractPaginator.php deleted file mode 100644 index 4fcc17d..0000000 --- a/src/Paginator/AbstractPaginator.php +++ /dev/null @@ -1,268 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Ecommit\CrudBundle\Paginator; - -abstract class AbstractPaginator implements \IteratorAggregate, \Countable -{ - protected $page = 1; - protected $maxPerPage = 1; - protected $lastPage = 1; - protected $countResults = 0; - protected $results = null; - - /** - * Constructor. - * - * @param string $class The model class - * @param int $maxPerPage Number of records to display per page - * - * @return AbstractPaginator - */ - public function __construct($maxPerPage = 10) - { - $this->setMaxPerPage($maxPerPage); - - return $this; - } - - /** - * Initializes the pager. - */ - abstract public function init(); - - /** - * Returns an array of results on the given page. - * - * @return \ArrayIterator - */ - abstract public function getResults(); - - /** - * Returns true if the current query requires pagination. - * - * @return bool - */ - public function haveToPaginate() - { - return $this->getCountResults() > $this->getMaxPerPage(); - } - - /** - * Returns the first index on the current page. - * - * @return int - */ - public function getFirstIndice() - { - if (0 == $this->getCountResults()) { - return 0; - } - - return ($this->page - 1) * $this->maxPerPage + 1; - } - - /** - * Returns the last index on the current page. - * - * @return int - */ - public function getLastIndice() - { - if ($this->page * $this->maxPerPage >= $this->countResults) { - return $this->countResults; - } - - return $this->page * $this->maxPerPage; - } - - /** - * Returns the number of results. - * - * @return int - */ - public function getCountResults() - { - return $this->countResults; - } - - /** - * Sets the number of results. - * - * @param int $nb - */ - protected function setCountResults($nb): void - { - $this->countResults = $nb; - } - - /** - * Returns the first page number. - * - * @return int - */ - public function getFirstPage() - { - return 1; - } - - /** - * Returns the last page number. - * - * @return int - */ - public function getLastPage() - { - return $this->lastPage; - } - - /** - * Init the last page number. - */ - protected function initLastPage(): void - { - if ($this->getCountResults() > 0) { - $lastPage = (int) ceil($this->getCountResults() / $this->getMaxPerPage()); - } else { - $lastPage = 1; - } - $this->lastPage = $lastPage; - - if ($this->getPage() > $lastPage) { - $this->setPage($lastPage); - } - } - - /** - * Returns the current page. - * - * @return int - */ - public function getPage() - { - return $this->page; - } - - /** - * Returns the next page. - * - * @return int - */ - public function getNextPage() - { - return min($this->getPage() + 1, $this->getLastPage()); - } - - /** - * Returns the previous page. - * - * @return int - */ - public function getPreviousPage() - { - return max($this->getPage() - 1, $this->getFirstPage()); - } - - /** - * Sets the current page. - * - * @param int $page - * - * @return AbstractPaginator - */ - public function setPage($page) - { - $this->page = (int) $page; - - if ($this->page <= 0) { - $this->page = 1; - } - - return $this; - } - - /** - * Returns the maximum number of results per page. - * - * @return int - */ - public function getMaxPerPage() - { - return $this->maxPerPage; - } - - /** - * Sets the maximum number of results per page. - * - * @param int $max - * - * @return AbstractPaginator - */ - public function setMaxPerPage($max) - { - $max = (int) $max; - - if ($max <= 0) { - throw new \Exception('Max results value must be positive'); - } - $this->maxPerPage = $max; - - return $this; - } - - /** - * Returns true if on the first page. - * - * @return bool - */ - public function isFirstPage() - { - return 1 == $this->page; - } - - /** - * Returns true if on the last page. - * - * @return bool - */ - public function isLastPage() - { - return $this->page == $this->lastPage; - } - - /** - * Returns true if the properties used for iteration have been initialized. - * - * @return bool - */ - protected function isIteratorInitialized() - { - return null !== $this->results; - } - - /** - * {@inheritdoc} - */ - public function count() - { - return $this->getCountResults(); - } - - /** - * {@inheritdoc} - */ - public function getIterator() - { - return $this->getResults(); - } -} diff --git a/src/Paginator/ArrayPaginator.php b/src/Paginator/ArrayPaginator.php deleted file mode 100644 index 2ad72c4..0000000 --- a/src/Paginator/ArrayPaginator.php +++ /dev/null @@ -1,92 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Ecommit\CrudBundle\Paginator; - -class ArrayPaginator extends AbstractPaginator -{ - protected $initialObjects = null; - protected $manualCountResults = null; - - /** - * {@inheritdoc} - */ - public function init(): void - { - if (null === $this->initialObjects) { - throw new \Exception('Results are required'); - } - - if (null === $this->manualCountResults) { - $this->setCountResults(\count($this->initialObjects)); - $this->initLastPage(); - - $offset = 0; - $limit = 0; - - if ($this->getCountResults() > 0) { - $offset = ($this->getPage() - 1) * $this->getMaxPerPage(); - $limit = $this->getMaxPerPage(); - } - - $this->results = \array_slice($this->initialObjects, $offset, $limit); - } else { - $this->setCountResults($this->manualCountResults); - $this->initLastPage(); - - $this->results = $this->initialObjects; - } - } - - /** - * Set an array of results. - * - * @param array|ArrayIterator $results - * - * @return ArrayPaginator - */ - public function setData($results) - { - if ($results instanceof \ArrayIterator) { - $this->initialObjects = $results->getArrayCopy(); - } elseif (\is_array($results)) { - $this->initialObjects = $results; - } else { - throw new \Exception('Results must be an array'); - } - - return $this; - } - - /** - * Set an array of results without slice. - * - * @param array|ArrayIterator $results - * @param int $manualCountResults - */ - public function setDataWithoutSlice($results, $manualCountResults) - { - $this->setData($results); - $this->manualCountResults = $manualCountResults; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function getResults() - { - return new \ArrayIterator($this->results); - } -} diff --git a/src/Paginator/DoctrineDBALPaginator.php b/src/Paginator/DoctrineDBALPaginator.php deleted file mode 100644 index c8e30db..0000000 --- a/src/Paginator/DoctrineDBALPaginator.php +++ /dev/null @@ -1,54 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Ecommit\CrudBundle\Paginator; - -use Ecommit\CrudBundle\DoctrineExtension\Paginate; - -class DoctrineDBALPaginator extends AbstractDoctrinePaginator -{ - /** - * {@inheritdoc} - */ - protected function getQueryBuilderClass() - { - return 'Doctrine\DBAL\Query\QueryBuilder'; - } - - /** - * {@inheritdoc} - */ - public function initPaginator(): void - { - //Calculation of the number of lines - if (null === $this->manualCountResults) { - $count = Paginate::countQueryBuilder($this->query, $this->countOptions); - $this->setCountResults($count); - } else { - $this->setCountResults($this->manualCountResults); - } - } - - /** - * {@inheritdoc} - */ - public function getResults() - { - if (null === $this->results) { - $results = $this->query->execute()->fetchAll(); - $this->results = new \ArrayIterator($results); - } - - return $this->results; - } -} diff --git a/src/Paginator/DoctrineORMPaginator.php b/src/Paginator/DoctrineORMPaginator.php deleted file mode 100644 index 312da9f..0000000 --- a/src/Paginator/DoctrineORMPaginator.php +++ /dev/null @@ -1,113 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Ecommit\CrudBundle\Paginator; - -use Doctrine\ORM\Tools\Pagination\Paginator; -use Ecommit\CrudBundle\DoctrineExtension\Paginate; - -class DoctrineORMPaginator extends AbstractDoctrinePaginator -{ - protected $simplifiedRequest = true; - protected $fetchJoinCollection = false; - - /** - * {@inheritdoc} - */ - protected function getQueryBuilderClass() - { - return 'Doctrine\ORM\QueryBuilder'; - } - - /** - * {@inheritdoc} - */ - public function initPaginator(): void - { - //Calculation of the number of lines - if (null === $this->manualCountResults) { - $countOptions = $this->countOptions; - if (!isset($countOptions['behavior'])) { - $countOptions['behavior'] = Paginate::getDefaultCountBehavior($this->query); - } - if ('orm' === $countOptions['behavior'] && !isset($countOptions['simplified_request'])) { - $countOptions['simplified_request'] = $this->simplifiedRequest; - } - - $count = Paginate::countQueryBuilder($this->query, $countOptions); - $this->setCountResults($count); - } else { - $this->setCountResults($this->manualCountResults); - } - } - - /** - * @return bool - */ - public function isSimplifiedRequest() - { - return $this->simplifiedRequest; - } - - /** - * Use simplified request (not subrequest and not order by) or not when count results. - * - * @param bool $simplifiedRequest - * - * @return DoctrineORMPaginator - */ - public function setSimplifiedRequest($simplifiedRequest) - { - $this->simplifiedRequest = $simplifiedRequest; - - return $this; - } - - /** - * @return bool - */ - public function isFetchJoinCollection() - { - return $this->fetchJoinCollection; - } - - /** - * Set to true when fetch join a to-many collection - * In that case 3 instead of the 2 queries described are executed. - * - * @param bool $fetchJoinCollection - * - * @return DoctrineORMPaginator - */ - public function setFetchJoinCollection($fetchJoinCollection) - { - $this->fetchJoinCollection = $fetchJoinCollection; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function getResults() - { - if (null === $this->results) { - $query = $this->query->getQuery(); - $paginator = new Paginator($query, $this->fetchJoinCollection); - $paginator->setUseOutputWalkers(!$this->simplifiedRequest); - $this->results = $paginator->getIterator(); - } - - return $this->results; - } -} diff --git a/src/Twig/CrudExtension.php b/src/Twig/CrudExtension.php index 7e96da4..9f10065 100644 --- a/src/Twig/CrudExtension.php +++ b/src/Twig/CrudExtension.php @@ -14,7 +14,7 @@ namespace Ecommit\CrudBundle\Twig; use Ecommit\CrudBundle\Crud\Crud; -use Ecommit\CrudBundle\Paginator\AbstractPaginator; +use Ecommit\Paginator\PaginatorInterface; use Symfony\Component\Form\FormRendererInterface; use Symfony\Component\Form\FormView; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -158,7 +158,7 @@ public function getFunctions() * * theme: Theme used. If null, default theme is used * * block: Twig block used. Default: paginator_links */ - public function paginatorLinks(Environment $environment, AbstractPaginator $paginator, string $routeName, array $routeParams = [], array $options = []): string + public function paginatorLinks(Environment $environment, PaginatorInterface $paginator, string $routeName, array $routeParams = [], array $options = []): string { $resolver = new OptionsResolver(); $resolver->setDefaults([ diff --git a/tests/Twig/CrudExtensionTest.php b/tests/Twig/CrudExtensionTest.php index 3b3e841..faf1e60 100644 --- a/tests/Twig/CrudExtensionTest.php +++ b/tests/Twig/CrudExtensionTest.php @@ -16,8 +16,8 @@ use Ecommit\CrudBundle\Crud\Crud; use Ecommit\CrudBundle\Crud\CrudColumn; use Ecommit\CrudBundle\Crud\CrudSession; -use Ecommit\CrudBundle\Paginator\ArrayPaginator; use Ecommit\CrudBundle\Twig\CrudExtension; +use Ecommit\Paginator\ArrayPaginator; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\Component\DomCrawler\Crawler; use Symfony\Component\Form\Extension\Core\Type\FormType; @@ -157,9 +157,10 @@ public function testAjaxAttributesWithBadAjaxOptions(): void public function testPaginatorLinksWithNoResult(): void { - $paginator = new ArrayPaginator(5); - $paginator->setData([]); - $paginator->init(); + $paginator = new ArrayPaginator([ + 'max_per_page' => 5, + 'data' => [], + ]); $result = $this->crudExtension->paginatorLinks($this->environment, $paginator, 'user_crud'); @@ -168,9 +169,10 @@ public function testPaginatorLinksWithNoResult(): void public function testPaginatorLinksWithOnePage(): void { - $paginator = new ArrayPaginator(5); - $paginator->setData(['val']); - $paginator->init(); + $paginator = new ArrayPaginator([ + 'max_per_page' => 5, + 'data' => ['val'], + ]); $result = $this->crudExtension->paginatorLinks($this->environment, $paginator, 'user_crud'); @@ -182,10 +184,11 @@ public function testPaginatorLinksWithOnePage(): void */ public function testPaginatorWithDefaultOptions(int $page, string $expected): void { - $paginator = new ArrayPaginator(5); - $paginator->setData(range(1, 100)); - $paginator->setPage($page); - $paginator->init(); + $paginator = new ArrayPaginator([ + 'max_per_page' => 5, + 'data' => range(1, 100), + 'page' => $page, + ]); $result = $this->crudExtension->paginatorLinks($this->environment, $paginator, 'user_crud'); @@ -208,10 +211,11 @@ public function getTestPaginatorWithDefaultOptionsProvider(): array */ public function testPaginatorWithMaxPagesOptions(int $page, string $expected): void { - $paginator = new ArrayPaginator(5); - $paginator->setData(range(1, 100)); - $paginator->setPage($page); - $paginator->init(); + $paginator = new ArrayPaginator([ + 'max_per_page' => 5, + 'data' => range(1, 100), + 'page' => $page, + ]); $result = $this->crudExtension->paginatorLinks($this->environment, $paginator, 'user_crud', [], [ 'max_pages_before' => 4, @@ -237,10 +241,11 @@ public function getTestPaginatorWithMaxPagesOptionsProvider(): array */ public function testPaginatorWithTypeOption(int $page, string $type, string $expected): void { - $paginator = new ArrayPaginator(5); - $paginator->setData(range(1, 100)); - $paginator->setPage($page); - $paginator->init(); + $paginator = new ArrayPaginator([ + 'max_per_page' => 5, + 'data' => range(1, 100), + 'page' => $page, + ]); $result = $this->crudExtension->paginatorLinks($this->environment, $paginator, 'user_crud', [], [ 'type' => $type, @@ -278,10 +283,11 @@ public function getTestPaginatorWithTypeOptionProvider(): array public function testPaginatorWithEmptyArrayAjaxOption(): void { - $paginator = new ArrayPaginator(5); - $paginator->setData(range(1, 100)); - $paginator->setPage(1); - $paginator->init(); + $paginator = new ArrayPaginator([ + 'max_per_page' => 5, + 'data' => range(1, 100), + 'page' => 1, + ]); $expected = ''; $result = $this->crudExtension->paginatorLinks($this->environment, $paginator, 'user_crud', [], [ @@ -295,10 +301,11 @@ public function testPaginatorWithEmptyArrayAjaxOption(): void public function testPaginatorWithAjaxOption(): void { - $paginator = new ArrayPaginator(5); - $paginator->setData(range(1, 100)); - $paginator->setPage(1); - $paginator->init(); + $paginator = new ArrayPaginator([ + 'max_per_page' => 5, + 'data' => range(1, 100), + 'page' => 1, + ]); $expected = ''; $result = $this->crudExtension->paginatorLinks($this->environment, $paginator, 'user_crud', [], [ @@ -314,10 +321,11 @@ public function testPaginatorWithAjaxOption(): void public function testPaginatorWithAttributePageOption(): void { - $paginator = new ArrayPaginator(5); - $paginator->setData(range(1, 100)); - $paginator->setPage(1); - $paginator->init(); + $paginator = new ArrayPaginator([ + 'max_per_page' => 5, + 'data' => range(1, 100), + 'page' => 1, + ]); $expected = ''; $result = $this->crudExtension->paginatorLinks($this->environment, $paginator, 'user_crud', [], [ @@ -331,10 +339,11 @@ public function testPaginatorWithAttributePageOption(): void public function testPaginatorWithAttrOptions(): void { - $paginator = new ArrayPaginator(5); - $paginator->setData(range(1, 100)); - $paginator->setPage(10); - $paginator->init(); + $paginator = new ArrayPaginator([ + 'max_per_page' => 5, + 'data' => range(1, 100), + 'page' => 10, + ]); $expected = ''; $result = $this->crudExtension->paginatorLinks($this->environment, $paginator, 'user_crud', [], [ @@ -365,10 +374,11 @@ public function testPaginatorWithAttrOptions(): void public function testPaginatorWithRenderOption(): void { - $paginator = new ArrayPaginator(5); - $paginator->setData(range(1, 100)); - $paginator->setPage(1); - $paginator->init(); + $paginator = new ArrayPaginator([ + 'max_per_page' => 5, + 'data' => range(1, 100), + 'page' => 1, + ]); $result = $this->crudExtension->paginatorLinks($this->environment, $paginator, 'user_crud', [], [ 'render' => 'render.html.twig', @@ -379,10 +389,11 @@ public function testPaginatorWithRenderOption(): void public function testPaginatorWithThemeAndBlockOptions(): void { - $paginator = new ArrayPaginator(5); - $paginator->setData(range(1, 100)); - $paginator->setPage(1); - $paginator->init(); + $paginator = new ArrayPaginator([ + 'max_per_page' => 5, + 'data' => range(1, 100), + 'page' => 1, + ]); $result = $this->crudExtension->paginatorLinks($this->environment, $paginator, 'user_crud', [], [ 'theme' => 'theme.html.twig', @@ -395,10 +406,11 @@ public function testPaginatorWithThemeAndBlockOptions(): void public function testPaginatorWithBadOptions(): void { $this->expectException(UndefinedOptionsException::class); - $paginator = new ArrayPaginator(5); - $paginator->setData(range(1, 100)); - $paginator->setPage(1); - $paginator->init(); + $paginator = new ArrayPaginator([ + 'max_per_page' => 5, + 'data' => range(1, 100), + 'page' => 1, + ]); $this->crudExtension->paginatorLinks($this->environment, $paginator, 'user_crud', [], [ 'bad_option' => 'bad', @@ -407,10 +419,11 @@ public function testPaginatorWithBadOptions(): void public function testCrudPaginatorLinks(): void { - $paginator = new ArrayPaginator(5); - $paginator->setData(range(1, 100)); - $paginator->setPage(1); - $paginator->init(); + $paginator = new ArrayPaginator([ + 'max_per_page' => 5, + 'data' => range(1, 100), + 'page' => 1, + ]); $crud = $this->getMockBuilder(Crud::class) ->disableOriginalConstructor() From 15f7e2cdb651ab06a950f74c9f22ec316910239b Mon Sep 17 00:00:00 2001 From: hlecorche Date: Tue, 28 Sep 2021 17:13:28 +0200 Subject: [PATCH 084/264] [SearchFormBuilder] Fix validation_groups --- src/Crud/SearchFormBuilder.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Crud/SearchFormBuilder.php b/src/Crud/SearchFormBuilder.php index 7f789fd..eee984d 100644 --- a/src/Crud/SearchFormBuilder.php +++ b/src/Crud/SearchFormBuilder.php @@ -69,11 +69,16 @@ protected function createFormBuilder(?string $type): void { $formFactory = $this->container->get('form.factory'); + $formOptions = $this->options['form_options']; + if (!isset($formOptions['validation_groups']) && null !== $this->options['validation_groups']) { + $formOptions['validation_groups'] = $this->options['validation_groups']; + } + if ($type) { - $this->form = $formFactory->createBuilder($type, null, $this->options['form_options']); + $this->form = $formFactory->createBuilder($type, null, $formOptions); } else { $formName = sprintf('crud_search_%s', $this->crud->getSessionName()); - $this->form = $formFactory->createNamedBuilder($formName, FormSearchType::class, null, $this->options['form_options']); + $this->form = $formFactory->createNamedBuilder($formName, FormSearchType::class, null, $formOptions); } $this->defaultData->buildForm($this, $this->options); From a5d09de2ad4bc1fc7893813dcdc1bae67425b470 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Tue, 28 Sep 2021 17:52:00 +0200 Subject: [PATCH 085/264] [Crud] Fix getTwigFunctionConfiguration --- src/Crud/Crud.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Crud/Crud.php b/src/Crud/Crud.php index 16f795b..b9a3112 100644 --- a/src/Crud/Crud.php +++ b/src/Crud/Crud.php @@ -938,7 +938,7 @@ public function getTwigFunctionsConfiguration(): array public function getTwigFunctionConfiguration(string $function): array { if (isset($this->twigFunctionsConfiguration[$function])) { - return $this->twigFunctionsConfiguration; + return $this->twigFunctionsConfiguration[$function]; } return []; From 039816a629de50ac025092159106efc548d1e5f0 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Tue, 28 Sep 2021 20:36:11 +0200 Subject: [PATCH 086/264] [searchFormStart] Add missing attributes --- src/Twig/CrudExtension.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Twig/CrudExtension.php b/src/Twig/CrudExtension.php index 9f10065..f8ad589 100644 --- a/src/Twig/CrudExtension.php +++ b/src/Twig/CrudExtension.php @@ -485,6 +485,7 @@ public function searchFormStart(Environment $environment, Crud $crud, array $opt 'data-crud-search-id' => $crud->getDivIdSearch(), 'data-crud-list-id' => $crud->getDivIdList(), ], + $crud->getSearchForm()->vars['attr'], $options['form_attr'], $this->getAjaxAttributes($this->validateAjaxOptions($options['ajax_options'])) ); From 81ab1e7cfacb3a90444e7629f7add04f18401cfc Mon Sep 17 00:00:00 2001 From: hlecorche Date: Wed, 29 Sep 2021 16:23:44 +0200 Subject: [PATCH 087/264] [CrudControllerTrait] Add getCrudOptions method --- src/Controller/CrudControllerTrait.php | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/Controller/CrudControllerTrait.php b/src/Controller/CrudControllerTrait.php index 6fa539c..a2d3680 100644 --- a/src/Controller/CrudControllerTrait.php +++ b/src/Controller/CrudControllerTrait.php @@ -31,22 +31,17 @@ final protected function createCrud($sessionName): Crud public function getCrudResponse(): Response { - return $this->container->get(CrudResponseGenerator::class)->getResponse($this->getCrud(), [ - 'template_generator' => function (string $action) { - return $this->getTemplateName($action); - }, - 'before_build_query' => function (Crud $crud, $data) { - return $this->beforeBuildQuery($crud, $data); - }, - 'after_build_query' => function (Crud $crud, $data) { - return $this->afterBuildQuery($crud, $data); - }, - ]); + return $this->container->get(CrudResponseGenerator::class)->getResponse($this->getCrud(), $this->getCrudOptions()); } public function getAjaxCrudResponse(): Response { - return $this->container->get(CrudResponseGenerator::class)->getAjaxResponse($this->getCrud(), [ + return $this->container->get(CrudResponseGenerator::class)->getAjaxResponse($this->getCrud(), $this->getCrudOptions()); + } + + protected function getCrudOptions(): array + { + return [ 'template_generator' => function (string $action) { return $this->getTemplateName($action); }, @@ -56,7 +51,7 @@ public function getAjaxCrudResponse(): Response 'after_build_query' => function (Crud $crud, $data) { return $this->afterBuildQuery($crud, $data); }, - ]); + ]; } protected function beforeBuildQuery(Crud $crud, array $data): array From 9d92cde724b0721728e75c118ac33dce24987d64 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Wed, 29 Sep 2021 16:25:29 +0200 Subject: [PATCH 088/264] [CrudResponseGenerator] Add getCrudData and getCrudContents methods --- src/Crud/CrudResponseGenerator.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/Crud/CrudResponseGenerator.php b/src/Crud/CrudResponseGenerator.php index aba1d7e..4b2bc7f 100644 --- a/src/Crud/CrudResponseGenerator.php +++ b/src/Crud/CrudResponseGenerator.php @@ -65,6 +65,23 @@ public function getAjaxResponse(Crud $crud, array $options = []): Response return $this->renderCrud($this->getTemplateName($options['template_generator'], 'list'), $data); } + public function getCrudData(Crud $crud, array $options = []): array + { + $options = $this->getOptions($options); + + return $this->processCrud($crud, $options); + } + + public function getCrudContents(Crud $crud, array $options = []): array + { + $data = $this->getCrudData($crud, $options); + + return [ + 'render_search' => $this->renderCrudView($this->getTemplateName($options['template_generator'], 'search'), $data), + 'render_list' => $this->renderCrudView($this->getTemplateName($options['template_generator'], 'list'), $data), + ]; + } + protected function processCrud(Crud $crud, array $options): array { $data = [ From 95a563833cee3472051ebe6bc909267232b00710 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Wed, 29 Sep 2021 16:28:00 +0200 Subject: [PATCH 089/264] Crud::inis is now automatically called --- src/Crud/Crud.php | 12 ++++++++++++ src/Crud/CrudResponseGenerator.php | 3 +++ 2 files changed, 15 insertions(+) diff --git a/src/Crud/Crud.php b/src/Crud/Crud.php index b9a3112..79f5101 100644 --- a/src/Crud/Crud.php +++ b/src/Crud/Crud.php @@ -47,6 +47,7 @@ class Crud */ protected $displaySettingsForm = null; + protected $isInitialized = false; protected $availableColumns = []; protected $availableVirtualColumns = []; protected $availableResultsPerPage = []; @@ -96,6 +97,10 @@ public function __construct($sessionName, ContainerInterface $container) */ public function init(): void { + if ($this->isInitialized) { + throw new \Exception('CRUD already initialized'); + } + //Cheks not empty values $checkValues = [ 'availableColumns', @@ -145,6 +150,13 @@ public function init(): void //Saves $this->save(); + + $this->isInitialized = true; + } + + public function isInitialized(): bool + { + return $this->isInitialized; } /** diff --git a/src/Crud/CrudResponseGenerator.php b/src/Crud/CrudResponseGenerator.php index 4b2bc7f..2df7c04 100644 --- a/src/Crud/CrudResponseGenerator.php +++ b/src/Crud/CrudResponseGenerator.php @@ -90,6 +90,9 @@ protected function processCrud(Crud $crud, array $options): array $request = $this->container->get('request_stack')->getCurrentRequest(); + if (!$crud->isInitialized()) { + $crud->init(); + } if ($request->query->has('search')) { $crud->processForm(); } From 745b6326ee93769a0f9166c3a7aebd2e323a5751 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Wed, 29 Sep 2021 16:29:35 +0200 Subject: [PATCH 090/264] Do not display results if paginator disabled --- src/Crud/Crud.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Crud/Crud.php b/src/Crud/Crud.php index 79f5101..5479b60 100644 --- a/src/Crud/Crud.php +++ b/src/Crud/Crud.php @@ -374,7 +374,7 @@ public function processForm(): void } $searchForm = $this->searchForm->getForm(); $searchForm->handleRequest($request); - if ($searchForm->isSubmitted() && $searchForm->isValid()) { + if ($searchForm->isSubmitted() && $searchForm->isValid() && false !== $this->buildPaginator) { $this->displayResults = true; $this->sessionValues->searchFormIsSubmittedAndValid = true; $this->changeFilterValues($searchForm->getData()); From 392b837396c2a8cc30b89ee4a7c8b8207050f1a2 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Wed, 29 Sep 2021 17:25:07 +0200 Subject: [PATCH 091/264] [Theme] Add dynamic labels --- src/Resources/views/Theme/base.html.twig | 18 ++++++++++++------ src/Resources/views/Theme/bootstrap3.html.twig | 18 ++++++++++++------ 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/Resources/views/Theme/base.html.twig b/src/Resources/views/Theme/base.html.twig index a727cee..0aa8481 100644 --- a/src/Resources/views/Theme/base.html.twig +++ b/src/Resources/views/Theme/base.html.twig @@ -185,9 +185,10 @@ {% endblock %} {% block display_settings_button %} + {% set display_settings_text = display_settings_text|default('display_settings.title'|trans({}, 'EcommitCrudBundle')) %} {% set button_attr = button_attr|default([])|merge({class: (button_attr.class|default('') ~ ' ec-crud-display-settings-button')|trim, 'data-display-settings': 'ec-crud-display-settings-'~crud.sessionName}) %} - {{ crud_icon('display_settings') }} {{ 'display_settings.title'|trans({}, 'EcommitCrudBundle') }} + {{ crud_icon('display_settings') }} {{ display_settings_text }} {% endblock %} @@ -210,15 +211,18 @@ {% block display_settings_content_without_modal %}
-

{{ 'display_settings.title'|trans({}, 'EcommitCrudBundle') }}

+ {% set display_settings_text = display_settings_text|default('display_settings.title'|trans({}, 'EcommitCrudBundle')) %} + {% set display_settings_check_all_text = display_settings_check_all_text|default('display_settings.check_all'|trans({}, 'EcommitCrudBundle')) %} + {% set display_settings_uncheck_all_text = display_settings_uncheck_all_text|default('display_settings.uncheck_all'|trans({}, 'EcommitCrudBundle')) %} +

{{ display_settings_text }}

{{ form_start(form) }} {{ form_row(form.resultsPerPage) }} {{ form_row(form.displayedColumns) }}
- +   - +
{{ form_row(form.reset) }} @@ -240,8 +244,9 @@ {% block search_form_submit %} {% apply spaceless %} + {% set submit_text = submit_text|default('search.submit'|trans({}, 'EcommitCrudBundle')) %} {% endapply %} {% endblock %} @@ -250,8 +255,9 @@ {% block search_form_reset %} {% apply spaceless %} + {% set reset_text = reset_text|default('search.reset'|trans({}, 'EcommitCrudBundle')) %} {% endapply %} {% endblock %} diff --git a/src/Resources/views/Theme/bootstrap3.html.twig b/src/Resources/views/Theme/bootstrap3.html.twig index 0bd32db..50aca24 100644 --- a/src/Resources/views/Theme/bootstrap3.html.twig +++ b/src/Resources/views/Theme/bootstrap3.html.twig @@ -34,10 +34,13 @@ {% block display_settings_content_modal %} {% form_theme form 'bootstrap_3_layout.html.twig' %} + {% set display_settings_text = display_settings_text|default('display_settings.title'|trans({}, 'EcommitCrudBundle')) %} + {% set display_settings_check_all_text = display_settings_check_all_text|default('display_settings.check_all'|trans({}, 'EcommitCrudBundle')) %} + {% set display_settings_uncheck_all_text = display_settings_uncheck_all_text|default('display_settings.uncheck_all'|trans({}, 'EcommitCrudBundle')) %}
- {{ form_row(form.reset, {'attr': {'class': 'btn-outline-secondary'}}) }} + {{ form_widget(form.reset, {'attr': {'class': form.reset.vars.attr.class ~ ' btn-outline-secondary'}}) }} {{ form_row(form.save, {'attr': {'class': 'btn-success'}}) }}
{{ form_end(form) }} diff --git a/templates/Theme/bootstrap5.html.twig b/templates/Theme/bootstrap5.html.twig index 9a45aa0..50a03e8 100644 --- a/templates/Theme/bootstrap5.html.twig +++ b/templates/Theme/bootstrap5.html.twig @@ -75,7 +75,7 @@
{{ form_end(form) }} @@ -101,7 +101,7 @@
- {{ form_row(form.reset, {'attr': {'class': 'btn-outline-secondary'}}) }} + {{ form_widget(form.reset, {'attr': {'class': form.reset.vars.attr.class ~ ' btn-outline-secondary'}}) }} {{ form_row(form.save, {'attr': {'class': 'btn-success'}}) }}
{{ form_end(form) }} From c9299de9f09f8d4ee0520ee7a35da4c152eece5b Mon Sep 17 00:00:00 2001 From: hlecorche Date: Tue, 26 Jul 2022 12:34:36 +0200 Subject: [PATCH 190/264] Upgrade symfony/webpack-encore 3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8ad83ae..dc027e6 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ }, "devDependencies": { "@ecommit/crud-bundle": "link:assets", - "@symfony/webpack-encore": "^1.8", + "@symfony/webpack-encore": "^3.0", "core-js": "^3.0.0", "eslint": "^8.0", "eslint-config-standard": "^16.0.3", From 8b704bb0a229a48e1df4b86d2b5fca1fb1b0182b Mon Sep 17 00:00:00 2001 From: hlecorche Date: Tue, 26 Jul 2022 23:16:50 +0200 Subject: [PATCH 191/264] Use stable releases --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 8be944a..eac0682 100644 --- a/composer.json +++ b/composer.json @@ -18,8 +18,8 @@ "doctrine/doctrine-bundle": "^2.4.5", "doctrine/orm": "^2.9", "doctrine/persistence": "^1.3|^2.0|^3.0", - "ecommit/doctrine-utils": "1.0.x-dev", - "ecommit/paginator": "1.0.x-dev", + "ecommit/doctrine-utils": "^1.0", + "ecommit/paginator": "^1.0", "ecommit/scalar-values": "^1.0", "psr/container": "^1.1|^2.0", "symfony/asset": "^5.4|^6.0", From 861a47422d89fa0c3ca764c98a9a9bb7de659991 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Fri, 29 Jul 2022 10:07:25 +0200 Subject: [PATCH 192/264] Fix update form after resetDisplaySettings --- src/Crud/Crud.php | 2 ++ tests/Functional/Controller/TestCrudControllerTest.php | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/src/Crud/Crud.php b/src/Crud/Crud.php index 1f74f7d..462cdf7 100644 --- a/src/Crud/Crud.php +++ b/src/Crud/Crud.php @@ -590,6 +590,8 @@ protected function resetDisplaySettings(): void ->getQuery() ->execute(); } + + $this->createDisplaySettingsForm(); } public function createView(): self diff --git a/tests/Functional/Controller/TestCrudControllerTest.php b/tests/Functional/Controller/TestCrudControllerTest.php index 2ef301f..8b7565f 100644 --- a/tests/Functional/Controller/TestCrudControllerTest.php +++ b/tests/Functional/Controller/TestCrudControllerTest.php @@ -269,6 +269,8 @@ public function testSessionValuesAfterResetSearch(Client $client): Client */ public function testResetSettings(Client $client): Client { + $this->assertSame(['username', 'firstName', 'lastName'], $this->getCheckedColumns($client->getCrawler())); + $button = $client->getCrawler()->filterXPath('//button[contains(., "Display Settings")]'); $button->first()->click(); @@ -281,6 +283,7 @@ public function testResetSettings(Client $client): Client $this->assertSame(['first_name', Crud::ASC], $this->getSort($client->getCrawler())); $this->assertSame('AudeJavel', $this->getFirstUsername($client->getCrawler())); $this->checkBeforeAndAfterBuild($client->getCrawler()); + $this->assertSame(['firstName', 'lastName'], $this->getCheckedColumns($client->getCrawler())); return $client; } @@ -292,6 +295,7 @@ public function testSessionValuesAfterResetSettings(Client $client): Client { $client->request('GET', static::URL); + $this->assertSame(['firstName', 'lastName'], $this->getCheckedColumns($client->getCrawler())); $this->assertSame([5, 2], $this->countRowsAndColumns($client->getCrawler())); $this->assertSame([1, 3], $this->getPagination($client->getCrawler())); $this->assertSame(['first_name', Crud::ASC], $this->getSort($client->getCrawler())); @@ -381,4 +385,9 @@ protected function checkBeforeAndAfterBuild(Crawler $crawler): void $this->assertCount(1, $crawler->filterXPath('//div[contains(text(), "TEST BEFORE AFTER BUILD '.static::SEARCH_IN_LIST.'")]')); } + + protected function getCheckedColumns(Crawler $crawler): array + { + return $crawler->filterXPath('//form[@name="crud_display_settings_'.static::SESSION_NAME.'"]/descendant::input[@type="checkbox" and @checked]')->extract(['value']); + } } From 7fd8fd346b19e25144cc756e8e827e96575de2e4 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Tue, 6 Sep 2022 18:11:07 +0200 Subject: [PATCH 193/264] Fix CS --- src/Crud/CrudConfig.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Crud/CrudConfig.php b/src/Crud/CrudConfig.php index 5225a90..f480b87 100644 --- a/src/Crud/CrudConfig.php +++ b/src/Crud/CrudConfig.php @@ -39,6 +39,7 @@ public function setSessionName(string $sessionName): self * Called with an array of options. * * @see CrudColumn for available options + * * @psalm-suppress MissingParamType */ public function addColumn(...$args): self @@ -78,6 +79,7 @@ public function addColumn(...$args): self * Called with an array of options. * * @see CrudColumn for available options + * * @psalm-suppress MissingParamType */ public function addVirtualColumn(...$args): self From 958a4061d0bfe9299c4f23e0482d56d0f92ac989 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Wed, 2 Nov 2022 14:19:20 +0100 Subject: [PATCH 194/264] Fix attr merge with class --- src/Twig/CrudExtension.php | 8 ++++++-- tests/Twig/CrudExtensionTest.php | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/Twig/CrudExtension.php b/src/Twig/CrudExtension.php index a5d0c3f..2eaea5a 100644 --- a/src/Twig/CrudExtension.php +++ b/src/Twig/CrudExtension.php @@ -494,7 +494,7 @@ public function searchFormStart(Environment $environment, Crud $crud, array $opt /** @var FormView $searchForm */ $searchForm = $crud->getSearchForm(); - return array_merge( + $attr = array_merge( [ 'data-crud-search-id' => $crud->getDivIdSearch(), 'data-crud-list-id' => $crud->getDivIdList(), @@ -502,8 +502,10 @@ public function searchFormStart(Environment $environment, Crud $crud, array $opt $searchForm->vars['attr'], $value, $this->getAjaxAttributes($this->validateAjaxOptions($options['ajax_options'])), - ['class' => (isset($value['class'])) ? $value['class'].' ec-crud-search-form' : 'ec-crud-search-form'], ); + $attr['class'] = (isset($attr['class'])) ? $attr['class'].' ec-crud-search-form' : 'ec-crud-search-form'; + + return $attr; }); $options = $resolver->resolve($this->buildOptions('crud_search_form_start', $options, $crud)); @@ -620,6 +622,8 @@ public function formStartAjax(FormView $formView, array $options = []): string } if (isset($options['attr']['class']) && null !== $options['attr']['class']) { $options['attr']['class'] = sprintf('%s %s', $autoClass, $options['attr']['class']); + } elseif (isset($formView->vars['attr']['class']) && null !== $formView->vars['attr']['class']) { + $options['attr']['class'] = sprintf('%s %s', $autoClass, $formView->vars['attr']['class']); } else { $options['attr']['class'] = $autoClass; } diff --git a/tests/Twig/CrudExtensionTest.php b/tests/Twig/CrudExtensionTest.php index b3c5df1..5147161 100644 --- a/tests/Twig/CrudExtensionTest.php +++ b/tests/Twig/CrudExtensionTest.php @@ -83,6 +83,22 @@ public function testFormStartAjaxWithClassAttr(): void $this->assertMatchesXpath($html, $xpath); } + public function testFormStartAjaxWithClassAttrInFormView(): void + { + $builder = $this->formFactory->createBuilder(FormType::class, null, [ + 'action' => '/url', + 'method' => 'POST', + 'csrf_protection' => false, + 'attr' => [ + 'class' => 'my-class', + ], + ]); + $html = $this->crudExtension->formStartAjax($builder->getForm()->createView()); + + $xpath = '//form[(contains(concat(" ", normalize-space(@class), " "), " ec-crud-ajax-form-auto ")) and contains(concat(" ", normalize-space(@class), " "), " my-class ")]'; + $this->assertMatchesXpath($html, $xpath); + } + public function testFormStartAjaxWithAjaxOptions(): void { $html = $this->crudExtension->formStartAjax($this->createFormView(), [ From 46345fe3254dddc74ee126ccbbd5910a7671ae33 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sat, 3 Dec 2022 18:24:31 +0100 Subject: [PATCH 195/264] Add Symfony 6.2 tests --- .github/workflows/tests.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 48ba971..321c55a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -26,6 +26,10 @@ jobs: symfony-version: '6.1.*' composer-flags: '--prefer-stable --prefer-lowest' description: 'with SF 6.1.* lowest' + - php-version: '8.1' + symfony-version: '6.2.*' + composer-flags: '--prefer-stable --prefer-lowest' + description: 'with SF 6.2.* lowest' #Symfony versions @@ -38,6 +42,9 @@ jobs: - php-version: '8.1' symfony-version: '6.1.*' description: 'with SF 6.1.*' + - php-version: '8.1' + symfony-version: '6.2.*' + description: 'with SF 6.2.*' #PHP versions - php-version: '8.0' From e663bbb00ce8e05f498dae095350e4b8cbcdc4cf Mon Sep 17 00:00:00 2001 From: hlecorche Date: Thu, 8 Dec 2022 21:40:01 +0100 Subject: [PATCH 196/264] Add PHP 8.2 tests --- .github/workflows/tests.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 321c55a..13a9296 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -33,22 +33,23 @@ jobs: #Symfony versions - - php-version: '8.1' + - php-version: '8.2' symfony-version: '5.4.*' description: 'with SF 5.4.*' - - php-version: '8.1' + - php-version: '8.2' symfony-version: '6.0.*' description: 'with SF 6.0.*' - - php-version: '8.1' + - php-version: '8.2' symfony-version: '6.1.*' description: 'with SF 6.1.*' - - php-version: '8.1' + - php-version: '8.2' symfony-version: '6.2.*' description: 'with SF 6.2.*' #PHP versions - php-version: '8.0' - php-version: '8.1' + - php-version: '8.2' #CS - php-version: '8.1' From 4de05e2a3afb663606123da37cef9dc4e00643d1 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Wed, 2 Nov 2022 12:25:58 +0100 Subject: [PATCH 197/264] [Ajax] Replace jquery by Fetch --- assets/js/ajax.js | 230 +++- assets/js/crud.js | 15 +- doc/install.md | 4 + doc/references/ajax.md | 61 +- package.json | 3 +- src/Crud/CrudResponseGenerator.php | 4 - src/Twig/CrudExtension.php | 8 +- tests/Twig/CrudExtensionTest.php | 4 +- tests/assets/js/ajax.spec.js | 1081 ++++++++++++++++--- tests/assets/js/main.js | 4 + tests/assets/js/modal/modal-manager.spec.js | 96 +- tests/assets/js/wait.js | 34 + 12 files changed, 1301 insertions(+), 243 deletions(-) create mode 100644 tests/assets/js/wait.js diff --git a/assets/js/ajax.js b/assets/js/ajax.js index 74802a9..8b6b808 100644 --- a/assets/js/ajax.js +++ b/assets/js/ajax.js @@ -20,7 +20,7 @@ $(function () { return } - click(this) + click(this).catch((error) => console.error(error)) }) $(document).on('click', 'a.ec-crud-ajax-link-auto', function (event) { @@ -31,7 +31,7 @@ $(function () { return } - link(this) + link(this).catch((error) => console.error(error)) }) $(document).on('submit', 'form.ec-crud-ajax-form-auto', function (event) { @@ -42,11 +42,24 @@ $(function () { return } - sendForm(this) + sendForm(this).catch((error) => console.error(error)) }) }) export function sendRequest (options) { + const eventBeginning = new CustomEvent('ec-crud-ajax', { + cancelable: true, + detail: { + options: options + } + }) + document.dispatchEvent(eventBeginning) + if (eventBeginning.defaultPrevented) { + return new Promise((resolve, reject) => { + resolve(null) + }) + } + options = optionsResolver.resolve( { url: null, @@ -56,59 +69,138 @@ export function sendRequest (options) { onSuccess: null, onError: null, onComplete: null, - dataType: 'html', + responseDataType: 'text', method: 'POST', - data: null, + query: {}, + body: null, + successfulResponseRequired: false, cache: false, options: {} }, options ) - options = $.extend({}, options, options.options) - if (optionsResolver.isNotBlank(options.url) === false) { - console.error('Value required: url') - - return + return new Promise((resolve, reject) => { + reject(new TypeError('Value required: url')) + }) } - if (optionsResolver.isNotBlank(options.onBeforeSend)) { - runCallback(options.onBeforeSend, options) - } - if (options.stop !== undefined && options.stop === true) { - return - } + options.urlResolved = resolveUrl(options) const callbacksSuccess = [] if (optionsResolver.isNotBlank(options.update)) { callbacksSuccess.push({ priority: 10, - callback: function (data, textStatus, jqXHR) { - updateDom(options.update, options.updateMode, data) - } + callback: (data, response) => updateDom(options.update, options.updateMode, data) }) } if (optionsResolver.isNotBlank(options.onSuccess)) { callbacksSuccess.push(options.onSuccess) } - $.ajax({ - url: options.url, - type: options.method, - dataType: options.dataType, - cache: options.cache, - data: options.data, - success: function (data, textStatus, jqXHR) { - runCallback(callbacksSuccess, data, textStatus, jqXHR) - }, - error: function (jqXHR, textStatus, errorThrown) { - runCallback(options.onError, jqXHR, textStatus, errorThrown) - }, - complete: function (jqXHR, textStatus) { - runCallback(options.onComplete, jqXHR, textStatus) + let fetchOptions = { + method: options.method + } + if (options.body) { + if (typeof options.body === 'string' || options.body instanceof String || options.body instanceof FormData) { + fetchOptions.body = options.body + } else if (options.body instanceof Object) { + const formData = new FormData() + Object.entries(options.body).forEach(entry => { + if (Array.isArray(entry[1])) { + entry[1].forEach(subEntry => { + formData.append(entry[0], subEntry) + }) + } else { + formData.append(entry[0], entry[1]) + } + }) + fetchOptions.body = formData + } else if (options.body !== null) { + return new Promise((resolve, reject) => { + reject(new TypeError('Bad type for option "body"')) + }) + } + } + + const eventBeforeSend = new CustomEvent('ec-crud-ajax-before-send', { + cancelable: true, + detail: { + options: options } }) + document.dispatchEvent(eventBeforeSend) + if (eventBeforeSend.defaultPrevented) { + return new Promise((resolve, reject) => { + resolve(null) + }) + } + if (optionsResolver.isNotBlank(options.onBeforeSend)) { + runCallback(options.onBeforeSend, options) + } + if (options.stop !== undefined && options.stop === true) { + return new Promise((resolve, reject) => { + resolve(null) + }) + } + + fetchOptions = $.extend(fetchOptions, options.options) + + const fetchPromise = fetch(options.urlResolved, fetchOptions) + const ajaxPromise = new Promise((resolve, reject) => { + fetchPromise.then(response => { + if (response.ok) { + // Response OK (status in the range 200 – 299) + + let dataPromise + if (options.responseDataType === 'text' || options.responseDataType === 'json') { + // Using a clone avoids "TypeError: Already read" when response read is read a 2nd time later + const responseCloned = response.clone() + + try { + if (options.responseDataType === 'text') { + dataPromise = responseCloned.text() + } else if (options.responseDataType === 'json') { + dataPromise = responseCloned.json() + } + } catch (e) { + } + } else { + dataPromise = new Promise((resolve, reject) => { + resolve(null) + }) + } + + dataPromise.then(data => { + executeEventsAndCallbacksSuccess(callbacksSuccess, options, data, response) + resolve(response) + }) + + dataPromise.catch(error => { + error = 'Error during fetching response body: ' + error + executeEventsAndCallbacksError(options, error, response) + reject(error) + }) + } else { + // Response not OK (status not in the range 200 – 299) + executeEventsAndCallbacksError(options, response.statusText, response) + if (options.successfulResponseRequired) { + reject(new Error('The response is not successful: ' + response.statusText)) + } else { + resolve(response) + } + } + }) + + fetchPromise.catch(error => { + error = 'Error during query execution: ' + error + executeEventsAndCallbacksError(options, error, null) + reject(error) + }) + }) + + return ajaxPromise } export function click (element, options) { @@ -118,7 +210,7 @@ export function click (element, options) { optionsResolver.getDataAttributes(element, 'ecCrudAjax') ) - sendRequest(options) + return sendRequest(options) } export function link (link, options) { @@ -134,7 +226,7 @@ export function link (link, options) { ) ) - sendRequest(options) + return sendRequest(options) } export function sendForm (form, options) { @@ -144,7 +236,7 @@ export function sendForm (form, options) { { url: $(form).attr('action'), method: $(form).attr('method'), - data: $(form).serialize() + body: new FormData($(form).get(0)) }, optionsResolver.resolve( options, @@ -152,7 +244,7 @@ export function sendForm (form, options) { ) ) - sendRequest(options) + return sendRequest(options) } export function updateDom (element, updateMode, content) { @@ -191,3 +283,67 @@ export function updateDom (element, updateMode, content) { }) $(element).trigger(eventAfter) } + +function resolveUrl (options) { + const url = new URL(options.url, window.location.origin) + const searchParams = url.searchParams + + Object.entries(options.query).forEach(entry => { + if (Array.isArray(entry[1])) { + if (searchParams.has(entry[0])) { + searchParams.delete(entry[0]) + } + entry[1].forEach(subEntry => { + searchParams.append(entry[0], subEntry) + }) + } else { + searchParams.set(entry[0], entry[1]) + } + }) + + if (!options.cache && !searchParams.has('_')) { + searchParams.set('_', Date.now()) + } + + return url.toString() +} + +function executeEventsAndCallbacksSuccess (callbacksSuccess, options, data, response) { + const eventOnSuccess = new CustomEvent('ec-crud-ajax-on-success', { + detail: { + data: data, + response: response + } + }) + document.dispatchEvent(eventOnSuccess) + runCallback(callbacksSuccess, data, response) + + const eventOnComplete = new CustomEvent('ec-crud-ajax-on-complete', { + detail: { + statusText: response.statusText, + response: response + } + }) + document.dispatchEvent(eventOnComplete) + runCallback(options.onComplete, response.statusText, response) +} + +function executeEventsAndCallbacksError (options, statusText, response) { + const eventOnError = new CustomEvent('ec-crud-ajax-on-error', { + detail: { + statusText: statusText, + response: response + } + }) + document.dispatchEvent(eventOnError) + runCallback(options.onError, statusText, response) + + const eventOnComplete = new CustomEvent('ec-crud-ajax-on-complete', { + detail: { + statusText: statusText, + response: response + } + }) + document.dispatchEvent(eventOnComplete) + runCallback(options.onComplete, statusText, response) +} diff --git a/assets/js/crud.js b/assets/js/crud.js index 873d54a..d257798 100644 --- a/assets/js/crud.js +++ b/assets/js/crud.js @@ -18,9 +18,8 @@ $(document).on('submit', 'form.ec-crud-search-form', function (event) { const listId = $(this).attr('data-crud-list-id') sendForm(this, { - onSuccess: function (data, textStatus, jqXHR) { - const json = $.parseJSON(data) - + responseDataType: 'json', + onSuccess: function (json, response) { updateDom($('#' + searchId), 'update', json.render_search) updateDom($('#' + listId), 'update', json.render_list) } @@ -32,9 +31,8 @@ $(document).on('click', 'button.ec-crud-search-reset', function (event) { const listId = $(this).attr('data-crud-list-id') click(this, { - onSuccess: function (data, textStatus, jqXHR) { - const json = $.parseJSON(data) - + responseDataType: 'json', + onSuccess: function (json, response) { updateDom($('#' + searchId), 'update', json.render_search) updateDom($('#' + listId), 'update', json.render_list) } @@ -93,9 +91,8 @@ $(document).on('submit', '.ec-crud-display-settings form', function (event) { closeDisplaySettings($('#' + displaySettingsContainerId)) sendForm(this, { - onSuccess: function (data, textStatus, jqXHR) { - const json = $.parseJSON(data) - + responseDataType: 'json', + onSuccess: function (json, response) { updateDom($('#' + listId), 'update', json.render_list) if (!json.form_is_valid) { openDisplaySettings($('#' + displaySettingsContainerId)) diff --git a/doc/install.md b/doc/install.md index a8a9256..c128019 100644 --- a/doc/install.md +++ b/doc/install.md @@ -6,6 +6,10 @@ Prérequis : * Yarn * Webpack Encore * jQuery +* Pour une compatibilité avec un maximum de navigateurs Internet, il est conseillé : + * De configurer Webpack Encore pour activer Babel et core-js + * D'utiliser un [polyfill pour Fetch](https://github.com/github/fetch) + * D'utiliser un [polyfill pour FormData](https://github.com/jimmywarting/FormData) * Un gestionnaire de thème chargé par Webpack Encore parmi : * Bootstrap 3 * Votre thème personnalisé (créer un thème Twig qui hérite `@EcommitCrud/Theme/base.html.twig`) diff --git a/doc/references/ajax.md b/doc/references/ajax.md index 372d676..f2c365d 100644 --- a/doc/references/ajax.md +++ b/doc/references/ajax.md @@ -15,22 +15,49 @@ ajax.sendRequest({ }); ``` -Options : - -| Option | Description | Requis | Valeur par défaut | -| ------ | ----------- | --------| ----------------- | -| url | Url de l'action Ajax | Oui | | -| update | Si défini, mise à jour du DOM avec le résultat. (voir doc de la fonction `updateDom` plus bas) | Non | | -| updateMode | Méthode à utiliser pour la mise à jour du DOM (voir doc de la fonction `updateDom` plus bas) | Non | update | -| onBeforeSend | [Callback(s)](js-callbacks.md#définition-des-callbacks) lancé(s) avant l'envoi de la requête. `Function(options)`. Dans le callback peut passer `options.stop = false` pour annuler la requête. | Non | | -| onSuccess | [Callback(s)](js-callbacks.md#définition-des-callbacks) lancé(s) en cas de succès de la réponse. `Function(data, textStatus, jqXHR)` | Non | | -| onError | [Callback(s)](js-callbacks.md#définition-des-callbacks) lancé(s) en cas d'erreur lors de la réponse. `Function(jqXHR, textStatus, errorThrown)` | Non | | -| onComplete | [Callback(s)](js-callbacks.md#définition-des-callbacks) lancé(s) après réponse. `Function(jqXHR, textStatus)` | Non | | -| dataType | Type de données attendu (html, xml, json, jsonp, text) | Non | html | -| method | Méthode HTTP | Non | POST | -| data | Données envoyées dans la requête | Non | | -| cache | Utilise ou non le cache | Non | false | -| options | Tableau d'options de la fonction `ajax` de jQuery | Non | { } | +**Options :** + +| Option | Description | Requis | Valeur par défaut | +|----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|-------------------| +| url | Url de l'action Ajax | Oui | | +| update | Si défini, mise à jour du DOM avec le résultat. (voir doc de la fonction `updateDom` plus bas) | Non | | +| updateMode | Méthode à utiliser pour la mise à jour du DOM (voir doc de la fonction `updateDom` plus bas) | Non | update | +| onBeforeSend | [Callback(s)](js-callbacks.md#définition-des-callbacks) lancé(s) avant l'envoi de la requête. `Function(options)`. Dans le callback peut passer `options.stop = false` pour annuler la requête. | Non | | +| onSuccess | [Callback(s)](js-callbacks.md#définition-des-callbacks) lancé(s) en cas de succès (code de réponse HTTP 200-299). `Function(data, response)`.
  • `data`: Donnée de la réponse. Voir option `responseDataType` pour le format attendu
  • `response`: Objet de type [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response)
| Non | | +| onError | [Callback(s)](js-callbacks.md#définition-des-callbacks) lancé(s) en cas d'erreur (code de réponse HTTP ≠ 200-299 ou erreur avant la réponse). `Function(statusText, response)`
  • `statusText`: [statusText](https://developer.mozilla.org/en-US/docs/Web/API/Response/statusText) de la réponse (si réponse). Sinon mesage d'erreur au format string
  • `response`: Objet de type [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) si réponse, nulm sinon
| Non | | +| onComplete | [Callback(s)](js-callbacks.md#définition-des-callbacks) lancé(s) après les callbacks `onSuccess` ou `onError`. `Function(statusText, response)`. Voir options `onSuccess` et `onError` pour détails sur `statusText` et `response` | Non | | +| successfulResponseRequired | En cas de code de réponse HTTP ≠ 200-299:
  • Si cette option est vraie, la promesse est alors rejetée
  • Sinon elle est résolue
| Non | false | +| responseDataType | Format de donnée de réponse attendu dans le callback `OnSuccess`. Valeurs disponibles:
  • `text`: Format string
  • `json`: Objet JavaScript converti depuis une réponse JSON
  • Autre valeur: NULL (donnée de réponse à récupérer manuellement dans l'objet Response)
| Non | text | +| method | Méthode HTTP | Non | POST | +| body | Données envoyées dans le corps de la requête. Types de données acceptés:
  • String
  • [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData)
  • Objet
| Non | | +| query | Paramètres à rajouter dans l'URL | Non | { } | +| cache | Utilise ou non le cache | Non | false | +| options | Tableau d'options de la fonction [Fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) | Non | { } | + +**Promesses :** + +La fonction `sendRequest` retourne une promesse. +* En cas de code de réponse HTTP 200-299, la promesse résout l'objet [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) représentant la réponse à la requête. +* En cas de code de réponse HTTP autre 200-299: + * Si l'option `successfulResponseRequired` est à `false`, alors la promesse résout l'objet [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) représentant la réponse à la requête. + * Sinon la promesse est rejetée. +* En cas d'annulation de la requête (par le callback `onBeforeSend` ou les événements `ec-crud-ajax` / `ec-crud-ajax-before-send`), la promesse résout une valeur nulle. +* En cas d'erreur lors de l'exécution de la requête, la promesse est rejetée. +* En cas d'erreur lors la lecture de la réponse, la promesse est rejetée. +* En cas d'erreur de configuration, la promesse est rejetée. + + +**Événements :** + +| Événement | Objet | Description | Propriétés disponibles | +|--------------------------|------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------| +| ec-crud-ajax | [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent) (cancelable) | Appelé avant l'exécution de la requête, avant la résolution des options. Peut annuler la requête avec `event.preventDefault()` |
  • event.details.options
| +| ec-crud-ajax-before-send | [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent) (cancelable) | Appelé avant l'exécution de la requête, après la résolution des options. Peut annuler la requête avec `event.preventDefault()` |
  • event.details.options
| +| ec-crud-ajax-on-success | [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent) | Même fonctionnement que callback `onSuccess` |
  • event.details.data
  • event.details.response
| +| ec-crud-ajax-on-error | [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent) | Même fonctionnement que callback `onError` |
  • event.details.statusText
  • event.details.response
| +| ec-crud-ajax-on-complete | [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent) | Même fonctionnement que callback `onComplete` |
  • event.details.statusText
  • event.details.response
| + + ### click @@ -94,6 +121,7 @@ ajax.link($('#linkToTest'), { * Ou la valeur de l'option `url` de la fonction `link` (si présent) * Ou la valeur de `href` * Les attributs `data-ec-crud-ajax-*` (si présents) écrasent les options de la fonction `link` (si présent) +* La fonction retourne la promesse générée par `sendRequest` ### sendForm @@ -154,6 +182,7 @@ ajax.sendForm($('#formToTest'), { * Ou la valeur de l'option `data` de la fonction `sendForm` (si présent) * Ou les données du formulaire * Les attributs `data-ec-crud-ajax-*` (si présents) écrasent les options de la fonction `sendForm` (si présent) +* La fonction retourne la promesse générée par `sendRequest` ### updateDom diff --git a/package.json b/package.json index dc027e6..26a770f 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "karma-jasmine-ajax": "^0.1.13", "karma-spec-reporter": "^0.0.32", "karma-webpack": "^5.0", - "webpack-notifier": "^1.6.0" + "webpack-notifier": "^1.6.0", + "whatwg-fetch": "^3.6.2" }, "private": true } diff --git a/src/Crud/CrudResponseGenerator.php b/src/Crud/CrudResponseGenerator.php index 898f144..3ec2384 100644 --- a/src/Crud/CrudResponseGenerator.php +++ b/src/Crud/CrudResponseGenerator.php @@ -18,7 +18,6 @@ use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Contracts\Service\ServiceSubscriberInterface; use Twig\Environment; @@ -40,9 +39,6 @@ public function getResponse(Crud $crud, array $options = []): Response public function getAjaxResponse(Crud $crud, array $options = []): Response { $masterRequest = $this->container->get('request_stack')->getMainRequest(); - if (!$masterRequest->isXmlHttpRequest()) { - throw new NotFoundHttpException('Ajax is required'); - } $options = $this->getOptions($options); $data = $this->processCrud($crud, $options); diff --git a/src/Twig/CrudExtension.php b/src/Twig/CrudExtension.php index 2eaea5a..c373282 100644 --- a/src/Twig/CrudExtension.php +++ b/src/Twig/CrudExtension.php @@ -651,9 +651,9 @@ public function formStartAjax(FormView $formView, array $options = []): string * * on_success: Callback * * on_error: Callback * * on_complete: Callback - * * data_type: Request type. Default: html * * method: Resquest method: Default: POST - * * data: Data sent + * * query: Query string parameters (GET) + * * body: Request body * * cache: Use cache. Default: false * * options: Array of options */ @@ -681,9 +681,9 @@ protected function validateAjaxOptions(array $options, array $requiredOptions = 'on_success' => null, 'on_error' => null, 'on_complete' => null, - 'data_type' => null, 'method' => null, - 'data' => null, + 'query' => null, + 'body' => null, 'cache' => null, 'options' => null, ]); diff --git a/tests/Twig/CrudExtensionTest.php b/tests/Twig/CrudExtensionTest.php index 5147161..e621cc1 100644 --- a/tests/Twig/CrudExtensionTest.php +++ b/tests/Twig/CrudExtensionTest.php @@ -165,10 +165,10 @@ public function getTestAjaxAttributesProvider(): array [[], ''], [['url' => '/url?id=a'], ' data-ec-crud-ajax-url="/url?id=a"'], [['url' => '/url?id=a', 'method' => 'GET'], ' data-ec-crud-ajax-url="/url?id=a" data-ec-crud-ajax-method="GET"'], - [['data' => ''], ' data-ec-crud-ajax-data="<script>ValueToEscape</script>"'], + [['body' => ''], ' data-ec-crud-ajax-body="<script>ValueToEscape</script>"'], [['cache' => true], ' data-ec-crud-ajax-cache="true"'], [['cache' => false], ' data-ec-crud-ajax-cache="false"'], - [['data' => ['var1' => 'value1']], ' data-ec-crud-ajax-data="{"var1":"value1"}"'], + [['body' => ['var1' => 'value1']], ' data-ec-crud-ajax-body="{"var1":"value1"}"'], ]; } diff --git a/tests/assets/js/ajax.spec.js b/tests/assets/js/ajax.spec.js index da7b2f9..caef555 100644 --- a/tests/assets/js/ajax.spec.js +++ b/tests/assets/js/ajax.spec.js @@ -10,95 +10,302 @@ import * as ajax from '@ecommit/crud-bundle/js/ajax' import * as callbackManager from '@ecommit/crud-bundle/js/callback-manager' import $ from 'jquery' +import wait from './wait' describe('Test Ajax.sendRequest', function () { beforeEach(function () { jasmine.Ajax.install() + addJasmineAjaxFormDataSupport() - jasmine.Ajax.stubRequest('/goodRequest').andReturn({ + jasmine.Ajax.stubRequest(/goodRequest/).andReturn({ status: 200, - responseText: 'OK' + statusText: 'OK', + response: 'CONTENT', + responseText: 'CONTENT' }) - jasmine.Ajax.stubRequest('/resultJS').andReturn({ + jasmine.Ajax.stubRequest(/resultJSON/).andReturn({ status: 200, + statusText: 'OK', + response: '{"var1": "value1", "var2": "value2"}', + responseText: '{"var1": "value1", "var2": "value2"}' + }) + + jasmine.Ajax.stubRequest(/badJSON/).andReturn({ + status: 200, + statusText: 'OK', + response: '{"var1":', + responseText: '{"var1":' + }) + + jasmine.Ajax.stubRequest(/resultJavaScript/).andReturn({ + status: 200, + statusText: 'OK', + response: '
BEFORE
', responseText: '
BEFORE
' }) - jasmine.Ajax.stubRequest('/error404').andReturn({ + jasmine.Ajax.stubRequest(/error404/).andReturn({ status: 404, + statusText: 'Not Found', + response: 'Page not found !', responseText: 'Page not found !' }) + + jasmine.Ajax.stubRequest(/failure/).andError() }) afterEach(function () { jasmine.Ajax.uninstall() $('.html-test').remove() callbackManager.clear() + $(document).off('ec-crud-ajax-on-success') + $(document).off('ec-crud-ajax-on-error') + $(document).off('ec-crud-ajax-on-complete') }) - it('Send request', function () { + it('Send request', async function () { const callbackSuccess = jasmine.createSpy('success') const callbackError = jasmine.createSpy('error') const callbackComplete = jasmine.createSpy('complete') + const callbackCatch = jasmine.createSpy('catch') + const eventSuccess = jasmine.createSpy('event-success') + const eventError = jasmine.createSpy('event-error') + const eventComplete = jasmine.createSpy('event-complete') + + $(document).on('ec-crud-ajax-on-success', function (event) { + expect(event.detail.data).toEqual('CONTENT') + expect(event.detail.response).toBeInstanceOf(Response) + eventSuccess() + }) + $(document).on('ec-crud-ajax-on-error', function (event) { + eventError() + }) + $(document).on('ec-crud-ajax-on-complete', function (event) { + expect(event.detail.statusText).toEqual('OK') + expect(event.detail.response).toBeInstanceOf(Response) + eventComplete() + }) - ajax.sendRequest({ + const promise = ajax.sendRequest({ url: '/goodRequest', - onComplete: function (jqXHR, textStatus) { + onComplete: function (statusText, response) { + expect(statusText).toEqual('OK') + expect(response).toBeInstanceOf(Response) callbackComplete() }, - onSuccess: function (data, textStatus, jqXHR) { - callbackSuccess(data) + onSuccess: function (data, response) { + expect(data).toEqual('CONTENT') + expect(response).toBeInstanceOf(Response) + callbackSuccess() }, - onError: function (jqXHR, textStatus, errorThrown) { - callbackError(jqXHR.responseText) + onError: function (statusText, response) { + callbackError() } + }).catch(() => { + callbackCatch() }) + expect(promise).toBeInstanceOf(Promise) - expect(jasmine.Ajax.requests.mostRecent().url).toBe('/goodRequest') + const response = await promise + + expect(response).toBeInstanceOf(Response) + expect(jasmine.Ajax.requests.mostRecent().url).toMatch('/goodRequest') expect(jasmine.Ajax.requests.mostRecent().method).toBe('POST') - expect(callbackSuccess).toHaveBeenCalledWith('OK') + expect(callbackSuccess).toHaveBeenCalledBefore(callbackComplete) expect(callbackError).not.toHaveBeenCalled() expect(callbackComplete).toHaveBeenCalled() + expect(callbackCatch).not.toHaveBeenCalled() + expect(eventSuccess).toHaveBeenCalledBefore(eventComplete) + expect(eventError).not.toHaveBeenCalled() + expect(eventComplete).toHaveBeenCalled() + }) + + it('Send bad request', async function () { + const callbackSuccess = jasmine.createSpy('success') + const callbackError = jasmine.createSpy('error') + const callbackComplete = jasmine.createSpy('complete') + const callbackCatch = jasmine.createSpy('catch') + const eventSuccess = jasmine.createSpy('event-success') + const eventError = jasmine.createSpy('event-error') + const eventComplete = jasmine.createSpy('event-complete') + + $(document).on('ec-crud-ajax-on-success', function (event) { + eventSuccess() + }) + $(document).on('ec-crud-ajax-on-error', function (event) { + expect(event.detail.statusText).toEqual('Not Found') + expect(event.detail.response).toBeInstanceOf(Response) + eventError() + }) + $(document).on('ec-crud-ajax-on-complete', function (event) { + expect(event.detail.statusText).toEqual('Not Found') + expect(event.detail.response).toBeInstanceOf(Response) + eventComplete() + }) + + const promise = ajax.sendRequest({ + url: '/error404', + onComplete: function (statusText, response) { + expect(statusText).toEqual('Not Found') + expect(response).toBeInstanceOf(Response) + callbackComplete() + }, + onSuccess: function (data, response) { + callbackSuccess() + }, + onError: function (statusText, response) { + expect(statusText).toEqual('Not Found') + expect(response).toBeInstanceOf(Response) + callbackError() + } + }).catch(() => { + callbackCatch() + }) + expect(promise).toBeInstanceOf(Promise) + + const response = await promise + + expect(response).toBeInstanceOf(Response) + expect(jasmine.Ajax.requests.mostRecent().url).toMatch('/error404') + expect(callbackSuccess).not.toHaveBeenCalled() + expect(callbackError).toHaveBeenCalledBefore(callbackComplete) + expect(callbackComplete).toHaveBeenCalled() + expect(callbackCatch).not.toHaveBeenCalled() + expect(eventSuccess).not.toHaveBeenCalled() + expect(eventError).toHaveBeenCalledBefore(eventComplete) + expect(eventComplete).toHaveBeenCalled() }) - it('Send bad request', function () { + it('Send bad request with successfulResponseRequired', async function () { const callbackSuccess = jasmine.createSpy('success') const callbackError = jasmine.createSpy('error') const callbackComplete = jasmine.createSpy('complete') + const callbackCatch = jasmine.createSpy('catch') + const eventSuccess = jasmine.createSpy('event-success') + const eventError = jasmine.createSpy('event-error') + const eventComplete = jasmine.createSpy('event-complete') - ajax.sendRequest({ + $(document).on('ec-crud-ajax-on-success', function (event) { + eventSuccess() + }) + $(document).on('ec-crud-ajax-on-error', function (event) { + expect(event.detail.statusText).toEqual('Not Found') + expect(event.detail.response).toBeInstanceOf(Response) + eventError() + }) + $(document).on('ec-crud-ajax-on-complete', function (event) { + expect(event.detail.statusText).toEqual('Not Found') + expect(event.detail.response).toBeInstanceOf(Response) + eventComplete() + }) + + const promise = ajax.sendRequest({ url: '/error404', - onComplete: function (jqXHR, textStatus) { + successfulResponseRequired: true, + onComplete: function (statusText, response) { + expect(statusText).toEqual('Not Found') + expect(response).toBeInstanceOf(Response) + callbackComplete() + }, + onSuccess: function (data, response) { + callbackSuccess() + }, + onError: function (statusText, response) { + expect(statusText).toEqual('Not Found') + expect(response).toBeInstanceOf(Response) + callbackError() + } + }).catch(error => { + expect(error).toBeInstanceOf(Error) + expect(error.message).toEqual('The response is not successful: Not Found') + callbackCatch() + }) + expect(promise).toBeInstanceOf(Promise) + + const response = await promise + + expect(response).toBeUndefined() + expect(jasmine.Ajax.requests.mostRecent().url).toMatch('/error404') + expect(callbackSuccess).not.toHaveBeenCalled() + expect(callbackError).toHaveBeenCalledBefore(callbackComplete) + expect(callbackComplete).toHaveBeenCalled() + expect(callbackCatch).toHaveBeenCalled() + expect(eventSuccess).not.toHaveBeenCalled() + expect(eventError).toHaveBeenCalledBefore(eventComplete) + expect(eventComplete).toHaveBeenCalled() + }) + + it('Send bad request with fetch error', async function () { + const callbackSuccess = jasmine.createSpy('success') + const callbackError = jasmine.createSpy('error') + const callbackComplete = jasmine.createSpy('complete') + const callbackCatch = jasmine.createSpy('catch') + const eventSuccess = jasmine.createSpy('event-success') + const eventError = jasmine.createSpy('event-error') + const eventComplete = jasmine.createSpy('event-complete') + + $(document).on('ec-crud-ajax-on-success', function (event) { + eventSuccess() + }) + $(document).on('ec-crud-ajax-on-error', function (event) { + expect(event.detail.statusText).toMatch(/Error during query execution:.+failed/) + expect(event.detail.response).toBeNull() + eventError() + }) + $(document).on('ec-crud-ajax-on-complete', function (event) { + expect(event.detail.statusText).toMatch(/Error during query execution:.+failed/) + expect(event.detail.response).toBeNull() + eventComplete() + }) + + const promise = ajax.sendRequest({ + url: '/failure', + onComplete: function (statusText, response) { + expect(statusText).toMatch('failed') + expect(response).toBeNull() callbackComplete() }, - onSuccess: function (data, textStatus, jqXHR) { - callbackSuccess(data) + onSuccess: function (data, response) { + callbackSuccess() }, - onError: function (jqXHR, textStatus, errorThrown) { - callbackError(jqXHR.responseText) + onError: function (statusText, response) { + expect(statusText).toMatch(/Error during query execution:.+failed/) + expect(response).toBeNull() + callbackError() } + }).catch(error => { + expect(error).toMatch(/Error during query execution:.+failed/) + callbackCatch() }) + expect(promise).toBeInstanceOf(Promise) - expect(jasmine.Ajax.requests.mostRecent().url).toBe('/error404') + const response = await promise + + expect(response).toBeUndefined() + expect(jasmine.Ajax.requests.mostRecent().url).toMatch('/failure') expect(callbackSuccess).not.toHaveBeenCalled() - expect(callbackError).toHaveBeenCalledWith('Page not found !') + expect(callbackError).toHaveBeenCalledBefore(callbackComplete) expect(callbackComplete).toHaveBeenCalled() + expect(callbackCatch).toHaveBeenCalled() + expect(eventSuccess).not.toHaveBeenCalled() + expect(eventError).toHaveBeenCalledBefore(eventComplete) + expect(eventComplete).toHaveBeenCalled() }) - it('Send request with callback priorities', function () { + it('Send request with callback priorities', async function () { const callbackSuccess1 = jasmine.createSpy('success1') const callbackSuccess2 = jasmine.createSpy('success2') - ajax.sendRequest({ + await ajax.sendRequest({ url: '/goodRequest', onSuccess: [ - function (data, textStatus, jqXHR) { + function (data, response) { callbackSuccess1() }, { priority: 99, - callback: function (data, textStatus, jqXHR) { + callback: function (data, response) { callbackSuccess2() } } @@ -109,20 +316,287 @@ describe('Test Ajax.sendRequest', function () { expect(callbackSuccess2).toHaveBeenCalledBefore(callbackSuccess1) }) - it('Send request without URL', function () { - spyOn(window.console, 'error') - ajax.sendRequest({}) - expect(window.console.error).toHaveBeenCalledWith('Value required: url') + it('Send request without URL', async function () { + const callbackSuccess = jasmine.createSpy('success') + const callbackError = jasmine.createSpy('error') + const callbackComplete = jasmine.createSpy('complete') + const callbackCatch = jasmine.createSpy('catch') + + const promise = ajax.sendRequest({ + onComplete: function (statusText, response) { + callbackComplete() + }, + onSuccess: function (data, response) { + callbackSuccess() + }, + onError: function (statusText, response) { + callbackError() + } + }).catch(error => { + expect(error).toBeInstanceOf(TypeError) + expect(error.message).toEqual('Value required: url') + callbackCatch() + }) + expect(promise).toBeInstanceOf(Promise) + + const response = await promise + + expect(response).toBeUndefined() + expect(jasmine.Ajax.requests.mostRecent()).toBeUndefined() + expect(callbackSuccess).not.toHaveBeenCalled() + expect(callbackError).not.toHaveBeenCalled() + expect(callbackComplete).not.toHaveBeenCalled() + expect(callbackCatch).toHaveBeenCalled() + }) + + it('Send request with string body', async function () { + const callbackSuccess = jasmine.createSpy('success') + const callbackError = jasmine.createSpy('error') + const callbackComplete = jasmine.createSpy('complete') + const callbackCatch = jasmine.createSpy('catch') + + const promise = ajax.sendRequest({ + url: '/goodRequest', + body: 'body-content', + onComplete: function (statusText, response) { + callbackComplete() + }, + onSuccess: function (data, response) { + callbackSuccess() + }, + onError: function (statusText, response) { + callbackError() + } + }).catch(() => { + callbackCatch() + }) + expect(promise).toBeInstanceOf(Promise) + + const response = await promise + + expect(response).toBeInstanceOf(Response) + expect(jasmine.Ajax.requests.mostRecent().url).toMatch('/goodRequest') + expect(jasmine.Ajax.requests.mostRecent().params).toEqual('body-content') + expect(callbackSuccess).toHaveBeenCalled() + expect(callbackError).not.toHaveBeenCalled() + expect(callbackComplete).toHaveBeenCalled() + expect(callbackCatch).not.toHaveBeenCalled() + }) + + it('Send request with FormData body', async function () { + const callbackSuccess = jasmine.createSpy('success') + const callbackError = jasmine.createSpy('error') + const callbackComplete = jasmine.createSpy('complete') + const callbackCatch = jasmine.createSpy('catch') + + const formData = new FormData() + formData.append('var1', 'My value 1') + formData.append('var2[]', 'val2A') + formData.append('var2[]', 'val2C') + + const promise = ajax.sendRequest({ + url: '/goodRequest', + body: formData, + onComplete: function (statusText, response) { + callbackComplete() + }, + onSuccess: function (data, response) { + callbackSuccess() + }, + onError: function (statusText, response) { + callbackError() + } + }).catch(() => { + callbackCatch() + }) + expect(promise).toBeInstanceOf(Promise) + + const response = await promise + + expect(response).toBeInstanceOf(Response) + expect(jasmine.Ajax.requests.mostRecent().url).toMatch('/goodRequest') + expect(jasmine.Ajax.requests.mostRecent().data()).toEqual([['var1', 'My value 1'], ['var2[]', 'val2A'], ['var2[]', 'val2C']]) // Parsed by addJasmineAjaxFormDataSupport + expect(callbackSuccess).toHaveBeenCalled() + expect(callbackError).not.toHaveBeenCalled() + expect(callbackComplete).toHaveBeenCalled() + expect(callbackCatch).not.toHaveBeenCalled() + }) + + it('Send request with object body', async function () { + const callbackSuccess = jasmine.createSpy('success') + const callbackError = jasmine.createSpy('error') + const callbackComplete = jasmine.createSpy('complete') + const callbackCatch = jasmine.createSpy('catch') + + const promise = ajax.sendRequest({ + url: '/goodRequest', + body: { + var1: 'My value 1', + 'var2[]': ['val2A', 'val2C'] + }, + onComplete: function (statusText, response) { + callbackComplete() + }, + onSuccess: function (data, response) { + callbackSuccess() + }, + onError: function (statusText, response) { + callbackError() + } + }).catch(() => { + callbackCatch() + }) + expect(promise).toBeInstanceOf(Promise) + + const response = await promise + + expect(response).toBeInstanceOf(Response) + expect(jasmine.Ajax.requests.mostRecent().url).toMatch('/goodRequest') + expect(jasmine.Ajax.requests.mostRecent().data()).toEqual([['var1', 'My value 1'], ['var2[]', 'val2A'], ['var2[]', 'val2C']]) // Parsed by addJasmineAjaxFormDataSupport + expect(callbackSuccess).toHaveBeenCalled() + expect(callbackError).not.toHaveBeenCalled() + expect(callbackComplete).toHaveBeenCalled() + expect(callbackCatch).not.toHaveBeenCalled() + }) + + it('Send request with bad body', async function () { + const callbackSuccess = jasmine.createSpy('success') + const callbackError = jasmine.createSpy('error') + const callbackComplete = jasmine.createSpy('complete') + const callbackCatch = jasmine.createSpy('catch') + + const promise = ajax.sendRequest({ + url: '/goodRequest', + body: true, + onComplete: function (statusText, response) { + callbackComplete() + }, + onSuccess: function (data, response) { + callbackSuccess() + }, + onError: function (statusText, response) { + callbackError() + } + }).catch(error => { + expect(error).toBeInstanceOf(TypeError) + expect(error.message).toEqual('Bad type for option "body"') + callbackCatch() + }) + expect(promise).toBeInstanceOf(Promise) + + const response = await promise + + expect(response).toBeUndefined() + expect(jasmine.Ajax.requests.mostRecent()).toBeUndefined() + expect(callbackSuccess).not.toHaveBeenCalled() + expect(callbackError).not.toHaveBeenCalled() + expect(callbackComplete).not.toHaveBeenCalled() + expect(callbackCatch).toHaveBeenCalled() + }) + + it('Send request with query option', async function () { + const promise = ajax.sendRequest({ + url: '/goodRequest?var1=val1&var2[]=val2a&var3=val3', + query: { + var1: 'new1', // override + 'var2[]': ['new2a', 'new2b'], // override + var4: 'new4' + }, + cache: true + }) + expect(promise).toBeInstanceOf(Promise) + + const response = await promise + + expect(response).toBeInstanceOf(Response) + expect(jasmine.Ajax.requests.mostRecent().url).toEqual('http://localhost:9876/goodRequest?var1=new1&var3=val3&var2%5B%5D=new2a&var2%5B%5D=new2b&var4=new4') + }) + + it('Send request with cache', async function () { + const promise = ajax.sendRequest({ + url: '/goodRequest', + cache: true + }) + expect(promise).toBeInstanceOf(Promise) + + const response = await promise + + expect(response).toBeInstanceOf(Response) + expect(jasmine.Ajax.requests.mostRecent().url).toMatch(/goodRequest$/) + }) + + it('Send request with default cache', async function () { + const promise = ajax.sendRequest({ + url: '/goodRequest' + }) + expect(promise).toBeInstanceOf(Promise) + + const response = await promise + + expect(response).toBeInstanceOf(Response) + expect(jasmine.Ajax.requests.mostRecent().url).toMatch(/goodRequest\?_=\d+$/) + }) + + it('Send request without cache', async function () { + const promise = ajax.sendRequest({ + url: '/goodRequest', + cache: false + }) + expect(promise).toBeInstanceOf(Promise) + + const response = await promise + + expect(response).toBeInstanceOf(Response) + expect(jasmine.Ajax.requests.mostRecent().url).toMatch(/goodRequest\?_=\d+$/) + }) + + it('Send request without cache but param is already used', async function () { + const promise = ajax.sendRequest({ + url: '/goodRequest?_=val', + cache: false + }) + expect(promise).toBeInstanceOf(Promise) + + const response = await promise + + expect(response).toBeInstanceOf(Response) + expect(jasmine.Ajax.requests.mostRecent().url).toMatch(/goodRequest\?_=val$/) + }) + + it('Send request with relative URL', async function () { + const promise = ajax.sendRequest({ + url: '/goodRequest', + cache: true + }) + expect(promise).toBeInstanceOf(Promise) + + const response = await promise + + expect(response).toBeInstanceOf(Response) + expect(jasmine.Ajax.requests.mostRecent().url).toEqual('http://localhost:9876/goodRequest') + }) + + it('Send request with absolute URL', async function () { + const promise = ajax.sendRequest({ + url: 'http://test.demo/goodRequest', + cache: true + }) + expect(promise).toBeInstanceOf(Promise) + + const response = await promise + + expect(response).toBeInstanceOf(Response) + expect(jasmine.Ajax.requests.mostRecent().url).toEqual('http://test.demo/goodRequest') }) - it('Send request and update DOM with default mode', function () { + it('Send request and update DOM with default mode', async function () { $('body').append('
') const callbackSuccess = jasmine.createSpy('success') - ajax.sendRequest({ + await ajax.sendRequest({ url: '/goodRequest', onSuccess: { - callback: function (data, textStatus, jqXHR) { + callback: function (data, response) { callbackSuccess($('#ajax-result').html()) }, priority: -99 @@ -130,37 +604,37 @@ describe('Test Ajax.sendRequest', function () { update: '#ajax-result .content' }) - expect(callbackSuccess).toHaveBeenCalledWith('
OK
') + expect(callbackSuccess).toHaveBeenCalledWith('
CONTENT
') }) - it('Send request and update DOM with "update" mode', function () { - testUpdate('update', '
OK
') + it('Send request and update DOM with "update" mode', async function () { + await testUpdate('update', '
CONTENT
') }) - it('Send request and update DOM with "before" mode', function () { - testUpdate('before', 'OK
X
') + it('Send request and update DOM with "before" mode', async function () { + await testUpdate('before', 'CONTENT
X
') }) - it('Send request and update DOM with "after" mode', function () { - testUpdate('after', '
X
OK') + it('Send request and update DOM with "after" mode', async function () { + await testUpdate('after', '
X
CONTENT') }) - it('Send request and update DOM with "prepend" mode', function () { - testUpdate('prepend', '
OKX
') + it('Send request and update DOM with "prepend" mode', async function () { + await testUpdate('prepend', '
CONTENTX
') }) - it('Send request and update DOM with "append" mode', function () { - testUpdate('append', '
XOK
') + it('Send request and update DOM with "append" mode', async function () { + await testUpdate('append', '
XCONTENT
') }) - it('Send request and update DOM with bad mode', function () { + it('Send request and update DOM with bad mode', async function () { spyOn(window.console, 'error') - testUpdate('badMode', '
X
') + await testUpdate('badMode', '
X
') expect(window.console.error).toHaveBeenCalledWith('Bad updateMode: badMode') }) - it('Send request with method option', function () { - ajax.sendRequest({ + it('Send request with method option', async function () { + await ajax.sendRequest({ url: '/goodRequest', method: 'GET' }) @@ -168,65 +642,271 @@ describe('Test Ajax.sendRequest', function () { expect(jasmine.Ajax.requests.mostRecent().method).toEqual('GET') }) - it('Send request with onBeforeSend option', function () { + it('Send request with onBeforeSend option', async function () { const callbackSuccess = jasmine.createSpy('success') const callbackBeforeSend = jasmine.createSpy('beforeSend') - ajax.sendRequest({ + const promise = ajax.sendRequest({ url: '/goodRequest', onBeforeSend: function (options) { + expect(options).toBeInstanceOf(Object) + expect(options.url).toEqual('/goodRequest') callbackBeforeSend() }, - onSuccess: function (data, textStatus, jqXHR) { + onSuccess: function (data, response) { callbackSuccess() } }) + const response = await promise + + expect(response).toBeInstanceOf(Response) expect(callbackSuccess).toHaveBeenCalled() expect(callbackBeforeSend).toHaveBeenCalledBefore(callbackSuccess) }) - it('Send request canceled by onBeforeSend option', function () { + it('Send request canceled by onBeforeSend option', async function () { const callbackSuccess = jasmine.createSpy('success') const callbackBeforeSend = jasmine.createSpy('beforeSend') - ajax.sendRequest({ + const promise = ajax.sendRequest({ url: '/goodRequest', onBeforeSend: function (options) { + expect(options).toBeInstanceOf(Object) + expect(options.url).toEqual('/goodRequest') callbackBeforeSend() options.stop = true }, - onSuccess: function (data, textStatus, jqXHR) { + onSuccess: function (data, response) { + callbackSuccess() + } + }) + + const response = await promise + + expect(response).toBeNull() + expect(callbackSuccess).not.toHaveBeenCalled() + expect(callbackBeforeSend).toHaveBeenCalled() + }) + + it('Test ec-crud-ajax-before-send event', async function () { + const callbackSuccess = jasmine.createSpy('success') + const callbackBeforeSend = jasmine.createSpy('beforeSend') + + $(document).on('ec-crud-ajax-before-send', function (event) { + expect(event.detail.options).toBeInstanceOf(Object) + expect(event.detail.options.url).toEqual('/goodRequest') + callbackBeforeSend() + }) + + const promise = ajax.sendRequest({ + url: '/goodRequest', + onSuccess: function (data, response) { + callbackSuccess() + } + }) + + const response = await promise + + expect(response).toBeInstanceOf(Response) + expect(callbackSuccess).toHaveBeenCalled() + expect(callbackBeforeSend).toHaveBeenCalledBefore(callbackSuccess) + + $(document).off('ec-crud-ajax-before-send') + }) + + it('Send request canceled by ec-crud-ajax-before-send event', async function () { + const callbackSuccess = jasmine.createSpy('success') + const callbackBeforeSend = jasmine.createSpy('beforeSend') + + $(document).on('ec-crud-ajax-before-send', function (event) { + expect(event.detail.options).toBeInstanceOf(Object) + expect(event.detail.options.url).toEqual('/goodRequest') + event.preventDefault() + callbackBeforeSend() + }) + + const promise = ajax.sendRequest({ + url: '/goodRequest', + onSuccess: function (data, response) { callbackSuccess() } }) + const response = await promise + + expect(response).toBeNull() expect(callbackSuccess).not.toHaveBeenCalled() expect(callbackBeforeSend).toHaveBeenCalled() + + $(document).off('ec-crud-ajax-before-send') + }) + + it('Test ec-crud-ajax event', async function () { + const callbackSuccess = jasmine.createSpy('success') + const callbackBeginning = jasmine.createSpy('beginning') + + $(document).on('ec-crud-ajax', function (event) { + expect(event.detail.options).toBeInstanceOf(Object) + expect(event.detail.options.url).toEqual('/goodRequest') + callbackBeginning() + }) + + const promise = ajax.sendRequest({ + url: '/goodRequest', + onSuccess: function (data, response) { + callbackSuccess() + } + }) + + const response = await promise + + expect(response).toBeInstanceOf(Response) + expect(callbackSuccess).toHaveBeenCalled() + expect(callbackBeginning).toHaveBeenCalled() + + $(document).off('ec-crud-ajax') }) - it('Send request with data option', function () { - ajax.sendRequest({ + it('Send request canceled by ec-crud-ajax event', async function () { + const callbackSuccess = jasmine.createSpy('success') + const callbackBeginning = jasmine.createSpy('beginning') + + $(document).on('ec-crud-ajax', function (event) { + expect(event.detail.options).toBeInstanceOf(Object) + expect(event.detail.options.url).toEqual('/goodRequest') + event.preventDefault() + callbackBeginning() + }) + + const promise = ajax.sendRequest({ url: '/goodRequest', - data: { - var1: 'value1', - var2: 'value2' + onSuccess: function (data, response) { + callbackSuccess() } }) - expect(jasmine.Ajax.requests.mostRecent().data()).toEqual({ - var1: ['value1'], var2: ['value2'] + const response = await promise + + expect(response).toBeNull() + expect(callbackSuccess).not.toHaveBeenCalled() + expect(callbackBeginning).toHaveBeenCalled() + + $(document).off('ec-crud-ajax') + }) + + it('Send request with options changed by ec-crud-ajax event', async function () { + const callbackSuccess = jasmine.createSpy('success') + const callbackBeginning = jasmine.createSpy('beginning') + + $(document).on('ec-crud-ajax', function (event) { + expect(event.detail.options).toBeInstanceOf(Object) + expect(event.detail.options.body).toBeUndefined() + event.detail.options.body = 'BODY ADDED BY EVENT' + callbackBeginning() + }) + + const promise = ajax.sendRequest({ + url: '/goodRequest', + onSuccess: function (data, response) { + callbackSuccess() + } }) + + const response = await promise + + expect(response).toBeInstanceOf(Response) + expect(jasmine.Ajax.requests.mostRecent().params).toEqual('BODY ADDED BY EVENT') + expect(callbackSuccess).toHaveBeenCalled() + expect(callbackBeginning).toHaveBeenCalled() + + $(document).off('ec-crud-ajax') }) - it('Send request with JS in response', function () { + it('Send request with text responseDataType', async function () { + const callbackSuccess = jasmine.createSpy('success') + + await ajax.sendRequest({ + url: '/goodRequest', + responseDataType: 'text', + onSuccess: function (data, response) { + expect(data).toBeInstanceOf(String) + expect(data).toEqual('CONTENT') + expect(response).toBeInstanceOf(Response) + callbackSuccess() + } + }) + + expect(callbackSuccess).toHaveBeenCalled() + }) + + it('Send request with json responseDataType', async function () { + const callbackSuccess = jasmine.createSpy('success') + + await ajax.sendRequest({ + url: '/resultJSON', + responseDataType: 'json', + onSuccess: function (data, response) { + expect(data).not.toBeInstanceOf(String) + expect(data).toEqual({ var1: 'value1', var2: 'value2' }) + expect(response).toBeInstanceOf(Response) + callbackSuccess() + } + }) + + expect(callbackSuccess).toHaveBeenCalled() + }) + + it('Send request with json responseDataType and bad result', async function () { + const callbackSuccess = jasmine.createSpy('success') + const callbackError = jasmine.createSpy('error') + const callbackCatch = jasmine.createSpy('catch') + + await ajax.sendRequest({ + url: '/badJSON', + responseDataType: 'json', + onSuccess: function (data, response) { + callbackSuccess() + }, + onError: function (statusText, response) { + expect(statusText).toMatch(/Error during fetching response body:.+JSON\.parse/) + expect(response).toBeInstanceOf(Response) + callbackError() + } + }).catch(error => { + expect(error).toMatch(/Error during fetching response body:.+JSON\.parse/) + callbackCatch() + }) + + expect(callbackSuccess).not.toHaveBeenCalled() + expect(callbackError).toHaveBeenCalled() + expect(callbackCatch).toHaveBeenCalled() + }) + + it('Send request with no responseDataType', async function () { + const callbackSuccess = jasmine.createSpy('success') + + await ajax.sendRequest({ + url: '/goodRequest', + responseDataType: null, + onSuccess: function (data, response) { + expect(data).toBeNull() + expect(response).toBeInstanceOf(Response) + callbackSuccess() + } + }) + + expect(callbackSuccess).toHaveBeenCalled() + }) + + it('Send request with JS in response', async function () { $('body').append('
X
') const callbackSuccess = jasmine.createSpy('success') - ajax.sendRequest({ - url: '/resultJS', + await ajax.sendRequest({ + url: '/resultJavaScript', onSuccess: { - callback: function (data, textStatus, jqXHR) { + callback: function (data, response) { callbackSuccess($('#ajax-result').html()) }, priority: -99 @@ -237,14 +917,14 @@ describe('Test Ajax.sendRequest', function () { expect(callbackSuccess).toHaveBeenCalledWith('
AFTER
') }) - function testUpdate (updateMode, expectedContent) { + async function testUpdate (updateMode, expectedContent) { $('body').append('
X
') const callbackSuccess = jasmine.createSpy('success') - ajax.sendRequest({ + await ajax.sendRequest({ url: '/goodRequest', onSuccess: { - callback: function (data, textStatus, jqXHR) { + callback: function (data, response) { callbackSuccess($('#ajax-result').html()) }, priority: -99 @@ -260,10 +940,11 @@ describe('Test Ajax.sendRequest', function () { describe('Test Ajax.click', function () { beforeEach(function () { jasmine.Ajax.install() + addJasmineAjaxFormDataSupport() - jasmine.Ajax.stubRequest('/goodRequest').andReturn({ + jasmine.Ajax.stubRequest(/goodRequest/).andReturn({ status: 200, - responseText: 'OK' + responseText: 'CONTENT' }) }) @@ -273,75 +954,83 @@ describe('Test Ajax.click', function () { callbackManager.clear() }) - it('Send request with button', function () { + it('Send request with button', async function () { $('body').append('') const callbackSuccess = jasmine.createSpy('success') - ajax.click($('#buttonToTest'), { + const promise = ajax.click($('#buttonToTest'), { url: '/goodRequest', - onSuccess: function (data, textStatus, jqXHR) { + onSuccess: function (data, response) { callbackSuccess() } }) + expect(promise).toBeInstanceOf(Promise) - expect(jasmine.Ajax.requests.mostRecent().url).toBe('/goodRequest') + const response = await promise + + expect(response).toBeInstanceOf(Response) + expect(jasmine.Ajax.requests.mostRecent().url).toMatch('/goodRequest') expect(callbackSuccess).toHaveBeenCalled() }) - it('Send request with button and data-*', function () { + it('Send request with button and data-*', async function () { $('body').append('') const callbackSuccess = jasmine.createSpy('success') - callbackManager.registerCallback('my_callback_on_success', function (data, textStatus, jqXHR) { + callbackManager.registerCallback('my_callback_on_success', function (data, response) { callbackSuccess() }) - ajax.click($('#buttonToTest')) + await ajax.click($('#buttonToTest')) - expect(jasmine.Ajax.requests.mostRecent().url).toBe('/goodRequest') + expect(jasmine.Ajax.requests.mostRecent().url).toMatch('/goodRequest') expect(callbackSuccess).toHaveBeenCalled() }) - it('Send request with button and data-* and options', function () { + it('Send request with button and data-* and options', async function () { $('body').append('') callbackManager.registerCallback('my_callback_on_before_send', function (options) { @@ -363,19 +1056,38 @@ describe('Test Ajax.click', function () { $('#clickToTest').click() + await wait(() => { + return false + }, 500) + expect(jasmine.Ajax.requests.mostRecent()).toBeUndefined() $(document).off('ec-crud-ajax-click-auto-before', '#clickToTest') }) + + it('Send auto-request with button and error', async function () { + $('body').append('') $('#buttonToTest').click() + await wait(() => { + return this.spyEngine.opened + }) expect(this.spyEngine.openModal).toHaveBeenCalled() expect(this.spyEngine.closeModal).not.toHaveBeenCalled() - expect(jasmine.Ajax.requests.mostRecent().url).toBe('/goodRequest') + expect(jasmine.Ajax.requests.mostRecent().url).toMatch('/goodRequest') expect(jasmine.Ajax.requests.mostRecent().method).toBe('POST') }) - it('Test auto openRemoteModal - button - canceled', function () { + it('Test auto openRemoteModal - button - canceled', async function () { $(document).on('ec-crud-remote-modal-auto-before', '#buttonToTest', function (event) { event.preventDefault() }) $('body').append('') $('#buttonToTest').click() + await wait(() => { + return this.spyEngine.opened + }, 500) expect(this.spyEngine.openModal).not.toHaveBeenCalled() expect(this.spyEngine.closeModal).not.toHaveBeenCalled() @@ -174,12 +197,14 @@ describe('Test Modal-manager with test engine', function () { $('body').append('
') jasmine.Ajax.install() - jasmine.Ajax.stubRequest('/goodRequest').andReturn({ + jasmine.Ajax.stubRequest(/goodRequest/).andReturn({ status: 200, + response: 'OK', responseText: 'OK' }) - jasmine.Ajax.stubRequest('/error404').andReturn({ + jasmine.Ajax.stubRequest(/error404/).andReturn({ status: 404, + response: 'Page not found !', responseText: 'Page not found !' }) }) @@ -199,20 +224,27 @@ describe('Test Modal-manager with test engine', function () { expect(modalManager.getEngine()).toEqual(bootstrap3Engine) }) - it('Test openModal with onOpen and onClose options', function () { + it('Test openModal with onOpen and onClose options', async function () { const callbackOpen = jasmine.createSpy('open') const callbackClose = jasmine.createSpy('close') + let opened = false modalManager.openModal({ element: '#test-modal', onOpen: function (element) { callbackOpen(element) + opened = true }, onClose: function (element) { callbackClose(element) + opened = false } }) + await wait(() => { + return opened + }) + expect(callbackOpen).toHaveBeenCalledWith($('#test-modal')) expect(callbackClose).not.toHaveBeenCalled() @@ -224,7 +256,7 @@ describe('Test Modal-manager with test engine', function () { }) describe('Test Modal-manager.openRemoteModal', function () { - it('Test openRemoteModal', function () { + it('Test openRemoteModal', async function () { const callbackOpen = jasmine.createSpy('open') const callbackClose = jasmine.createSpy('close') @@ -240,9 +272,13 @@ describe('Test Modal-manager with test engine', function () { } }) + await wait(() => { + return $('#test-modal .content').text().length > 0 + }) + expect(callbackOpen).toHaveBeenCalledWith($('#test-modal')) expect(callbackClose).not.toHaveBeenCalled() - expect(jasmine.Ajax.requests.mostRecent().url).toBe('/goodRequest') + expect(jasmine.Ajax.requests.mostRecent().url).toMatch('/goodRequest') expect(jasmine.Ajax.requests.mostRecent().method).toBe('POST') expect($('#test-modal .content').html()).toBe('OK') }) @@ -277,7 +313,7 @@ describe('Test Modal-manager with test engine', function () { expect(jasmine.Ajax.requests.mostRecent()).toBeUndefined() }) - it('Test openRemoteModal with ajaxOptions.onSuccess', function () { + it('Test openRemoteModal with ajaxOptions.onSuccess', async function () { const callbackOpen = jasmine.createSpy('open') const callbackClose = jasmine.createSpy('close') const callbackSuccess1 = jasmine.createSpy('success1') @@ -311,16 +347,20 @@ describe('Test Modal-manager with test engine', function () { } }) + await wait(() => { + return $('#test-modal .content').text().length > 0 + }) + expect(callbackOpen).toHaveBeenCalledWith($('#test-modal')) expect(callbackSuccess1).toHaveBeenCalledBefore(callbackOpen) expect(callbackOpen).toHaveBeenCalledBefore(callbackSuccess2) expect(callbackClose).not.toHaveBeenCalled() - expect(jasmine.Ajax.requests.mostRecent().url).toBe('/goodRequest') + expect(jasmine.Ajax.requests.mostRecent().url).toMatch('/goodRequest') expect(jasmine.Ajax.requests.mostRecent().method).toBe('POST') expect($('#test-modal .content').html()).toBe('OK') }) - it('Test openRemoteModal with method option', function () { + it('Test openRemoteModal with method option', async function () { const callbackOpen = jasmine.createSpy('open') const callbackClose = jasmine.createSpy('close') @@ -337,14 +377,18 @@ describe('Test Modal-manager with test engine', function () { method: 'PUT' }) + await wait(() => { + return $('#test-modal .content').text().length > 0 + }) + expect(callbackOpen).toHaveBeenCalledWith($('#test-modal')) expect(callbackClose).not.toHaveBeenCalled() - expect(jasmine.Ajax.requests.mostRecent().url).toBe('/goodRequest') + expect(jasmine.Ajax.requests.mostRecent().url).toMatch('/goodRequest') expect(jasmine.Ajax.requests.mostRecent().method).toBe('PUT') expect($('#test-modal .content').html()).toBe('OK') }) - it('Test openRemoteModal with bad request', function () { + it('Test openRemoteModal with bad request', async function () { const callbackOpen = jasmine.createSpy('open') const callbackClose = jasmine.createSpy('close') @@ -360,9 +404,13 @@ describe('Test Modal-manager with test engine', function () { } }) + await wait(() => { + return false + }, 500) + expect(callbackOpen).not.toHaveBeenCalled() expect(callbackClose).not.toHaveBeenCalled() - expect(jasmine.Ajax.requests.mostRecent().url).toBe('/error404') + expect(jasmine.Ajax.requests.mostRecent().url).toMatch('/error404') expect(jasmine.Ajax.requests.mostRecent().method).toBe('POST') expect($('#test-modal .content').html()).toBe('') }) diff --git a/tests/assets/js/wait.js b/tests/assets/js/wait.js new file mode 100644 index 0000000..f148ec9 --- /dev/null +++ b/tests/assets/js/wait.js @@ -0,0 +1,34 @@ +/* + * This file is part of the EcommitCrudBundle package. + * + * (c) E-commit + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +export default function (stopCondition, timeout = 10000) { + const dateTimeout = Date.now() + timeout + + return new Promise(resolve => { + testCondition(stopCondition, dateTimeout, resolve) + }) +} + +function testCondition (stopCondition, dateTimeout, resolve) { + if (Date.now() > dateTimeout) { + resolve('timeout') + + return + } + + if (stopCondition()) { + resolve('ok') + + return + } + + setTimeout(() => { + testCondition(stopCondition, dateTimeout, resolve) + }, 1000) +} From c52048e0b2ebaefe2264da668d230af7b3ca7311 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Wed, 28 Dec 2022 17:28:25 +0100 Subject: [PATCH 198/264] Fix install.md --- doc/install.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/install.md b/doc/install.md index c128019..8bb0425 100644 --- a/doc/install.md +++ b/doc/install.md @@ -12,6 +12,8 @@ Prérequis : * D'utiliser un [polyfill pour FormData](https://github.com/jimmywarting/FormData) * Un gestionnaire de thème chargé par Webpack Encore parmi : * Bootstrap 3 + * Bootstrap 4 + * Bootstrap 5 * Votre thème personnalisé (créer un thème Twig qui hérite `@EcommitCrud/Theme/base.html.twig`) * Un gestionnaire d'icones chargé par Webpack Encore parmi : * Fontawesome 4 From 726df5d48cb0fb729b3cab8411e1871c5f075100 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Thu, 29 Dec 2022 11:30:14 +0100 Subject: [PATCH 199/264] Remove jquery dependency (except for tests) --- assets/js/ajax.js | 136 +++++++++----- assets/js/callback.js | 7 +- assets/js/crud.js | 149 ++++++++++------ assets/js/modal/engine/bootstrap5.js | 22 ++- assets/js/modal/engine/empty.js | 18 ++ assets/js/modal/modal-manager.js | 97 ++++++---- assets/js/options-resolver.js | 66 ++++++- doc/install.md | 2 - doc/references/ajax.md | 18 +- package.json | 4 +- tests/Functional/App/assets/js/app.js | 2 +- tests/assets/js/ajax.spec.js | 186 +++++++++++++++++--- tests/assets/js/modal/modal-manager.spec.js | 38 ++-- tests/assets/js/options-resolver.spec.js | 122 ++++++++++++- webpack-encore-config.js | 1 - 15 files changed, 652 insertions(+), 216 deletions(-) create mode 100644 assets/js/modal/engine/empty.js diff --git a/assets/js/ajax.js b/assets/js/ajax.js index 8b6b808..c3aea7d 100644 --- a/assets/js/ajax.js +++ b/assets/js/ajax.js @@ -7,44 +7,73 @@ * file that was distributed with this source code. */ -import $ from 'jquery' import * as optionsResolver from './options-resolver' import runCallback from './callback' -$(function () { - $(document).on('click', '.ec-crud-ajax-click-auto', function (event) { - event.preventDefault() - const eventBefore = $.Event('ec-crud-ajax-click-auto-before') - $(this).trigger(eventBefore) - if (eventBefore.isDefaultPrevented()) { - return +const ready = (callback) => { + if (document.readyState !== 'loading') callback() + else document.addEventListener('DOMContentLoaded', callback) +} + +ready(function () { + document.addEventListener('click', function (event) { + if (event.target.matches('.ec-crud-ajax-click-auto')) { + onClickAuto(event) } - click(this).catch((error) => console.error(error)) + if (event.target.matches('a.ec-crud-ajax-link-auto')) { + onClickLinkAuto(event) + } }) - $(document).on('click', 'a.ec-crud-ajax-link-auto', function (event) { - event.preventDefault() - const eventBefore = $.Event('ec-crud-ajax-link-auto-before') - $(this).trigger(eventBefore) - if (eventBefore.isDefaultPrevented()) { - return + document.addEventListener('submit', function (event) { + if (event.target.matches('form.ec-crud-ajax-form-auto')) { + onSubmitFormAuto(event) } + }) +}) - link(this).catch((error) => console.error(error)) +function onClickAuto (event) { + event.preventDefault() + const eventBefore = new CustomEvent('ec-crud-ajax-click-auto-before', { + bubbles: true, + cancelable: true }) + event.target.dispatchEvent(eventBefore) + if (eventBefore.defaultPrevented) { + return + } - $(document).on('submit', 'form.ec-crud-ajax-form-auto', function (event) { - event.preventDefault() - const eventBefore = $.Event('ec-crud-ajax-form-auto-before') - $(this).trigger(eventBefore) - if (eventBefore.isDefaultPrevented()) { - return - } + click(event.target).catch((error) => console.error(error)) +} - sendForm(this).catch((error) => console.error(error)) +function onClickLinkAuto (event) { + event.preventDefault() + const eventBefore = new CustomEvent('ec-crud-ajax-link-auto-before', { + bubbles: true, + cancelable: true }) -}) + event.target.dispatchEvent(eventBefore) + if (eventBefore.defaultPrevented) { + return + } + + link(event.target).catch((error) => console.error(error)) +} + +function onSubmitFormAuto (event) { + event.preventDefault() + const eventBefore = new CustomEvent('ec-crud-ajax-form-auto-before', { + bubbles: true, + cancelable: true + }) + event.target.dispatchEvent(eventBefore) + if (eventBefore.defaultPrevented) { + return + } + + sendForm(event.target).catch((error) => console.error(error)) +} export function sendRequest (options) { const eventBeginning = new CustomEvent('ec-crud-ajax', { @@ -145,7 +174,7 @@ export function sendRequest (options) { }) } - fetchOptions = $.extend(fetchOptions, options.options) + fetchOptions = optionsResolver.extend(fetchOptions, options.options) const fetchPromise = fetch(options.urlResolved, fetchOptions) const ajaxPromise = new Promise((resolve, reject) => { @@ -214,11 +243,12 @@ export function click (element, options) { } export function link (link, options) { + link = optionsResolver.getElement(link) // Options in data-* override options argument // Option argument override href options = optionsResolver.resolve( { - url: $(link).attr('href') + url: link.getAttribute('href') }, optionsResolver.resolve( options, @@ -230,13 +260,14 @@ export function link (link, options) { } export function sendForm (form, options) { + form = optionsResolver.getElement(form) // Options in data-* override options argument // Option argument override action, method and data form options = optionsResolver.resolve( { - url: $(form).attr('action'), - method: $(form).attr('method'), - body: new FormData($(form).get(0)) + url: form.getAttribute('action'), + method: form.getAttribute('method'), + body: new FormData(form) }, optionsResolver.resolve( options, @@ -248,40 +279,49 @@ export function sendForm (form, options) { } export function updateDom (element, updateMode, content) { - const eventBefore = $.Event('ec-crud-ajax-update-dom-before', { - element: element, - updateMode: updateMode, - content: content + const originElement = element + element = optionsResolver.getElement(element) + const eventBefore = new CustomEvent('ec-crud-ajax-update-dom-before', { + bubbles: true, + cancelable: true, + detail: { + element: originElement, + updateMode: updateMode, + content: content + } }) - $(element).trigger(eventBefore) - if (eventBefore.isDefaultPrevented()) { + element.dispatchEvent(eventBefore) + if (eventBefore.defaultPrevented) { return } - updateMode = eventBefore.updateMode - content = eventBefore.content + updateMode = eventBefore.detail.updateMode + content = eventBefore.detail.content if (updateMode === 'update') { - $(element).html(content) + element.innerHTML = content } else if (updateMode === 'before') { - $(element).before(content) + element.outerHTML = content + element.outerHTML } else if (updateMode === 'after') { - $(element).after(content) + element.outerHTML = element.outerHTML + content } else if (updateMode === 'prepend') { - $(element).prepend(content) + element.innerHTML = content + element.innerHTML } else if (updateMode === 'append') { - $(element).append(content) + element.innerHTML = element.innerHTML + content } else { console.error('Bad updateMode: ' + updateMode) return } - const eventAfter = $.Event('ec-crud-ajax-update-dom-after', { - element: element, - updateMode: updateMode, - content: content + const eventAfter = new CustomEvent('ec-crud-ajax-update-dom-after', { + bubbles: true, + detail: { + element: originElement, + updateMode: updateMode, + content: content + } }) - $(element).trigger(eventAfter) + element.dispatchEvent(eventAfter) } function resolveUrl (options) { diff --git a/assets/js/callback.js b/assets/js/callback.js index 0b32719..14fa7e3 100644 --- a/assets/js/callback.js +++ b/assets/js/callback.js @@ -7,7 +7,6 @@ * file that was distributed with this source code. */ -import $ from 'jquery' import * as callbackManager from './callback-manager' export default function (callbacks, ...args) { @@ -24,7 +23,7 @@ export default function (callbacks, ...args) { } const newCallbacks = [] - $.each(callbacks, function (key, value) { + callbacks.forEach((value) => { addCallbacksToStack(value, newCallbacks) }) @@ -36,7 +35,7 @@ export default function (callbacks, ...args) { return 1 }) - $.each(newCallbacks, function (key, value) { + newCallbacks.forEach((value) => { processCallback(value.callback, args) }) } @@ -48,7 +47,7 @@ function addCallbacksToStack (value, stack) { priority: 0 }) } else if (Array === value.constructor) { - $.each(value, function (key, subValue) { + value.forEach((subValue) => { addCallbacksToStack(subValue, stack) }) } else if (undefined !== value.callback) { diff --git a/assets/js/crud.js b/assets/js/crud.js index d257798..7f94e66 100644 --- a/assets/js/crud.js +++ b/assets/js/crud.js @@ -7,117 +7,162 @@ * file that was distributed with this source code. */ -import $ from 'jquery' import { click, sendForm, sendRequest, updateDom } from './ajax' import { closeModal, openModal } from './modal/modal-manager' +import { getElement } from './options-resolver' -$(document).on('submit', 'form.ec-crud-search-form', function (event) { +const ready = (callback) => { + if (document.readyState !== 'loading') callback() + else document.addEventListener('DOMContentLoaded', callback) +} + +ready(function () { + document.addEventListener('submit', function (event) { + if (event.target.matches('form.ec-crud-search-form')) { + onSubmitCrudSearchForm(event) + } + + if (event.target.matches('.ec-crud-display-settings form')) { + onCrudDisplaySettingsSubmit(event) + } + }) + + document.addEventListener('click', function (event) { + if (event.target.matches('button.ec-crud-search-reset')) { + onResetCrudSearchForm(event) + } + + if (event.target.matches('button.ec-crud-display-settings-button')) { + onCrudDisplaySettingsOpen(event) + } + + if (event.target.matches('button.ec-crud-display-settings-check-all-columns')) { + onCrudDisplaySettingsCheckAllColumns(event) + } + + if (event.target.matches('button.ec-crud-display-settings-uncheck-all-columns')) { + onCrudDisplaySettingsUncheckAllColumns(event) + } + + if (event.target.matches('button.ec-crud-display-settings-reset')) { + onCrudDisplaySettingsReset(event) + } + }) +}) + +function onSubmitCrudSearchForm (event) { event.preventDefault() - const searchId = $(this).attr('data-crud-search-id') - const listId = $(this).attr('data-crud-list-id') + const form = event.target + const searchContainer = getElement('#' + form.getAttribute('data-crud-search-id')) + const listContainer = getElement('#' + form.getAttribute('data-crud-list-id')) - sendForm(this, { + sendForm(form, { responseDataType: 'json', onSuccess: function (json, response) { - updateDom($('#' + searchId), 'update', json.render_search) - updateDom($('#' + listId), 'update', json.render_list) + updateDom(searchContainer, 'update', json.render_search) + updateDom(listContainer, 'update', json.render_list) } }) -}) +} -$(document).on('click', 'button.ec-crud-search-reset', function (event) { - const searchId = $(this).attr('data-crud-search-id') - const listId = $(this).attr('data-crud-list-id') +function onResetCrudSearchForm (event) { + const button = event.target + const searchContainer = getElement('#' + button.getAttribute('data-crud-search-id')) + const listContainer = getElement('#' + button.getAttribute('data-crud-list-id')) - click(this, { + click(button, { responseDataType: 'json', onSuccess: function (json, response) { - updateDom($('#' + searchId), 'update', json.render_search) - updateDom($('#' + listId), 'update', json.render_list) + updateDom(searchContainer, 'update', json.render_search) + updateDom(listContainer, 'update', json.render_list) } }) -}) +} -$(document).on('click', 'button.ec-crud-display-settings-button', function (event) { - const displaySettingsContainerId = $(this).attr('data-display-settings') - const isModal = $('#' + displaySettingsContainerId).attr('data-modal') === '1' +function onCrudDisplaySettingsOpen (event) { + const button = event.target + const displaySettingsContainer = getElement('#' + button.getAttribute('data-display-settings')) + const isModal = displaySettingsContainer.getAttribute('data-modal') === '1' if (isModal) { - openDisplaySettings($('#' + displaySettingsContainerId)) + openDisplaySettings(displaySettingsContainer) return } - if ($('#' + displaySettingsContainerId).is(':visible')) { - closeDisplaySettings($('#' + displaySettingsContainerId)) + if (displaySettingsContainer.offsetWidth > 0 && displaySettingsContainer.offsetHeight > 0) { // is visible ? + closeDisplaySettings(displaySettingsContainer) } else { - openDisplaySettings($('#' + displaySettingsContainerId)) + openDisplaySettings(displaySettingsContainer) } -}) +} -$(document).on('click', 'button.ec-crud-display-settings-check-all-columns', function (event) { - $(this).parents('div.ec-crud-display-settings').find('input[type=checkbox]').each(function () { - $(this).prop('checked', true) +function onCrudDisplaySettingsCheckAllColumns (event) { + const button = event.target + button.parentNode.closest('div.ec-crud-display-settings').querySelectorAll('input[type=checkbox]').forEach(checkbox => { + checkbox.checked = true }) -}) +} -$(document).on('click', 'button.ec-crud-display-settings-uncheck-all-columns', function (event) { - $(this).parents('div.ec-crud-display-settings').find('input[type=checkbox]').each(function () { - $(this).prop('checked', false) +function onCrudDisplaySettingsUncheckAllColumns (event) { + const button = event.target + button.parentNode.closest('div.ec-crud-display-settings').querySelectorAll('input[type=checkbox]').forEach(checkbox => { + checkbox.checked = false }) -}) +} -$(document).on('click', 'button.ec-crud-display-settings-reset', function (event) { - const displaySettingsContainer = $(this).parents('div.ec-crud-display-settings') - const displaySettingsContainerId = $(displaySettingsContainer).attr('id') - const listId = $(displaySettingsContainer).attr('data-crud-list-id') +function onCrudDisplaySettingsReset (event) { + const button = event.target + const displaySettingsContainer = button.parentNode.closest('div.ec-crud-display-settings') + const listContainer = getElement('#' + displaySettingsContainer.getAttribute('data-crud-list-id')) - closeDisplaySettings($('#' + displaySettingsContainerId)) + closeDisplaySettings(displaySettingsContainer) sendRequest({ - url: $(this).attr('data-reset-url'), - update: $('#' + listId) + url: button.getAttribute('data-reset-url'), + update: listContainer }) -}) +} -$(document).on('submit', '.ec-crud-display-settings form', function (event) { +function onCrudDisplaySettingsSubmit (event) { event.preventDefault() + const form = event.target - const displaySettingsContainer = $(this).parents('div.ec-crud-display-settings') - const displaySettingsContainerId = $(displaySettingsContainer).attr('id') - const listId = $(displaySettingsContainer).attr('data-crud-list-id') + const displaySettingsContainer = form.parentNode.closest('div.ec-crud-display-settings') + const listContainer = getElement('#' + displaySettingsContainer.getAttribute('data-crud-list-id')) - closeDisplaySettings($('#' + displaySettingsContainerId)) + closeDisplaySettings(displaySettingsContainer) - sendForm(this, { + sendForm(form, { responseDataType: 'json', onSuccess: function (json, response) { - updateDom($('#' + listId), 'update', json.render_list) + const displaySettingsContainerId = displaySettingsContainer.getAttribute('id') // Backup before deletion (by updateDom) + updateDom(listContainer, 'update', json.render_list) if (!json.form_is_valid) { - openDisplaySettings($('#' + displaySettingsContainerId)) + openDisplaySettings(getElement('#' + displaySettingsContainerId)) } } }) -}) +} function openDisplaySettings (displaySettingsContainer) { - const isModal = $(displaySettingsContainer).attr('data-modal') === '1' + const isModal = displaySettingsContainer.getAttribute('data-modal') === '1' if (isModal) { openModal({ element: displaySettingsContainer }) } else { - $(displaySettingsContainer).show() + displaySettingsContainer.style.display = 'block' } } function closeDisplaySettings (displaySettingsContainer) { - const isModal = $(displaySettingsContainer).attr('data-modal') === '1' + const isModal = displaySettingsContainer.getAttribute('data-modal') === '1' if (isModal) { closeModal(displaySettingsContainer) } else { - $(displaySettingsContainer).hide() + displaySettingsContainer.style.display = 'none' } } diff --git a/assets/js/modal/engine/bootstrap5.js b/assets/js/modal/engine/bootstrap5.js index 01c0c39..08e56b2 100644 --- a/assets/js/modal/engine/bootstrap5.js +++ b/assets/js/modal/engine/bootstrap5.js @@ -7,30 +7,28 @@ * file that was distributed with this source code. */ -import $ from 'jquery' import { Modal } from 'bootstrap' import runCallback from '../../callback' +import { getElement } from '../../options-resolver' export function openModal (options) { - // Suppression des événements - $(options.element).off('shown.bs.modal') - $(options.element).off('hide.bs.modal') + const element = getElement(options.element) - $(options.element).on('shown.bs.modal', function (e) { - runCallback(options.onOpen, $(options.element)) - }) + element.addEventListener('shown.bs.modal', e => { + runCallback(options.onOpen, element) + }, { once: true }) - $(options.element).on('hide.bs.modal', function (e) { - runCallback(options.onClose, $(options.element)) - }) + element.addEventListener('hide.bs.modal', e => { + runCallback(options.onClose, element) + }, { once: true }) - const modal = new Modal($(options.element), { + const modal = new Modal(element, { focus: true }) modal.show() } export function closeModal (element) { - const modal = Modal.getInstance($(element)) + const modal = Modal.getInstance(getElement(element)) modal.hide() } diff --git a/assets/js/modal/engine/empty.js b/assets/js/modal/engine/empty.js new file mode 100644 index 0000000..8865495 --- /dev/null +++ b/assets/js/modal/engine/empty.js @@ -0,0 +1,18 @@ +/* + * This file is part of the EcommitCrudBundle package. + * + * (c) E-commit + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import runCallback from '../../callback' + +export function openModal (options) { + runCallback(options.onOpen, options.element) +} + +export function closeModal (element) { + runCallback(element) +} diff --git a/assets/js/modal/modal-manager.js b/assets/js/modal/modal-manager.js index f5f9123..ac3de0b 100644 --- a/assets/js/modal/modal-manager.js +++ b/assets/js/modal/modal-manager.js @@ -8,7 +8,6 @@ */ import * as optionsResolver from '../options-resolver' -import $ from 'jquery' import * as ajax from '../ajax' const ENGINE_KEY = Symbol.for('ecommit.crudbundle.modalengine') @@ -17,48 +16,76 @@ if (globalSymbols.indexOf(ENGINE_KEY) === -1) { global[ENGINE_KEY] = null } -$(function () { - $(document).on('click', '.ec-crud-modal-auto', function (event) { - event.preventDefault() - const eventBefore = $.Event('ec-crud-modal-auto-before') - $(this).trigger(eventBefore) - if (eventBefore.isDefaultPrevented()) { - return +const ready = (callback) => { + if (document.readyState !== 'loading') callback() + else document.addEventListener('DOMContentLoaded', callback) +} + +ready(function () { + document.addEventListener('click', function (event) { + if (event.target.matches('.ec-crud-modal-auto')) { + onClickModalAuto(event) } - openModal(optionsResolver.getDataAttributes(this, 'ecCrudModal')) - }) + if (event.target.matches('button.ec-crud-remote-modal-auto')) { + onClickButtonRemoteModalAuto(event) + } - $(document).on('click', 'button.ec-crud-remote-modal-auto', function (event) { - event.preventDefault() - const eventBefore = $.Event('ec-crud-remote-modal-auto-before') - $(this).trigger(eventBefore) - if (eventBefore.isDefaultPrevented()) { - return + if (event.target.matches('a.ec-crud-remote-modal-auto')) { + onClickLinkRemoteModalAuto(event) } + }) +}) - openRemoteModal(optionsResolver.getDataAttributes(this, 'ecCrudModal')) +function onClickModalAuto (event) { + event.preventDefault() + const eventBefore = new CustomEvent('ec-crud-modal-auto-before', { + bubbles: true, + cancelable: true }) + event.target.dispatchEvent(eventBefore) + if (eventBefore.defaultPrevented) { + return + } - $(document).on('click', 'a.ec-crud-remote-modal-auto', function (event) { - event.preventDefault() - const eventBefore = $.Event('ec-crud-remote-modal-auto-before') - $(this).trigger(eventBefore) - if (eventBefore.isDefaultPrevented()) { - return - } + openModal(optionsResolver.getDataAttributes(event.target, 'ecCrudModal')) +} + +function onClickButtonRemoteModalAuto (event) { + event.preventDefault() + const eventBefore = new CustomEvent('ec-crud-remote-modal-auto-before', { + bubbles: true, + cancelable: true + }) + event.target.dispatchEvent(eventBefore) + if (eventBefore.defaultPrevented) { + return + } - // Options in data-* override href - const options = optionsResolver.resolve( - { - url: $(this).attr('href') - }, - optionsResolver.getDataAttributes(this, 'ecCrudModal') - ) + openRemoteModal(optionsResolver.getDataAttributes(event.target, 'ecCrudModal')) +} - openRemoteModal(options) +function onClickLinkRemoteModalAuto (event) { + event.preventDefault() + const eventBefore = new CustomEvent('ec-crud-remote-modal-auto-before', { + bubbles: true, + cancelable: true }) -}) + event.target.dispatchEvent(eventBefore) + if (eventBefore.defaultPrevented) { + return + } + + // Options in data-* override href + const options = optionsResolver.resolve( + { + url: event.target.getAttribute('href') + }, + optionsResolver.getDataAttributes(event.target, 'ecCrudModal') + ) + + openRemoteModal(options) +} export function defineEngine (newEngine) { global[ENGINE_KEY] = newEngine @@ -107,8 +134,8 @@ export function openRemoteModal (options) { options ) - let hasError = false - $.each(['url', 'element', 'elementContent', 'method'], function (index, value) { + let hasError = false; + ['url', 'element', 'elementContent', 'method'].forEach((value) => { if (optionsResolver.isNotBlank(options[value]) === false) { console.error('Value required: ' + value) hasError = true diff --git a/assets/js/options-resolver.js b/assets/js/options-resolver.js index b96e928..6425220 100644 --- a/assets/js/options-resolver.js +++ b/assets/js/options-resolver.js @@ -7,29 +7,56 @@ * file that was distributed with this source code. */ -import $ from 'jquery' - export function resolve (defaultOptions, options) { Object.keys(options).forEach(key => options[key] === undefined ? delete options[key] : {}) - return $.extend({}, defaultOptions, options) + return extend({}, defaultOptions, options) } export function getDataAttributes (element, prefix) { const prefixLength = prefix.length const attributes = {} - $.each($(element).data(), function (index, value) { + element = getElement(element) + if (!element) { + return attributes + } + + Object.entries(element.dataset).forEach((property) => { + const index = property[0] + const value = property[1] if (index.length > prefixLength && index.substr(0, prefixLength) === prefix) { let newIndex = index.substr(prefixLength) newIndex = newIndex.charAt(0).toLowerCase() + newIndex.slice(1) - attributes[newIndex] = value + attributes[newIndex] = transformDataValue(value) } }) return attributes } +function transformDataValue (value) { + if (value.length === 0) { + return null + } + + if (/^\d+$/.test(value)) { + return parseInt(value, 10) + } else if (/^true$/i.test(value)) { + return true + } else if (/^false$/i.test(value)) { + return false + } else if (/^[[{]/i.test(value)) { + try { + return JSON.parse(value) + } catch (e) { + return value + } + } + + return value +} + export function isNotBlank (value) { if (undefined === value || value === null || value.length === 0) { return false @@ -37,3 +64,32 @@ export function isNotBlank (value) { return true } + +export function getElement (element) { + if (element === null) { + return null + } else if (typeof element === 'string' || element instanceof String) { + return document.querySelector(element) + } else if (element instanceof Element) { + return element + } else if (typeof element === 'object' && element.jquery !== undefined) { + const elementByJquery = element.get(0) + if (elementByJquery instanceof Element) { + return elementByJquery + } + } + + return null +} + +export function extend () { + for (let i = 1; i < arguments.length; i++) { + for (const key in arguments[i]) { + if (Object.prototype.hasOwnProperty.call(arguments[i], key)) { + arguments[0][key] = arguments[i][key] + } + } + } + + return arguments[0] +} diff --git a/doc/install.md b/doc/install.md index 8bb0425..528c6e7 100644 --- a/doc/install.md +++ b/doc/install.md @@ -5,11 +5,9 @@ Prérequis : * Entité utilisateur avec Doctrine * Yarn * Webpack Encore -* jQuery * Pour une compatibilité avec un maximum de navigateurs Internet, il est conseillé : * De configurer Webpack Encore pour activer Babel et core-js * D'utiliser un [polyfill pour Fetch](https://github.com/github/fetch) - * D'utiliser un [polyfill pour FormData](https://github.com/jimmywarting/FormData) * Un gestionnaire de thème chargé par Webpack Encore parmi : * Bootstrap 3 * Bootstrap 4 diff --git a/doc/references/ajax.md b/doc/references/ajax.md index f2c365d..1769965 100644 --- a/doc/references/ajax.md +++ b/doc/references/ajax.md @@ -200,10 +200,14 @@ ajax.updateDom($('#myDiv'), 'update', 'Hello world'); Méthodes disponibles pour la mise à jour : -| Méthode | Description | -| ------- | ----------- | -| update | Utilise le fonction [`html` de jQuery](https://api.jquery.com/html/) | -| before | Utilise le fonction [`before` de jQuery](https://api.jquery.com/before/) | -| after | Utilise le fonction [`after` de jQuery](https://api.jquery.com/after/) | -| prepend | Utilise le fonction [`prepend` de jQuery](https://api.jquery.com/prepend/) | -| append | Utilise le fonction [`append` de jQuery](https://api.jquery.com/append/) | +| Méthode | Description | +|---------|-------------------------------------------------------------------------| +| update | Modifie le contenu de l'élément par le nouveau contenu | +| before | Ajoute le nouveau contenu avant l'élément | +| after | Ajoute le nouveau contenu après l'élément | +| prepend | Modifie le contenu de l'élément en ajoutant le nouveau contenu au début | +| append | Modifie le contenu de l'élément en ajoutant le nouveau contenu à la fin | + +La mise à jour du DOM utilise la fonction [innerHTML](https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML). +Si le nouveau contenu contient des balises `') + expect(callbackSuccess).toHaveBeenCalledWith('
BEFORE
') }) async function testUpdate (updateMode, expectedContent) { @@ -959,6 +959,46 @@ describe('Test Ajax.click', function () { const callbackSuccess = jasmine.createSpy('success') + const promise = ajax.click('#buttonToTest', { + url: '/goodRequest', + onSuccess: function (data, response) { + callbackSuccess() + } + }) + expect(promise).toBeInstanceOf(Promise) + + const response = await promise + + expect(response).toBeInstanceOf(Response) + expect(jasmine.Ajax.requests.mostRecent().url).toMatch('/goodRequest') + expect(callbackSuccess).toHaveBeenCalled() + }) + + it('Send request with button and Element', async function () { + $('body').append('') + + const callbackSuccess = jasmine.createSpy('success') + + const promise = ajax.click(document.querySelector('#buttonToTest'), { + url: '/goodRequest', + onSuccess: function (data, response) { + callbackSuccess() + } + }) + expect(promise).toBeInstanceOf(Promise) + + const response = await promise + + expect(response).toBeInstanceOf(Response) + expect(jasmine.Ajax.requests.mostRecent().url).toMatch('/goodRequest') + expect(callbackSuccess).toHaveBeenCalled() + }) + + it('Send request with button and jQuery', async function () { + $('body').append('') + + const callbackSuccess = jasmine.createSpy('success') + const promise = ajax.click($('#buttonToTest'), { url: '/goodRequest', onSuccess: function (data, response) { @@ -983,7 +1023,7 @@ describe('Test Ajax.click', function () { callbackSuccess() }) - await ajax.click($('#buttonToTest')) + await ajax.click('#buttonToTest') expect(jasmine.Ajax.requests.mostRecent().url).toMatch('/goodRequest') expect(callbackSuccess).toHaveBeenCalled() @@ -1000,7 +1040,7 @@ describe('Test Ajax.click', function () { callbackSuccess1() }) - await ajax.click($('#buttonToTest'), { + await ajax.click('#buttonToTest', { url: '/badRequest', // overridden by data-ec-crud-ajax-url method: 'GET', // overridden by data-ec-crud-ajax-method onSuccess: function (data, response) { // overridden by data-ec-crud-ajax-on-success @@ -1021,7 +1061,7 @@ describe('Test Ajax.click', function () { it('Send auto-request with button', async function () { $('body').append('') - $('#clickToTest').click() + $('#clickToTest').get(0).click() await wait(() => { return false @@ -1054,7 +1094,7 @@ describe('Test Ajax.click', function () { options.stop = true }) - $('#clickToTest').click() + $('#clickToTest').get(0).click() await wait(() => { return false @@ -1069,7 +1109,7 @@ describe('Test Ajax.click', function () { $('body').append('') $('#formToTest input[name=var1]').val('My value 1') $('#formToTest input[name=var2]').val('My value 2') - $('#formToTest').submit() + $('#formToTest button[type="submit"]').get(0).click() await wait(() => { return false @@ -1355,11 +1479,11 @@ describe('Test Ajax.form', function () { $(document).on('ec-crud-ajax-form-auto-before', '#formToTest', function (event) { event.preventDefault() }) - $('body').append('
') + $('body').append('
') $('#formToTest input[name=var1]').val('My value 1') $('#formToTest input[name=var2]').val('My value 2') - $('#formToTest').submit() + $('#formToTest button[type="submit"]').get(0).click() await wait(() => { return false @@ -1371,10 +1495,10 @@ describe('Test Ajax.form', function () { }) it('Send auto-request with form and error', async function () { - $('body').append('
') + $('body').append('
') spyOn(window.console, 'error') - $('#formToTest').submit() + $('#formToTest button[type="submit"]').get(0).click() await wait(() => { return false @@ -1419,7 +1543,7 @@ describe('Test Ajax.updateDom', function () { it('Update with "update" mode and ec-crud-ajax-update-dom-before event - change updateMode', function () { $(document).on('ec-crud-ajax-update-dom-before', function (event) { - event.updateMode = 'append' + event.detail.updateMode = 'append' }) testUpdateDom('update', '
XOK
') @@ -1427,7 +1551,7 @@ describe('Test Ajax.updateDom', function () { it('Update with "update" mode and ec-crud-ajax-update-dom-before event - change content', function () { $(document).on('ec-crud-ajax-update-dom-before', function (event) { - event.content = 'NEW OK' + event.detail.content = 'NEW OK' }) testUpdateDom('update', '
NEW OK
') @@ -1443,7 +1567,7 @@ describe('Test Ajax.updateDom', function () { it('Update with "update" mode and ec-crud-ajax-update-dom-after event', function () { $(document).on('ec-crud-ajax-update-dom-after', function (event) { - $(event.element).find('.content').html('OK') + $(event.detail.element).find('.content').html('OK') }) ajax.updateDom('#container .content', 'update', '
') @@ -1482,6 +1606,16 @@ describe('Test Ajax.updateDom', function () { expect(window.console.error).toHaveBeenCalledWith('Bad updateMode: badMode') expect($('#container').html()).toEqual('
X
') }) + + it('Update with "update" mode and Element', function () { + ajax.updateDom(document.querySelector('#container .content'), 'update', 'OK') + expect($('#container').html()).toEqual('
OK
') + }) + + it('Update with "update" mode and jQuery', function () { + ajax.updateDom($('#container .content'), 'update', 'OK') + expect($('#container').html()).toEqual('
OK
') + }) }) function addJasmineAjaxFormDataSupport () { diff --git a/tests/assets/js/modal/modal-manager.spec.js b/tests/assets/js/modal/modal-manager.spec.js index 747b9da..bdc8418 100644 --- a/tests/assets/js/modal/modal-manager.spec.js +++ b/tests/assets/js/modal/modal-manager.spec.js @@ -26,6 +26,7 @@ it('Get engine when not defined', function () { describe('Test Modal-manager with spy engine', function () { beforeEach(function () { + $('body').append('
') this.spyEngine = { opened: false, openModal: function (options) { @@ -50,12 +51,13 @@ describe('Test Modal-manager with spy engine', function () { afterEach(function () { $('.html-test').remove() + $('#test-modal').remove() jasmine.Ajax.uninstall() }) it('Test openModal', function () { modalManager.openModal({ - element: '#myId' + element: '#test-modal' }) expect(this.spyEngine.openModal).toHaveBeenCalled() @@ -69,16 +71,16 @@ describe('Test Modal-manager with spy engine', function () { }) it('Test closeModal', function () { - modalManager.closeModal('#myId') + modalManager.closeModal('#test-modal') expect(this.spyEngine.openModal).not.toHaveBeenCalled() expect(this.spyEngine.closeModal).toHaveBeenCalled() }) it('Test auto openModal', function () { - $('body').append('Go !') + $('body').append('Go !') - $('#linkToTest').click() + $('#linkToTest').get(0).click() expect(this.spyEngine.openModal).toHaveBeenCalled() expect(this.spyEngine.closeModal).not.toHaveBeenCalled() @@ -88,9 +90,9 @@ describe('Test Modal-manager with spy engine', function () { $(document).on('ec-crud-modal-auto-before', '#linkToTest', function (event) { event.preventDefault() }) - $('body').append('Go !') + $('body').append('Go !') - $('#linkToTest').click() + $('#linkToTest').get(0).click() expect(this.spyEngine.openModal).not.toHaveBeenCalled() expect(this.spyEngine.closeModal).not.toHaveBeenCalled() @@ -99,9 +101,9 @@ describe('Test Modal-manager with spy engine', function () { }) it('Test auto openRemoteModal - link', async function () { - $('body').append('Go !') + $('body').append('Go !') - $('#linkToTest').click() + $('#linkToTest').get(0).click() await wait(() => { return this.spyEngine.opened }) @@ -113,9 +115,9 @@ describe('Test Modal-manager with spy engine', function () { }) it('Test auto openRemoteModal - link with href', async function () { - $('body').append('Go !') + $('body').append('Go !') - $('#linkToTest').click() + $('#linkToTest').get(0).click() await wait(() => { return this.spyEngine.opened }) @@ -127,9 +129,9 @@ describe('Test Modal-manager with spy engine', function () { }) it('Test auto openRemoteModal - link - prioriry url attr', async function () { - $('body').append('Go !') + $('body').append('Go !') - $('#linkToTest').click() + $('#linkToTest').get(0).click() await wait(() => { return this.spyEngine.opened }) @@ -144,9 +146,9 @@ describe('Test Modal-manager with spy engine', function () { $(document).on('ec-crud-remote-modal-auto-before', '#linkToTest', function (event) { event.preventDefault() }) - $('body').append('Go !') + $('body').append('Go !') - $('#linkToTest').click() + $('#linkToTest').get(0).click() await wait(() => { return this.spyEngine.opened }, 500) @@ -159,9 +161,9 @@ describe('Test Modal-manager with spy engine', function () { }) it('Test auto openRemoteModal - button', async function () { - $('body').append('') + $('body').append('') - $('#buttonToTest').click() + $('#buttonToTest').get(0).click() await wait(() => { return this.spyEngine.opened }) @@ -176,9 +178,9 @@ describe('Test Modal-manager with spy engine', function () { $(document).on('ec-crud-remote-modal-auto-before', '#buttonToTest', function (event) { event.preventDefault() }) - $('body').append('') + $('body').append('') - $('#buttonToTest').click() + $('#buttonToTest').get(0).click() await wait(() => { return this.spyEngine.opened }, 500) diff --git a/tests/assets/js/options-resolver.spec.js b/tests/assets/js/options-resolver.spec.js index dd2dbb6..bb9ccae 100644 --- a/tests/assets/js/options-resolver.spec.js +++ b/tests/assets/js/options-resolver.spec.js @@ -75,7 +75,7 @@ describe('Test options-resolver.getDataAttributes', function () { }) it('Element with different data types', function () { - $('body').append('
') + $('body').append('
') const expected = { var1: 'value1', @@ -85,11 +85,28 @@ describe('Test options-resolver.getDataAttributes', function () { var5: { result: true, count: 100 - } + }, + var6: true, + var7: '[a', + var8: null } expect(optionsRevolser.getDataAttributes('#myDiv', 'myPrefix')).toEqual(expected) }) + + it('Element with Element', function () { + $('body').append('
') + const element = document.querySelector('#myDiv') + + expect(optionsRevolser.getDataAttributes(element, 'myPrefix')).toEqual({ var1: 'value1' }) + }) + + it('Element with jQuery', function () { + $('body').append('
') + const element = $('#myDiv') + + expect(optionsRevolser.getDataAttributes(element, 'myPrefix')).toEqual({ var1: 'value1' }) + }) }) describe('Test options-resolver.isNotBlank', function () { @@ -121,3 +138,104 @@ describe('Test options-resolver.isNotBlank', function () { expect(optionsRevolser.isNotBlank(['val'])).toBe(true) }) }) + +describe('Test options-resolver.getElement', function () { + beforeEach(function () { + $('body').append('
') + }) + + afterEach(function () { + $('.html-test').remove() + }) + + it('Test with null', function () { + const result = optionsRevolser.getElement(null) + + expect(result).toBeNull() + }) + + it('Test with string - found', function () { + const result = optionsRevolser.getElement('#myDiv1') + + expect(result).toBeInstanceOf(Element) + expect(result.getAttribute('id')).toEqual('myDiv1') + }) + + it('Test with string - found (multiple)', function () { + const result = optionsRevolser.getElement('.myDiv') + + expect(result).toBeInstanceOf(Element) + expect(result.getAttribute('id')).toEqual('myDiv1') + }) + + it('Test with string - not found', function () { + const result = optionsRevolser.getElement('#myDiv3') + + expect(result).toBeNull() + }) + + it('Test with element', function () { + const element = document.querySelector('#myDiv1') + const result = optionsRevolser.getElement(element) + + expect(result).toBeInstanceOf(Element) + expect(result.getAttribute('id')).toEqual('myDiv1') + expect(result).toBe(element) + }) + + // If "jquery" dependency deleted in the future, the "Test with fake jQuery" test must be kept (replacement test) + it('Test with jQuery', function () { + const result = optionsRevolser.getElement($('#myDiv1')) + + expect(result).toBeInstanceOf(Element) + expect(result.getAttribute('id')).toEqual('myDiv1') + }) + + // If "jquery" dependency deleted in the future, the "Test with fake empty jQuery" test must be kept (replacement test) + it('Test with empty jQuery', function () { + const result = optionsRevolser.getElement($('#notFound')) + + expect(result).toBeNull() + }) + + it('Test with fake jQuery', function () { + const object = { + get: function (index) { + if (index === 0) { + return document.querySelector('#myDiv1') + } + + return undefined + }, + jquery: 'fake' + } + const result = optionsRevolser.getElement(object) + + expect(result).toBeInstanceOf(Element) + expect(result.getAttribute('id')).toEqual('myDiv1') + }) + + it('Test with fake empty jQuery', function () { + const object = { + get: function (index) { + return undefined + }, + jquery: 'fake' + } + const result = optionsRevolser.getElement(object) + + expect(result).toBeNull() + }) + + it('Test with other type', function () { + const result = optionsRevolser.getElement({}) + + expect(result).toBeNull() + }) + + it('Test with undefined', function () { + const result = optionsRevolser.getElement(undefined) + + expect(result).toBeNull() + }) +}) diff --git a/webpack-encore-config.js b/webpack-encore-config.js index e9ee59d..3ece1a5 100644 --- a/webpack-encore-config.js +++ b/webpack-encore-config.js @@ -19,7 +19,6 @@ module.exports = function (outputPath) { config.useBuiltIns = 'usage'; config.corejs = 3; }) - .autoProvidejQuery() ; return Encore; From 82cf0e2da2da961cd931bdba9dd797aa6087501d Mon Sep 17 00:00:00 2001 From: hlecorche Date: Fri, 30 Dec 2022 20:34:37 +0100 Subject: [PATCH 200/264] Add missing functional tests --- .../Controller/TestCrudControllerTest.php | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tests/Functional/Controller/TestCrudControllerTest.php b/tests/Functional/Controller/TestCrudControllerTest.php index 8b7565f..34c2ab7 100644 --- a/tests/Functional/Controller/TestCrudControllerTest.php +++ b/tests/Functional/Controller/TestCrudControllerTest.php @@ -90,10 +90,12 @@ public function testChangeDisplayedColumns(Client $client): Client { $button = $client->getCrawler()->filterXPath('//button[contains(.,"Display Settings")]'); $button->first()->click(); + $client->waitForVisibility('#ec-crud-display-settings-'.static::SESSION_NAME); $client->getCrawler()->filterXPath('//form[@name="crud_display_settings_'.static::SESSION_NAME.'"]/descendant::input[@value="username"]')->click(); $client->getCrawler()->filterXPath('//form[@name="crud_display_settings_'.static::SESSION_NAME.'"]/descendant::button[@type="submit"]')->click(); $this->waitForAjax($client); + $client->waitForInvisibility('#ec-crud-display-settings-'.static::SESSION_NAME); $this->assertSame([5, 3], $this->countRowsAndColumns($client->getCrawler())); $this->assertSame([1, 3], $this->getPagination($client->getCrawler())); @@ -308,6 +310,44 @@ public function testSessionValuesAfterResetSettings(Client $client): Client /** * @depends testSessionValuesAfterResetSettings */ + public function testCheckAndUncheckAllColumns(Client $client): Client + { + $client->request('GET', static::URL); + + $button = $client->getCrawler()->filterXPath('//button[contains(.,"Display Settings")]'); + $button->first()->click(); + $form = $client->getCrawler()->selectButton('Save')->form(); + $this->assertCount(2, $form->get('crud_display_settings_'.static::SESSION_NAME.'[displayedColumns]')->getValue()); + + // Check all + $client->getCrawler()->filterXPath('//form[@name="crud_display_settings_'.static::SESSION_NAME.'"]/descendant::button[contains(.,"Check all")]')->click(); + $client->wait(5, 30)->until(fn () => 3 === \count($form->get('crud_display_settings_'.static::SESSION_NAME.'[displayedColumns]')->getValue())); + $this->assertCount(3, $form->get('crud_display_settings_'.static::SESSION_NAME.'[displayedColumns]')->getValue()); + + // Uncheck all + $client->getCrawler()->filterXPath('//form[@name="crud_display_settings_'.static::SESSION_NAME.'"]/descendant::button[contains(.,"Uncheck all")]')->click(); + $client->wait(5, 30)->until(fn () => null === $form->get('crud_display_settings_'.static::SESSION_NAME.'[displayedColumns]')->getValue()); + $this->assertNull($form->get('crud_display_settings_'.static::SESSION_NAME.'[displayedColumns]')->getValue()); + + // Save and error (save not done) + $client->getCrawler()->filterXPath('//form[@name="crud_display_settings_'.static::SESSION_NAME.'"]/descendant::button[@type="submit"]')->click(); + $this->waitForAjax($client); + $client->waitForVisibility('#ec-crud-display-settings-'.static::SESSION_NAME); + $this->assertCount(1, $client->getCrawler()->filterXPath('//div[@id="ec-crud-display-settings-'.static::SESSION_NAME.'"]/descendant::li[contains(text(), "This collection should contain 1 element or more")]')); + $this->assertSame([5, 2], $this->countRowsAndColumns($client->getCrawler())); + $this->assertSame([1, 3], $this->getPagination($client->getCrawler())); + + // Save not done after reload + $client->request('GET', static::URL); + $this->assertSame([5, 2], $this->countRowsAndColumns($client->getCrawler())); + $this->assertSame([1, 3], $this->getPagination($client->getCrawler())); + + return $client; + } + + /** + * @depends testCheckAndUncheckAllColumns + */ public function testManualReset(Client $client): Client { $client->request('GET', static::URL); From b4241d34e0b9f1f77313e4dd65d350e2357c5ca0 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Fri, 30 Dec 2022 21:20:19 +0100 Subject: [PATCH 201/264] Replace CSS classes by data-ec-crud- attributes .ec-crud-modal-auto -> data-ec-crud-toggle="modal-auto" .ec-crud-remote-modal-auto -> data-ec-crud-toggle="remote-modal-auto" .ec-crud-ajax-click-auto -> data-ec-crud-toggle="ajax-click" .ec-crud-ajax-link-auto -> data-ec-crud-toggle="ajax-link" .ec-crud-ajax-form-auto -> data-ec-crud-toggle="ajax-form" .ec-crud-search-form -> data-ec-crud-toggle="search-form" .ec-crud-search-reset -> data-ec-crud-toggle="search-reset" .ec-crud-display-settings -> data-ec-crud-toggle="display-settings" .ec-crud-display-settings-button -> data-ec-crud-toggle="display-settings-button" .ec-crud-display-settings-check-all-columns -> data-ec-crud-toggle="display-settings-check-all-columns" .ec-crud-display-settings-uncheck-all-columns -> data-ec-crud-toggle="display-settings-uncheck-all-columns" .ec-crud-display-settings-reset -> data-ec-crud-toggle="display-settings-reset" --- assets/js/ajax.js | 6 +- assets/js/crud.js | 22 +++---- assets/js/modal/modal-manager.js | 6 +- doc/references/ajax.md | 12 ++-- doc/references/modal.md | 10 +-- src/Form/Type/DisplaySettingsType.php | 2 +- src/Twig/CrudExtension.php | 22 ++----- templates/Theme/base.html.twig | 8 +-- templates/Theme/bootstrap3.html.twig | 8 +-- templates/Theme/bootstrap4.html.twig | 8 +-- templates/Theme/bootstrap5.html.twig | 8 +-- tests/Twig/CrudExtensionTest.php | 68 +++++---------------- tests/assets/js/ajax.spec.js | 20 +++--- tests/assets/js/modal/modal-manager.spec.js | 16 ++--- 14 files changed, 83 insertions(+), 133 deletions(-) diff --git a/assets/js/ajax.js b/assets/js/ajax.js index c3aea7d..0a7e2b1 100644 --- a/assets/js/ajax.js +++ b/assets/js/ajax.js @@ -17,17 +17,17 @@ const ready = (callback) => { ready(function () { document.addEventListener('click', function (event) { - if (event.target.matches('.ec-crud-ajax-click-auto')) { + if (event.target.matches('[data-ec-crud-toggle="ajax-click"]')) { onClickAuto(event) } - if (event.target.matches('a.ec-crud-ajax-link-auto')) { + if (event.target.matches('a[data-ec-crud-toggle="ajax-link"]')) { onClickLinkAuto(event) } }) document.addEventListener('submit', function (event) { - if (event.target.matches('form.ec-crud-ajax-form-auto')) { + if (event.target.matches('form[data-ec-crud-toggle="ajax-form"]')) { onSubmitFormAuto(event) } }) diff --git a/assets/js/crud.js b/assets/js/crud.js index 7f94e66..af336a6 100644 --- a/assets/js/crud.js +++ b/assets/js/crud.js @@ -18,33 +18,33 @@ const ready = (callback) => { ready(function () { document.addEventListener('submit', function (event) { - if (event.target.matches('form.ec-crud-search-form')) { + if (event.target.matches('form[data-ec-crud-toggle="search-form"]')) { onSubmitCrudSearchForm(event) } - if (event.target.matches('.ec-crud-display-settings form')) { + if (event.target.matches('[data-ec-crud-toggle="display-settings"] form')) { onCrudDisplaySettingsSubmit(event) } }) document.addEventListener('click', function (event) { - if (event.target.matches('button.ec-crud-search-reset')) { + if (event.target.matches('button[data-ec-crud-toggle="search-reset"]')) { onResetCrudSearchForm(event) } - if (event.target.matches('button.ec-crud-display-settings-button')) { + if (event.target.matches('button[data-ec-crud-toggle="display-settings-button"]')) { onCrudDisplaySettingsOpen(event) } - if (event.target.matches('button.ec-crud-display-settings-check-all-columns')) { + if (event.target.matches('button[data-ec-crud-toggle="display-settings-check-all-columns"]')) { onCrudDisplaySettingsCheckAllColumns(event) } - if (event.target.matches('button.ec-crud-display-settings-uncheck-all-columns')) { + if (event.target.matches('button[data-ec-crud-toggle="display-settings-uncheck-all-columns"]')) { onCrudDisplaySettingsUncheckAllColumns(event) } - if (event.target.matches('button.ec-crud-display-settings-reset')) { + if (event.target.matches('button[data-ec-crud-toggle="display-settings-reset"]')) { onCrudDisplaySettingsReset(event) } }) @@ -100,21 +100,21 @@ function onCrudDisplaySettingsOpen (event) { function onCrudDisplaySettingsCheckAllColumns (event) { const button = event.target - button.parentNode.closest('div.ec-crud-display-settings').querySelectorAll('input[type=checkbox]').forEach(checkbox => { + button.parentNode.closest('div[data-ec-crud-toggle="display-settings"]').querySelectorAll('input[type=checkbox]').forEach(checkbox => { checkbox.checked = true }) } function onCrudDisplaySettingsUncheckAllColumns (event) { const button = event.target - button.parentNode.closest('div.ec-crud-display-settings').querySelectorAll('input[type=checkbox]').forEach(checkbox => { + button.parentNode.closest('div[data-ec-crud-toggle="display-settings"]').querySelectorAll('input[type=checkbox]').forEach(checkbox => { checkbox.checked = false }) } function onCrudDisplaySettingsReset (event) { const button = event.target - const displaySettingsContainer = button.parentNode.closest('div.ec-crud-display-settings') + const displaySettingsContainer = button.parentNode.closest('div[data-ec-crud-toggle="display-settings"]') const listContainer = getElement('#' + displaySettingsContainer.getAttribute('data-crud-list-id')) closeDisplaySettings(displaySettingsContainer) @@ -129,7 +129,7 @@ function onCrudDisplaySettingsSubmit (event) { event.preventDefault() const form = event.target - const displaySettingsContainer = form.parentNode.closest('div.ec-crud-display-settings') + const displaySettingsContainer = form.parentNode.closest('div[data-ec-crud-toggle="display-settings"]') const listContainer = getElement('#' + displaySettingsContainer.getAttribute('data-crud-list-id')) closeDisplaySettings(displaySettingsContainer) diff --git a/assets/js/modal/modal-manager.js b/assets/js/modal/modal-manager.js index ac3de0b..6fd4b05 100644 --- a/assets/js/modal/modal-manager.js +++ b/assets/js/modal/modal-manager.js @@ -23,15 +23,15 @@ const ready = (callback) => { ready(function () { document.addEventListener('click', function (event) { - if (event.target.matches('.ec-crud-modal-auto')) { + if (event.target.matches('[data-ec-crud-toggle="modal"]')) { onClickModalAuto(event) } - if (event.target.matches('button.ec-crud-remote-modal-auto')) { + if (event.target.matches('button[data-ec-crud-toggle="remote-modal"]')) { onClickButtonRemoteModalAuto(event) } - if (event.target.matches('a.ec-crud-remote-modal-auto')) { + if (event.target.matches('a[data-ec-crud-toggle="remote-modal"]')) { onClickLinkRemoteModalAuto(event) } }) diff --git a/doc/references/ajax.md b/doc/references/ajax.md index 1769965..984b04f 100644 --- a/doc/references/ajax.md +++ b/doc/references/ajax.md @@ -63,7 +63,7 @@ La fonction `sendRequest` retourne une promesse. Fonction permettant de faire une requête AJAX lors d'un clic sur élément du DOM. -L'élément du DOM doit avoir comme classe CSS `ec-crud-ajax-click-auto`. +L'élément du DOM doit avoir l'attribut HTML `data-ec-crud-toggle="ajax-click"`. Toutes les options de la fonction `sendRequest` peuvent être utilisées en les passant par les attributs `data-`. Pour cela: * Préfixer chaque option par `data-ec-crud-ajax-` @@ -73,7 +73,7 @@ Exemple: L'équivalent de l'option `updateMode` est `data-ec-crud-ajax-update-mo Exemple : ```html - + ``` ### link @@ -87,12 +87,12 @@ Toutes les options de la fonction `sendRequest` peuvent être utilisées en les #### Mode automatique -Le lien doit avoir comme classe CSS `ec-crud-ajax-link-auto`. +Le lien doit avoir l'attribut HTML `data-ec-crud-toggle="ajax-link"`. Exemple : ```html -Go ! +Go ! ``` L'URL utilisée pour la requête Ajax est: @@ -134,12 +134,12 @@ Toutes les options de la fonction `sendRequest` peuvent être utilisées en les #### Mode automatique -Le formulaire doit avoir comme classe CSS `ec-crud-ajax-form-auto`. +Le formulaire doit avoir l'attribut HTML `data-ec-crud-toggle="ajax-form"`. Exemple : ```html -
+ ``` * L'URL utilisée pour la requête Ajax est: diff --git a/doc/references/modal.md b/doc/references/modal.md index 8d49146..b8f9981 100644 --- a/doc/references/modal.md +++ b/doc/references/modal.md @@ -40,7 +40,7 @@ Options disponibles : ### Ouverture automatique Ouverture de la fenêtre modale lors d'un clic sur un élément du DOM. -L'élément du DOM doit avoir comme classe CSS `ec-crud-modal-auto`. +L'élément du DOM doit avoir comme l'attribut HTML `data-ec-crud-toggle="modal"`. Toutes les options de la fonction `openModal` peuvent être utilisées en les passant par les attributs `data-`. Pour cela: * Préfixer chaque option par `data-ec-crud-modal-` @@ -50,7 +50,7 @@ Toutes les options de la fonction `openModal` peuvent être utilisées en les pa Exemple : ```html -Go ! +Go ! ``` ## Ouverture fenêtre modale avec Ajax @@ -84,7 +84,7 @@ Options disponibles : ### Ouverture automatique Ouverture de la fenêtre modale lors d'un clic sur un bouton ou un lien. -Le bouton ou le lien doit avoir comme classe CSS `ec-crud-remote-modal-auto`. +Le bouton ou le lien doit avoir l'attribut HTML `data-ec-crud-toggle="remote-modal"`. Toutes les options de la fonction `openRemoteModal` peuvent être utilisées en les passant par les attributs `data-`. Pour cela: * Préfixer chaque option par `data-ec-crud-modal-` @@ -94,13 +94,13 @@ Toutes les options de la fonction `openRemoteModal` peuvent être utilisées en Exemple avec un bouton : ```html - + ``` Exemple avec un lien : ```html -Go ! +Go ! ``` Avec un lien, l'URL utilisée pour la requête Ajax est: diff --git a/src/Form/Type/DisplaySettingsType.php b/src/Form/Type/DisplaySettingsType.php index 60204ff..895dfd3 100644 --- a/src/Form/Type/DisplaySettingsType.php +++ b/src/Form/Type/DisplaySettingsType.php @@ -51,7 +51,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'label' => 'display_settings.reset_display_settings', 'translation_domain' => 'EcommitCrudBundle', 'attr' => [ - 'class' => 'ec-crud-display-settings-reset', + 'data-ec-crud-toggle' => 'display-settings-reset', 'data-reset-url' => $options['reset_settings_url'], ], ]); diff --git a/src/Twig/CrudExtension.php b/src/Twig/CrudExtension.php index c373282..e191ff5 100644 --- a/src/Twig/CrudExtension.php +++ b/src/Twig/CrudExtension.php @@ -185,7 +185,7 @@ public function paginatorLinks(Environment $environment, PaginatorInterface $pag if (null !== $parent['ajax_options']) { return array_merge( $value, - ['class' => (isset($value['class'])) ? $value['class'].' ec-crud-ajax-link-auto' : 'ec-crud-ajax-link-auto'], + ['data-ec-crud-toggle' => 'ajax-link'], $this->getAjaxAttributes($this->validateAjaxOptions($parent['ajax_options'])), ); } @@ -309,7 +309,7 @@ public function th(Environment $environment, string $columnId, Crud $crud, array if (null !== $parent['ajax_options']) { return array_merge( $value, - ['class' => (isset($value['class'])) ? $value['class'].' ec-crud-ajax-link-auto' : 'ec-crud-ajax-link-auto'], + ['data-ec-crud-toggle' => 'ajax-link'], $this->getAjaxAttributes($this->validateAjaxOptions($parent['ajax_options'])), ); } @@ -496,6 +496,7 @@ public function searchFormStart(Environment $environment, Crud $crud, array $opt $attr = array_merge( [ + 'data-ec-crud-toggle' => 'search-form', 'data-crud-search-id' => $crud->getDivIdSearch(), 'data-crud-list-id' => $crud->getDivIdList(), ], @@ -503,7 +504,6 @@ public function searchFormStart(Environment $environment, Crud $crud, array $opt $value, $this->getAjaxAttributes($this->validateAjaxOptions($options['ajax_options'])), ); - $attr['class'] = (isset($attr['class'])) ? $attr['class'].' ec-crud-search-form' : 'ec-crud-search-form'; return $attr; }); @@ -581,13 +581,13 @@ public function searchFormReset(Environment $environment, Crud $crud, array $opt $resolver->addNormalizer('button_attr', function (Options $options, mixed $value) use ($crud): array { return array_merge( [ + 'data-ec-crud-toggle' => 'search-reset', 'data-crud-search-id' => $crud->getDivIdSearch(), 'data-crud-list-id' => $crud->getDivIdList(), 'data-ec-crud-ajax-url' => $crud->getSearchUrl(['reset' => 1]), ], $value, $this->getAjaxAttributes($this->validateAjaxOptions($options['ajax_options'])), - ['class' => (isset($value['class'])) ? $value['class'].' ec-crud-search-reset' : 'ec-crud-search-reset'], ); }); $options = $resolver->resolve($this->buildOptions('crud_search_form_reset', $options, $crud)); @@ -609,24 +609,12 @@ public function searchFormReset(Environment $environment, Crud $crud, array $opt * Ajax form start tag. * * @param array $options Options: - * * auto_class: Auto CSS class used. Default: ec-crud-ajax-form-auto * * ajax_options: See "ajaxAttributes" method options * * + form_start function options */ public function formStartAjax(FormView $formView, array $options = []): string { - $autoClass = 'ec-crud-ajax-form-auto'; - if (isset($options['auto_class']) && null !== $options['auto_class']) { - $autoClass = $options['auto_class']; - unset($options['auto_class']); - } - if (isset($options['attr']['class']) && null !== $options['attr']['class']) { - $options['attr']['class'] = sprintf('%s %s', $autoClass, $options['attr']['class']); - } elseif (isset($formView->vars['attr']['class']) && null !== $formView->vars['attr']['class']) { - $options['attr']['class'] = sprintf('%s %s', $autoClass, $formView->vars['attr']['class']); - } else { - $options['attr']['class'] = $autoClass; - } + $options['attr']['data-ec-crud-toggle'] = 'ajax-form'; if (isset($options['ajax_options'])) { $this->validateAjaxOptions($options['ajax_options']); diff --git a/templates/Theme/base.html.twig b/templates/Theme/base.html.twig index 29e03b6..4d43dad 100644 --- a/templates/Theme/base.html.twig +++ b/templates/Theme/base.html.twig @@ -154,7 +154,7 @@ {% block display_settings %} {% apply spaceless %} {% if crud.displayResults %} - {% set display_settings_container_attributes = {'class': 'ec-crud-display-settings', 'id': 'ec-crud-display-settings-'~crud.sessionName, 'data-crud-list-id': crud.divIdList} %} + {% set display_settings_container_attributes = {'data-ec-crud-toggle': 'display-settings', 'id': 'ec-crud-display-settings-'~crud.sessionName, 'data-crud-list-id': crud.divIdList} %} {% if modal %} {{ block('display_settings_button_modal', template_name) }} {{ block('display_settings_container_modal', template_name) }} @@ -178,7 +178,7 @@ {% block display_settings_button %} {% set display_settings_text = display_settings_text|default('display_settings.title'|trans({}, 'EcommitCrudBundle')) %} - {% set button_attr = button_attr|default([])|merge({class: (button_attr.class|default('') ~ ' ec-crud-display-settings-button')|trim, 'data-display-settings': 'ec-crud-display-settings-'~crud.sessionName}) %} + {% set button_attr = button_attr|default([])|merge({'data-ec-crud-toggle': 'display-settings-button', 'data-display-settings': 'ec-crud-display-settings-'~crud.sessionName}) %} {{ crud_icon('display_settings') }} {{ display_settings_text }} @@ -212,9 +212,9 @@ {{ form_row(form.resultsPerPage) }} {{ form_row(form.displayedColumns) }}
- +   - +
{{ form_row(form.reset) }} diff --git a/templates/Theme/bootstrap3.html.twig b/templates/Theme/bootstrap3.html.twig index 7b68411..9cfcd18 100644 --- a/templates/Theme/bootstrap3.html.twig +++ b/templates/Theme/bootstrap3.html.twig @@ -63,9 +63,9 @@ {{ form_row(form.resultsPerPage) }} {{ form_row(form.displayedColumns) }}
- +   - +
{{ form_end(form) }} @@ -101,7 +101,7 @@
- {{ form_widget(form.reset, {'attr': {'class': form.reset.vars.attr.class ~ ' btn-outline-secondary'}}) }} + {{ form_widget(form.reset, {'attr': {'class': form.reset.vars.attr.class|default('') ~ ' btn-outline-secondary'}}) }} {{ form_row(form.save, {'attr': {'class': 'btn-success'}}) }}
{{ form_end(form) }} From 62d55bb872fc5297aa75cf94abfa762d0267f562 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Mon, 2 Jan 2023 20:29:46 +0100 Subject: [PATCH 204/264] Fix data-ec-crud-toggle with child --- assets/js/ajax.js | 14 ++++---- assets/js/crud.js | 20 +++++------ assets/js/modal/modal-manager.js | 23 +++++++------ tests/assets/js/ajax.spec.js | 24 +++++++++++++ tests/assets/js/modal/modal-manager.spec.js | 37 +++++++++++++++++++++ 5 files changed, 92 insertions(+), 26 deletions(-) diff --git a/assets/js/ajax.js b/assets/js/ajax.js index 0a7e2b1..79c79e4 100644 --- a/assets/js/ajax.js +++ b/assets/js/ajax.js @@ -17,11 +17,11 @@ const ready = (callback) => { ready(function () { document.addEventListener('click', function (event) { - if (event.target.matches('[data-ec-crud-toggle="ajax-click"]')) { + if (event.target.closest('[data-ec-crud-toggle="ajax-click"]')) { onClickAuto(event) } - if (event.target.matches('a[data-ec-crud-toggle="ajax-link"]')) { + if (event.target.closest('a[data-ec-crud-toggle="ajax-link"]')) { onClickLinkAuto(event) } }) @@ -35,30 +35,32 @@ ready(function () { function onClickAuto (event) { event.preventDefault() + const element = event.target.closest('[data-ec-crud-toggle="ajax-click"]') const eventBefore = new CustomEvent('ec-crud-ajax-click-auto-before', { bubbles: true, cancelable: true }) - event.target.dispatchEvent(eventBefore) + element.dispatchEvent(eventBefore) if (eventBefore.defaultPrevented) { return } - click(event.target).catch((error) => console.error(error)) + click(element).catch((error) => console.error(error)) } function onClickLinkAuto (event) { event.preventDefault() + const aLink = event.target.closest('a[data-ec-crud-toggle="ajax-link"]') const eventBefore = new CustomEvent('ec-crud-ajax-link-auto-before', { bubbles: true, cancelable: true }) - event.target.dispatchEvent(eventBefore) + aLink.dispatchEvent(eventBefore) if (eventBefore.defaultPrevented) { return } - link(event.target).catch((error) => console.error(error)) + link(aLink).catch((error) => console.error(error)) } function onSubmitFormAuto (event) { diff --git a/assets/js/crud.js b/assets/js/crud.js index af336a6..9f17954 100644 --- a/assets/js/crud.js +++ b/assets/js/crud.js @@ -28,23 +28,23 @@ ready(function () { }) document.addEventListener('click', function (event) { - if (event.target.matches('button[data-ec-crud-toggle="search-reset"]')) { + if (event.target.closest('button[data-ec-crud-toggle="search-reset"]')) { onResetCrudSearchForm(event) } - if (event.target.matches('button[data-ec-crud-toggle="display-settings-button"]')) { + if (event.target.closest('button[data-ec-crud-toggle="display-settings-button"]')) { onCrudDisplaySettingsOpen(event) } - if (event.target.matches('button[data-ec-crud-toggle="display-settings-check-all-columns"]')) { + if (event.target.closest('button[data-ec-crud-toggle="display-settings-check-all-columns"]')) { onCrudDisplaySettingsCheckAllColumns(event) } - if (event.target.matches('button[data-ec-crud-toggle="display-settings-uncheck-all-columns"]')) { + if (event.target.closest('button[data-ec-crud-toggle="display-settings-uncheck-all-columns"]')) { onCrudDisplaySettingsUncheckAllColumns(event) } - if (event.target.matches('button[data-ec-crud-toggle="display-settings-reset"]')) { + if (event.target.closest('button[data-ec-crud-toggle="display-settings-reset"]')) { onCrudDisplaySettingsReset(event) } }) @@ -67,7 +67,7 @@ function onSubmitCrudSearchForm (event) { } function onResetCrudSearchForm (event) { - const button = event.target + const button = event.target.closest('button[data-ec-crud-toggle="search-reset"]') const searchContainer = getElement('#' + button.getAttribute('data-crud-search-id')) const listContainer = getElement('#' + button.getAttribute('data-crud-list-id')) @@ -81,7 +81,7 @@ function onResetCrudSearchForm (event) { } function onCrudDisplaySettingsOpen (event) { - const button = event.target + const button = event.target.closest('button[data-ec-crud-toggle="display-settings-button"]') const displaySettingsContainer = getElement('#' + button.getAttribute('data-display-settings')) const isModal = displaySettingsContainer.getAttribute('data-modal') === '1' @@ -99,21 +99,21 @@ function onCrudDisplaySettingsOpen (event) { } function onCrudDisplaySettingsCheckAllColumns (event) { - const button = event.target + const button = event.target.closest('button[data-ec-crud-toggle="display-settings-check-all-columns"]') button.parentNode.closest('div[data-ec-crud-toggle="display-settings"]').querySelectorAll('input[type=checkbox]').forEach(checkbox => { checkbox.checked = true }) } function onCrudDisplaySettingsUncheckAllColumns (event) { - const button = event.target + const button = event.target.closest('button[data-ec-crud-toggle="display-settings-uncheck-all-columns"]') button.parentNode.closest('div[data-ec-crud-toggle="display-settings"]').querySelectorAll('input[type=checkbox]').forEach(checkbox => { checkbox.checked = false }) } function onCrudDisplaySettingsReset (event) { - const button = event.target + const button = event.target.closest('button[data-ec-crud-toggle="display-settings-reset"]') const displaySettingsContainer = button.parentNode.closest('div[data-ec-crud-toggle="display-settings"]') const listContainer = getElement('#' + displaySettingsContainer.getAttribute('data-crud-list-id')) diff --git a/assets/js/modal/modal-manager.js b/assets/js/modal/modal-manager.js index 6fd4b05..83dec11 100644 --- a/assets/js/modal/modal-manager.js +++ b/assets/js/modal/modal-manager.js @@ -23,15 +23,15 @@ const ready = (callback) => { ready(function () { document.addEventListener('click', function (event) { - if (event.target.matches('[data-ec-crud-toggle="modal"]')) { + if (event.target.closest('[data-ec-crud-toggle="modal"]')) { onClickModalAuto(event) } - if (event.target.matches('button[data-ec-crud-toggle="remote-modal"]')) { + if (event.target.closest('button[data-ec-crud-toggle="remote-modal"]')) { onClickButtonRemoteModalAuto(event) } - if (event.target.matches('a[data-ec-crud-toggle="remote-modal"]')) { + if (event.target.closest('a[data-ec-crud-toggle="remote-modal"]')) { onClickLinkRemoteModalAuto(event) } }) @@ -39,39 +39,42 @@ ready(function () { function onClickModalAuto (event) { event.preventDefault() + const element = event.target.closest('[data-ec-crud-toggle="modal"]') const eventBefore = new CustomEvent('ec-crud-modal-auto-before', { bubbles: true, cancelable: true }) - event.target.dispatchEvent(eventBefore) + element.dispatchEvent(eventBefore) if (eventBefore.defaultPrevented) { return } - openModal(optionsResolver.getDataAttributes(event.target, 'ecCrudModal')) + openModal(optionsResolver.getDataAttributes(element, 'ecCrudModal')) } function onClickButtonRemoteModalAuto (event) { event.preventDefault() + const button = event.target.closest('button[data-ec-crud-toggle="remote-modal"]') const eventBefore = new CustomEvent('ec-crud-remote-modal-auto-before', { bubbles: true, cancelable: true }) - event.target.dispatchEvent(eventBefore) + button.dispatchEvent(eventBefore) if (eventBefore.defaultPrevented) { return } - openRemoteModal(optionsResolver.getDataAttributes(event.target, 'ecCrudModal')) + openRemoteModal(optionsResolver.getDataAttributes(button, 'ecCrudModal')) } function onClickLinkRemoteModalAuto (event) { event.preventDefault() + const aLink = event.target.closest('a[data-ec-crud-toggle="remote-modal"]') const eventBefore = new CustomEvent('ec-crud-remote-modal-auto-before', { bubbles: true, cancelable: true }) - event.target.dispatchEvent(eventBefore) + aLink.dispatchEvent(eventBefore) if (eventBefore.defaultPrevented) { return } @@ -79,9 +82,9 @@ function onClickLinkRemoteModalAuto (event) { // Options in data-* override href const options = optionsResolver.resolve( { - url: event.target.getAttribute('href') + url: aLink.getAttribute('href') }, - optionsResolver.getDataAttributes(event.target, 'ecCrudModal') + optionsResolver.getDataAttributes(aLink, 'ecCrudModal') ) openRemoteModal(options) diff --git a/tests/assets/js/ajax.spec.js b/tests/assets/js/ajax.spec.js index 133a8c9..733ce3b 100644 --- a/tests/assets/js/ajax.spec.js +++ b/tests/assets/js/ajax.spec.js @@ -1070,6 +1070,18 @@ describe('Test Ajax.click', function () { expect(jasmine.Ajax.requests.mostRecent().url).toMatch('/goodRequest') }) + it('Send auto-request with button and child', async function () { + $('body').append('') + + $('#childToTest').get(0).click() + + await wait(() => { + return false + }, 500) + + expect(jasmine.Ajax.requests.mostRecent().url).toMatch('/goodRequest') + }) + it('Send auto-request with button canceled', async function () { $(document).on('ec-crud-ajax-click-auto-before', '#clickToTest', function (event) { event.preventDefault() @@ -1251,6 +1263,18 @@ describe('Test Ajax.link', function () { expect(jasmine.Ajax.requests.mostRecent().url).toMatch('/goodRequest') }) + it('Send auto-request with link and child', async function () { + $('body').append('Go !') + + $('#childToTest').get(0).click() + + await wait(() => { + return false + }, 500) + + expect(jasmine.Ajax.requests.mostRecent().url).toMatch('/goodRequest') + }) + it('Send auto-request with link canceled', async function () { $(document).on('ec-crud-ajax-link-auto-before', '#linkToTest', function (event) { event.preventDefault() diff --git a/tests/assets/js/modal/modal-manager.spec.js b/tests/assets/js/modal/modal-manager.spec.js index 4dd67f3..a75240c 100644 --- a/tests/assets/js/modal/modal-manager.spec.js +++ b/tests/assets/js/modal/modal-manager.spec.js @@ -86,6 +86,15 @@ describe('Test Modal-manager with spy engine', function () { expect(this.spyEngine.closeModal).not.toHaveBeenCalled() }) + it('Test auto openModal with child', function () { + $('body').append('Go !') + + $('#childToTest').get(0).click() + + expect(this.spyEngine.openModal).toHaveBeenCalled() + expect(this.spyEngine.closeModal).not.toHaveBeenCalled() + }) + it('Test auto openModal canceled', function () { $(document).on('ec-crud-modal-auto-before', '#linkToTest', function (event) { event.preventDefault() @@ -114,6 +123,20 @@ describe('Test Modal-manager with spy engine', function () { expect(jasmine.Ajax.requests.mostRecent().method).toBe('POST') }) + it('Test auto openRemoteModal - link with child', async function () { + $('body').append('Go !') + + $('#childToTest').get(0).click() + await wait(() => { + return this.spyEngine.opened + }) + + expect(this.spyEngine.openModal).toHaveBeenCalled() + expect(this.spyEngine.closeModal).not.toHaveBeenCalled() + expect(jasmine.Ajax.requests.mostRecent().url).toMatch('/goodRequest') + expect(jasmine.Ajax.requests.mostRecent().method).toBe('POST') + }) + it('Test auto openRemoteModal - link with href', async function () { $('body').append('Go !') @@ -174,6 +197,20 @@ describe('Test Modal-manager with spy engine', function () { expect(jasmine.Ajax.requests.mostRecent().method).toBe('POST') }) + it('Test auto openRemoteModal - button with child', async function () { + $('body').append('') + + $('#childToTest').get(0).click() + await wait(() => { + return this.spyEngine.opened + }) + + expect(this.spyEngine.openModal).toHaveBeenCalled() + expect(this.spyEngine.closeModal).not.toHaveBeenCalled() + expect(jasmine.Ajax.requests.mostRecent().url).toMatch('/goodRequest') + expect(jasmine.Ajax.requests.mostRecent().method).toBe('POST') + }) + it('Test auto openRemoteModal - button - canceled', async function () { $(document).on('ec-crud-remote-modal-auto-before', '#buttonToTest', function (event) { event.preventDefault() From 7ea52639964744a76bbfeec728c1a9ea0f38a3f9 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Tue, 3 Jan 2023 13:09:12 +0100 Subject: [PATCH 205/264] [Ajax] Fix body and query parameters with objects --- assets/js/ajax.js | 49 +++++++++++++++++----------- tests/assets/js/ajax.spec.js | 62 ++++++++++++++++++++++++++++++++---- 2 files changed, 85 insertions(+), 26 deletions(-) diff --git a/assets/js/ajax.js b/assets/js/ajax.js index 79c79e4..419ea81 100644 --- a/assets/js/ajax.js +++ b/assets/js/ajax.js @@ -138,14 +138,8 @@ export function sendRequest (options) { fetchOptions.body = options.body } else if (options.body instanceof Object) { const formData = new FormData() - Object.entries(options.body).forEach(entry => { - if (Array.isArray(entry[1])) { - entry[1].forEach(subEntry => { - formData.append(entry[0], subEntry) - }) - } else { - formData.append(entry[0], entry[1]) - } + generateParameters([], '', '', options.body).forEach(entry => { + formData.append(entry[0], entry[1]) }) fetchOptions.body = formData } else if (options.body !== null) { @@ -330,17 +324,8 @@ function resolveUrl (options) { const url = new URL(options.url, window.location.origin) const searchParams = url.searchParams - Object.entries(options.query).forEach(entry => { - if (Array.isArray(entry[1])) { - if (searchParams.has(entry[0])) { - searchParams.delete(entry[0]) - } - entry[1].forEach(subEntry => { - searchParams.append(entry[0], subEntry) - }) - } else { - searchParams.set(entry[0], entry[1]) - } + generateParameters([], '', '', options.query).forEach(entry => { + searchParams.set(entry[0], entry[1]) }) if (!options.cache && !searchParams.has('_')) { @@ -350,6 +335,32 @@ function resolveUrl (options) { return url.toString() } +function generateParameters (result, propertyPath, property, value) { + if (/[[\]]/.test(property)) { + return result + } + + if (propertyPath === '') { + propertyPath = property + } else { + propertyPath = propertyPath + '[' + property + ']' + } + + if (typeof (value) === 'undefined' || typeof (value) !== 'object') { + result.push([propertyPath, value]) + + return result + } + + for (const param in value) { + if (Object.prototype.hasOwnProperty.call(value, param)) { + result = generateParameters(result, propertyPath, param, value[param]) + } + } + + return result +} + function executeEventsAndCallbacksSuccess (callbacksSuccess, options, data, response) { const eventOnSuccess = new CustomEvent('ec-crud-ajax-on-success', { detail: { diff --git a/tests/assets/js/ajax.spec.js b/tests/assets/js/ajax.spec.js index 733ce3b..92f15d3 100644 --- a/tests/assets/js/ajax.spec.js +++ b/tests/assets/js/ajax.spec.js @@ -432,7 +432,15 @@ describe('Test Ajax.sendRequest', function () { url: '/goodRequest', body: { var1: 'My value 1', - 'var2[]': ['val2A', 'val2C'] + var2: ['val2A', 'val2C'], + 'var[': 'value', // Ignored + object: { + property1: 'A', + property2: ['B', 'C'], + subObject: { + property3: 'D' + } + } }, onComplete: function (statusText, response) { callbackComplete() @@ -452,7 +460,15 @@ describe('Test Ajax.sendRequest', function () { expect(response).toBeInstanceOf(Response) expect(jasmine.Ajax.requests.mostRecent().url).toMatch('/goodRequest') - expect(jasmine.Ajax.requests.mostRecent().data()).toEqual([['var1', 'My value 1'], ['var2[]', 'val2A'], ['var2[]', 'val2C']]) // Parsed by addJasmineAjaxFormDataSupport + expect(jasmine.Ajax.requests.mostRecent().data()).toEqual([ + ['var1', 'My value 1'], + ['var2[0]', 'val2A'], + ['var2[1]', 'val2C'], + ['object[property1]', 'A'], + ['object[property2][0]', 'B'], + ['object[property2][1]', 'C'], + ['object[subObject][property3]', 'D'] + ]) // Parsed by addJasmineAjaxFormDataSupport expect(callbackSuccess).toHaveBeenCalled() expect(callbackError).not.toHaveBeenCalled() expect(callbackComplete).toHaveBeenCalled() @@ -496,11 +512,43 @@ describe('Test Ajax.sendRequest', function () { it('Send request with query option', async function () { const promise = ajax.sendRequest({ - url: '/goodRequest?var1=val1&var2[]=val2a&var3=val3', + url: '/goodRequest', + query: { + // Same example as https://www.php.net/manual/fr/function.http-build-query.php + user: { + name: 'Bob Smith', + age: 47, + sex: 'M', + dob: '5/12/1956' + }, + 'var[': 'value', // Ignored + pastimes: ['golf', 'opera', 'poker', 'rap'], + children: { + bobby: { + age: 12, + sex: 'M' + }, + sally: { + age: 8, + sex: 'F' + } + } + }, + cache: true + }) + expect(promise).toBeInstanceOf(Promise) + + const response = await promise + + expect(response).toBeInstanceOf(Response) + expect(jasmine.Ajax.requests.mostRecent().url).toEqual('http://localhost:9876/goodRequest?user%5Bname%5D=Bob+Smith&user%5Bage%5D=47&user%5Bsex%5D=M&user%5Bdob%5D=5%2F12%2F1956&pastimes%5B0%5D=golf&pastimes%5B1%5D=opera&pastimes%5B2%5D=poker&pastimes%5B3%5D=rap&children%5Bbobby%5D%5Bage%5D=12&children%5Bbobby%5D%5Bsex%5D=M&children%5Bsally%5D%5Bage%5D=8&children%5Bsally%5D%5Bsex%5D=F') + }) + + it('Send request with query option and override', async function () { + const promise = ajax.sendRequest({ + url: '/goodRequest?var1=value1&var2=value2', query: { - var1: 'new1', // override - 'var2[]': ['new2a', 'new2b'], // override - var4: 'new4' + var1: 'newValue1' }, cache: true }) @@ -509,7 +557,7 @@ describe('Test Ajax.sendRequest', function () { const response = await promise expect(response).toBeInstanceOf(Response) - expect(jasmine.Ajax.requests.mostRecent().url).toEqual('http://localhost:9876/goodRequest?var1=new1&var3=val3&var2%5B%5D=new2a&var2%5B%5D=new2b&var4=new4') + expect(jasmine.Ajax.requests.mostRecent().url).toEqual('http://localhost:9876/goodRequest?var1=newValue1&var2=value2') }) it('Send request with cache', async function () { From 206d412d6a85647d6f1d864838f882e8c5313476 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Fri, 27 Jan 2023 19:17:46 +0100 Subject: [PATCH 206/264] Add doctrine/collections v2 support --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index eac0682..dccfaeb 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ "php": "^8.0", "ext-json": "*", "ext-mbstring": "*", - "doctrine/collections": "^1.5", + "doctrine/collections": "^1.5|^2.0", "doctrine/dbal": "^2.13.1|^3.2", "doctrine/doctrine-bundle": "^2.4.5", "doctrine/orm": "^2.9", From 5877b257342cd94d641a897a6de1a31aac97a217 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sat, 3 Jun 2023 19:36:48 +0200 Subject: [PATCH 207/264] Fix CS --- composer.json | 5 ++++- src/Controller/CrudControllerTrait.php | 2 +- src/Crud/Crud.php | 8 ++++---- src/Crud/CrudConfig.php | 4 ++-- src/Crud/CrudSession.php | 2 +- src/Crud/Http/QueryBuilder.php | 4 ++-- src/Form/Type/DisplaySettingsType.php | 9 --------- src/Form/Type/FormSearchType.php | 9 --------- src/Twig/CrudExtension.php | 4 ++-- tests/Crud/AbstractCrudTest.php | 2 +- tests/Crud/CrudSessionTest.php | 2 +- tests/Crud/SearchFormBuilderTest.php | 2 +- .../TestCrudControllerWithPersistentSettingsTest.php | 2 +- 13 files changed, 20 insertions(+), 35 deletions(-) diff --git a/composer.json b/composer.json index dccfaeb..53f5d28 100644 --- a/composer.json +++ b/composer.json @@ -65,6 +65,9 @@ "psr-4": { "Ecommit\\CrudBundle\\Tests\\": "tests/" } }, "config": { - "sort-packages": true + "sort-packages": true, + "allow-plugins": { + "phpstan/extension-installer": true + } } } diff --git a/src/Controller/CrudControllerTrait.php b/src/Controller/CrudControllerTrait.php index 21d08e7..bc79937 100644 --- a/src/Controller/CrudControllerTrait.php +++ b/src/Controller/CrudControllerTrait.php @@ -26,7 +26,7 @@ abstract protected function getCrudOptions(): array; abstract protected function getTemplateName(string $action): string; - protected function createCrudConfig(?string $sessionName = null): CrudConfig + protected function createCrudConfig(string $sessionName = null): CrudConfig { return new CrudConfig($sessionName); } diff --git a/src/Crud/Crud.php b/src/Crud/Crud.php index 462cdf7..dcceb66 100644 --- a/src/Crud/Crud.php +++ b/src/Crud/Crud.php @@ -882,10 +882,10 @@ protected function save(): void $em->flush(); } else { // Create object in database only if not default values - if ($this->sessionValues->getDisplayedColumns() != $this->getDefaultDisplayedColumns() || - $this->sessionValues->getMaxPerPage() != $this->getDefaultMaxPerPage() || - $this->sessionValues->getSortDirection() != $this->getDefaultSortDirection() || - $this->sessionValues->getSort() != $this->getDefaultSort() + if ($this->sessionValues->getDisplayedColumns() != $this->getDefaultDisplayedColumns() + || $this->sessionValues->getMaxPerPage() != $this->getDefaultMaxPerPage() + || $this->sessionValues->getSortDirection() != $this->getDefaultSortDirection() + || $this->sessionValues->getSort() != $this->getDefaultSort() ) { $objectDatabase = new UserCrudSettings( $this->container->get('security.token_storage')->getToken()->getUser(), diff --git a/src/Crud/CrudConfig.php b/src/Crud/CrudConfig.php index f480b87..4443abb 100644 --- a/src/Crud/CrudConfig.php +++ b/src/Crud/CrudConfig.php @@ -19,7 +19,7 @@ final class CrudConfig implements \ArrayAccess { protected array $options = []; - public function __construct(?string $sessionName = null) + public function __construct(string $sessionName = null) { if (null !== $sessionName) { $this->setSessionName($sessionName); @@ -155,7 +155,7 @@ public function setRoute(string $name, array $parameters = []): self return $this; } - public function createSearchForm(SearcherInterface $defaultData, ?string $type = null, array $options = []): self + public function createSearchForm(SearcherInterface $defaultData, string $type = null, array $options = []): self { $this->options['search_form_data'] = $defaultData; $this->options['search_form_type'] = $type; diff --git a/src/Crud/CrudSession.php b/src/Crud/CrudSession.php index 5e96581..1615e89 100644 --- a/src/Crud/CrudSession.php +++ b/src/Crud/CrudSession.php @@ -29,7 +29,7 @@ final class CrudSession /** * @internal */ - public function __construct(protected int $maxPerPage, protected array $displayedColumns, protected string $sort, protected string $sortDirection, ?SearcherInterface $searchFormData = null) + public function __construct(protected int $maxPerPage, protected array $displayedColumns, protected string $sort, protected string $sortDirection, SearcherInterface $searchFormData = null) { $this->searchFormData = ($searchFormData) ? clone $searchFormData : null; // Avoid modification by reference (by the user or an invalid form) } diff --git a/src/Crud/Http/QueryBuilder.php b/src/Crud/Http/QueryBuilder.php index dfefe6d..d953a53 100644 --- a/src/Crud/Http/QueryBuilder.php +++ b/src/Crud/Http/QueryBuilder.php @@ -31,7 +31,7 @@ final class QueryBuilder implements QueryBuilderInterface protected array $orders = []; protected HttpClientInterface $client; - public function __construct(protected string $url, protected string $httpMethod, ?HttpClientInterface $client = null) + public function __construct(protected string $url, protected string $httpMethod, HttpClientInterface $client = null) { if (null === $client) { $client = HttpClient::create(); @@ -50,7 +50,7 @@ public function setOrderBuilder(\Closure $orderBuilder): self } /** - * @param \Closure $orderBuilder Closure aguments: $queryBuilder, $page, $resultsPerPage + * Closure aguments: $queryBuilder, $page, $resultsPerPage. */ public function setPaginationBuilder(\Closure $paginationBuilder): self { diff --git a/src/Form/Type/DisplaySettingsType.php b/src/Form/Type/DisplaySettingsType.php index 895dfd3..6dff738 100644 --- a/src/Form/Type/DisplaySettingsType.php +++ b/src/Form/Type/DisplaySettingsType.php @@ -24,9 +24,6 @@ class DisplaySettingsType extends AbstractType { - /** - * {@inheritdoc} - */ public function buildForm(FormBuilderInterface $builder, array $options): void { $builder->add('resultsPerPage', ChoiceType::class, [ @@ -62,9 +59,6 @@ public function buildForm(FormBuilderInterface $builder, array $options): void ]); } - /** - * {@inheritdoc} - */ public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ @@ -78,9 +72,6 @@ public function configureOptions(OptionsResolver $resolver): void ]); } - /** - * {@inheritdoc} - */ public function getBlockPrefix() { return 'crud_display_settings'; diff --git a/src/Form/Type/FormSearchType.php b/src/Form/Type/FormSearchType.php index c9615e0..e031237 100644 --- a/src/Form/Type/FormSearchType.php +++ b/src/Form/Type/FormSearchType.php @@ -19,16 +19,10 @@ class FormSearchType extends AbstractType { - /** - * {@inheritdoc} - */ public function buildForm(FormBuilderInterface $builder, array $options): void { } - /** - * {@inheritdoc} - */ public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ @@ -37,9 +31,6 @@ public function configureOptions(OptionsResolver $resolver): void ]); } - /** - * {@inheritdoc} - */ public function getBlockPrefix() { return 'crud_search'; diff --git a/src/Twig/CrudExtension.php b/src/Twig/CrudExtension.php index e191ff5..4b563cd 100644 --- a/src/Twig/CrudExtension.php +++ b/src/Twig/CrudExtension.php @@ -701,7 +701,7 @@ protected function getAjaxAttributes(array $options): array return $attributes; } - public function crudIcon(Environment $environment, string $iconName, ?string $iconTheme = null): string + public function crudIcon(Environment $environment, string $iconName, string $iconTheme = null): string { if (null === $iconTheme) { $iconTheme = $this->iconTheme; @@ -720,7 +720,7 @@ protected function renderBlock(Environment $environment, string $templateName, s return ob_get_clean(); } - protected function buildOptions(string $function, array $inlineOptions, ?Crud $crud = null): array + protected function buildOptions(string $function, array $inlineOptions, Crud $crud = null): array { $options = []; diff --git a/tests/Crud/AbstractCrudTest.php b/tests/Crud/AbstractCrudTest.php index 146dcea..1c838b0 100644 --- a/tests/Crud/AbstractCrudTest.php +++ b/tests/Crud/AbstractCrudTest.php @@ -81,7 +81,7 @@ protected function createCrud(array|CrudConfig $crudConfig, array $filters = [], return $crud; } - protected function createValidCrudConfig(?CrudConfig $crudConfig = null, bool $withSearcher = false): CrudConfig + protected function createValidCrudConfig(CrudConfig $crudConfig = null, bool $withSearcher = false): CrudConfig { $queryBuilder = self::getContainer()->get(ManagerRegistry::class)->getRepository(TestUser::class) ->createQueryBuilder('u') diff --git a/tests/Crud/CrudSessionTest.php b/tests/Crud/CrudSessionTest.php index 6cdb020..c095df2 100644 --- a/tests/Crud/CrudSessionTest.php +++ b/tests/Crud/CrudSessionTest.php @@ -239,7 +239,7 @@ protected function setPropertyWithoutGetter(CrudSession $crudSession, string $pr $reflectionMethod->setValue($crudSession, $value); } - protected function createCrudSession(?UserSearcher $searchFormData = null): CrudSession + protected function createCrudSession(UserSearcher $searchFormData = null): CrudSession { return new CrudSession(100, ['col1'], 'sort1', Crud::ASC, $searchFormData); } diff --git a/tests/Crud/SearchFormBuilderTest.php b/tests/Crud/SearchFormBuilderTest.php index 47fa5e5..b47cb35 100644 --- a/tests/Crud/SearchFormBuilderTest.php +++ b/tests/Crud/SearchFormBuilderTest.php @@ -470,7 +470,7 @@ protected function createCrudContainer(array $filters = []): ContainerInterface return $container; } - protected function createSearchFormBuilder(?array $filters = null, ?Crud $crud = null, ?SearcherInterface $defaultData = null, ?string $type = null, array $options = []): SearchFormBuilder + protected function createSearchFormBuilder(array $filters = null, Crud $crud = null, SearcherInterface $defaultData = null, string $type = null, array $options = []): SearchFormBuilder { $filters = (null !== $filters) ? $filters : ['my_filter' => $this->createMock(FilterInterface::class)]; $crud = ($crud) ?: $this->createCrud($this->createValidCrudConfig()); diff --git a/tests/Functional/Controller/TestCrudControllerWithPersistentSettingsTest.php b/tests/Functional/Controller/TestCrudControllerWithPersistentSettingsTest.php index ecf7fd9..fe14c75 100644 --- a/tests/Functional/Controller/TestCrudControllerWithPersistentSettingsTest.php +++ b/tests/Functional/Controller/TestCrudControllerWithPersistentSettingsTest.php @@ -270,7 +270,7 @@ protected function logout(Client $client): void $client->request('GET', '/logout'); } - protected function changeSettings(Client $client, array $headerColumnClicks = [], array $displayColumnClicks = [], ?int $resultsPerPage = null): void + protected function changeSettings(Client $client, array $headerColumnClicks = [], array $displayColumnClicks = [], int $resultsPerPage = null): void { foreach ($headerColumnClicks as $column) { $link = $client->getCrawler()->filterXPath('//table[@class="result"]/thead/tr/th/a[contains(text(), "'.$column.'")]'); From 3678cc4d054914e767556c3295ceb1ea59b52bc6 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sat, 3 Jun 2023 19:44:10 +0200 Subject: [PATCH 208/264] Fix doc --- doc/references/ajax.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/references/ajax.md b/doc/references/ajax.md index 984b04f..d120b23 100644 --- a/doc/references/ajax.md +++ b/doc/references/ajax.md @@ -51,11 +51,11 @@ La fonction `sendRequest` retourne une promesse. | Événement | Objet | Description | Propriétés disponibles | |--------------------------|------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------| -| ec-crud-ajax | [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent) (cancelable) | Appelé avant l'exécution de la requête, avant la résolution des options. Peut annuler la requête avec `event.preventDefault()` |
  • event.details.options
| -| ec-crud-ajax-before-send | [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent) (cancelable) | Appelé avant l'exécution de la requête, après la résolution des options. Peut annuler la requête avec `event.preventDefault()` |
  • event.details.options
| -| ec-crud-ajax-on-success | [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent) | Même fonctionnement que callback `onSuccess` |
  • event.details.data
  • event.details.response
| -| ec-crud-ajax-on-error | [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent) | Même fonctionnement que callback `onError` |
  • event.details.statusText
  • event.details.response
| -| ec-crud-ajax-on-complete | [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent) | Même fonctionnement que callback `onComplete` |
  • event.details.statusText
  • event.details.response
| +| ec-crud-ajax | [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent) (cancelable) | Appelé avant l'exécution de la requête, avant la résolution des options. Peut annuler la requête avec `event.preventDefault()` |
  • event.detail.options
| +| ec-crud-ajax-before-send | [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent) (cancelable) | Appelé avant l'exécution de la requête, après la résolution des options. Peut annuler la requête avec `event.preventDefault()` |
  • event.detail.options
| +| ec-crud-ajax-on-success | [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent) | Même fonctionnement que callback `onSuccess` |
  • event.detail.data
  • event.detail.response
| +| ec-crud-ajax-on-error | [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent) | Même fonctionnement que callback `onError` |
  • event.detail.statusText
  • event.detail.response
| +| ec-crud-ajax-on-complete | [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent) | Même fonctionnement que callback `onComplete` |
  • event.detail.statusText
  • event.detail.response
| From 19c9f7a3cbeb2d455215b9414de25350ffd88141 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sat, 3 Jun 2023 19:44:35 +0200 Subject: [PATCH 209/264] Add Symfony 6.3 tests --- .github/workflows/tests.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 13a9296..f2f5013 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -30,6 +30,10 @@ jobs: symfony-version: '6.2.*' composer-flags: '--prefer-stable --prefer-lowest' description: 'with SF 6.2.* lowest' + - php-version: '8.1' + symfony-version: '6.3.*' + composer-flags: '--prefer-stable --prefer-lowest' + description: 'with SF 6.3.* lowest' #Symfony versions @@ -45,6 +49,9 @@ jobs: - php-version: '8.2' symfony-version: '6.2.*' description: 'with SF 6.2.*' + - php-version: '8.2' + symfony-version: '6.3.*' + description: 'with SF 6.3.*' #PHP versions - php-version: '8.0' From a83e4bd9b1ada9868c05d563f4a24fb7cfdbe319 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sat, 3 Jun 2023 20:37:49 +0200 Subject: [PATCH 210/264] Require PHP 8.1 --- .github/workflows/tests.yml | 7 +++---- .php-cs-fixer.dist.php | 2 +- composer.json | 2 +- tests/Functional/App/public/index.php | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f2f5013..0a73078 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,11 +14,11 @@ jobs: matrix: include: #Mini (for each Symfony version) - - php-version: '8.0' + - php-version: '8.1' symfony-version: '5.4.*' composer-flags: '--prefer-stable --prefer-lowest' description: 'with SF 5.4.* lowest' - - php-version: '8.0' + - php-version: '8.1' symfony-version: '6.0.*' composer-flags: '--prefer-stable --prefer-lowest' description: 'with SF 6.0.* lowest' @@ -54,7 +54,6 @@ jobs: description: 'with SF 6.3.*' #PHP versions - - php-version: '8.0' - php-version: '8.1' - php-version: '8.2' @@ -64,7 +63,7 @@ jobs: coding-standards: true #Static Analysis (min PHP version) - - php-version: '8.0' + - php-version: '8.1' description: 'with Static Analysis' static-analysis: true diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 55777ed..0738a31 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -21,7 +21,7 @@ '@Symfony' => true, '@Symfony:risky' => true, '@DoctrineAnnotation' => true, - '@PHP80Migration' => true, + '@PHP81Migration' => true, '@PHP80Migration:risky' => true, '@PHPUnit84Migration:risky' => true, 'array_syntax' => ['syntax' => 'short'], diff --git a/composer.json b/composer.json index 53f5d28..a9a2e2e 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,7 @@ } ], "require": { - "php": "^8.0", + "php": "^8.1", "ext-json": "*", "ext-mbstring": "*", "doctrine/collections": "^1.5|^2.0", diff --git a/tests/Functional/App/public/index.php b/tests/Functional/App/public/index.php index 77552c2..0367e06 100644 --- a/tests/Functional/App/public/index.php +++ b/tests/Functional/App/public/index.php @@ -18,7 +18,7 @@ require dirname(__DIR__).'/config/bootstrap.php'; if ($_SERVER['APP_DEBUG']) { - umask(0000); + umask(0); Debug::enable(); } From 1f909f25c3e9bf1de8d7f3f5db1484f019e9818c Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sat, 15 Jul 2023 16:29:03 +0200 Subject: [PATCH 211/264] Fix CS --- src/Crud/CrudConfig.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Crud/CrudConfig.php b/src/Crud/CrudConfig.php index 4443abb..3ea5e23 100644 --- a/src/Crud/CrudConfig.php +++ b/src/Crud/CrudConfig.php @@ -209,7 +209,7 @@ public function setTwigFunctionsConfiguration(array $value): self return $this; } - public function resetOptions(null|string|array $options = null): self + public function resetOptions(string|array $options = null): self { if (null === $options) { $this->options = []; From c3f464f700cf2853b113706dc2b47e92c8d59d1e Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sat, 15 Jul 2023 17:04:22 +0200 Subject: [PATCH 212/264] Add auto_reset option --- src/Crud/CrudResponseGenerator.php | 12 ++++++---- .../Controller/TestCrudControllerTest.php | 22 +++++++++---------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/Crud/CrudResponseGenerator.php b/src/Crud/CrudResponseGenerator.php index 3ec2384..751a781 100644 --- a/src/Crud/CrudResponseGenerator.php +++ b/src/Crud/CrudResponseGenerator.php @@ -30,7 +30,7 @@ public function __construct(protected ContainerInterface $container) public function getResponse(Crud $crud, array $options = []): Response { - $options = $this->getOptions($options); + $options = $this->getOptions($options, true); $data = $this->processCrud($crud, $options); return $this->renderCrud($this->getTemplateName($options['template_generator'], 'index'), $data); @@ -40,7 +40,7 @@ public function getAjaxResponse(Crud $crud, array $options = []): Response { $masterRequest = $this->container->get('request_stack')->getMainRequest(); - $options = $this->getOptions($options); + $options = $this->getOptions($options, false); $data = $this->processCrud($crud, $options); $request = $this->container->get('request_stack')->getCurrentRequest(); @@ -64,7 +64,7 @@ public function getAjaxResponse(Crud $crud, array $options = []): Response public function getCrudData(Crud $crud, array $options = []): array { - $options = $this->getOptions($options); + $options = $this->getOptions($options, false); return $this->processCrud($crud, $options); } @@ -87,6 +87,9 @@ protected function processCrud(Crud $crud, array $options): array $request = $this->container->get('request_stack')->getCurrentRequest(); + if ($options['auto_reset']) { + $crud->reset(); + } if ($request->query->has('search')) { $crud->processSearchForm(); } @@ -106,12 +109,13 @@ protected function processCrud(Crud $crud, array $options): array return $data; } - protected function getOptions(array $options): array + protected function getOptions(array $options, bool $autoResetDefaultValue): array { $resolver = new OptionsResolver(); $resolver->setDefaults([ 'before_build' => null, 'after_build' => null, + 'auto_reset' => $autoResetDefaultValue, ]); $resolver->setRequired([ 'template_generator', diff --git a/tests/Functional/Controller/TestCrudControllerTest.php b/tests/Functional/Controller/TestCrudControllerTest.php index 34c2ab7..ff40ac7 100644 --- a/tests/Functional/Controller/TestCrudControllerTest.php +++ b/tests/Functional/Controller/TestCrudControllerTest.php @@ -165,21 +165,21 @@ public function testChangePage(Client $client): Client /** * @depends testChangePage */ - public function testSessionValuesAfterChangePage(Client $client): Client + public function testSessionValuesAfterChangePageAndReload(Client $client): Client { $client->request('GET', static::URL); - $this->assertSame([1, 3], $this->countRowsAndColumns($client->getCrawler())); - $this->assertSame([2, 2], $this->getPagination($client->getCrawler())); + $this->assertSame([10, 3], $this->countRowsAndColumns($client->getCrawler())); + $this->assertSame([1, 2], $this->getPagination($client->getCrawler())); $this->assertSame(['last_name', Crud::DESC], $this->getSort($client->getCrawler())); - $this->assertSame('JudieCieux', $this->getFirstUsername($client->getCrawler())); + $this->assertSame('ClémentTine', $this->getFirstUsername($client->getCrawler())); $this->checkBeforeAndAfterBuild($client->getCrawler()); return $client; } /** - * @depends testSessionValuesAfterChangePage + * @depends testSessionValuesAfterChangePageAndReload */ public function testSearch(Client $client): Client { @@ -200,21 +200,21 @@ public function testSearch(Client $client): Client /** * @depends testSearch */ - public function testSessionValuesAfterSearch(Client $client): Client + public function testSessionValuesAfterSearchAndReload(Client $client): Client { $client->request('GET', static::URL); - $this->assertSame([2, 3], $this->countRowsAndColumns($client->getCrawler())); - $this->assertSame([1, 1], $this->getPagination($client->getCrawler())); + $this->assertSame([10, 3], $this->countRowsAndColumns($client->getCrawler())); + $this->assertSame([1, 2], $this->getPagination($client->getCrawler())); $this->assertSame(['last_name', Crud::DESC], $this->getSort($client->getCrawler())); - $this->assertSame('HenriPoste', $this->getFirstUsername($client->getCrawler())); + $this->assertSame('ClémentTine', $this->getFirstUsername($client->getCrawler())); $this->checkBeforeAndAfterBuild($client->getCrawler()); return $client; } /** - * @depends testSessionValuesAfterSearch + * @depends testSessionValuesAfterSearchAndReload */ public function testSearchWithoutFilter(Client $client): Client { @@ -409,7 +409,7 @@ public function testManualResetSort(Client $client): Client } $client->request('GET', $resetUrl); - $this->assertSame([2, 3], $this->countRowsAndColumns($client->getCrawler())); // Not reset display settings and search + $this->assertSame([5, 3], $this->countRowsAndColumns($client->getCrawler())); // Not reset display settings and search $this->assertSame(['first_name', Crud::ASC], $this->getSort($client->getCrawler())); return $client; From 3d0d5aa1d204fbf364c649bb6a3e26f89c4806a2 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sat, 22 Jul 2023 14:28:51 +0200 Subject: [PATCH 213/264] Revert "Add auto_reset option" This reverts commit c3f464f700cf2853b113706dc2b47e92c8d59d1e. --- src/Crud/CrudResponseGenerator.php | 12 ++++------ .../Controller/TestCrudControllerTest.php | 22 +++++++++---------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/Crud/CrudResponseGenerator.php b/src/Crud/CrudResponseGenerator.php index 751a781..3ec2384 100644 --- a/src/Crud/CrudResponseGenerator.php +++ b/src/Crud/CrudResponseGenerator.php @@ -30,7 +30,7 @@ public function __construct(protected ContainerInterface $container) public function getResponse(Crud $crud, array $options = []): Response { - $options = $this->getOptions($options, true); + $options = $this->getOptions($options); $data = $this->processCrud($crud, $options); return $this->renderCrud($this->getTemplateName($options['template_generator'], 'index'), $data); @@ -40,7 +40,7 @@ public function getAjaxResponse(Crud $crud, array $options = []): Response { $masterRequest = $this->container->get('request_stack')->getMainRequest(); - $options = $this->getOptions($options, false); + $options = $this->getOptions($options); $data = $this->processCrud($crud, $options); $request = $this->container->get('request_stack')->getCurrentRequest(); @@ -64,7 +64,7 @@ public function getAjaxResponse(Crud $crud, array $options = []): Response public function getCrudData(Crud $crud, array $options = []): array { - $options = $this->getOptions($options, false); + $options = $this->getOptions($options); return $this->processCrud($crud, $options); } @@ -87,9 +87,6 @@ protected function processCrud(Crud $crud, array $options): array $request = $this->container->get('request_stack')->getCurrentRequest(); - if ($options['auto_reset']) { - $crud->reset(); - } if ($request->query->has('search')) { $crud->processSearchForm(); } @@ -109,13 +106,12 @@ protected function processCrud(Crud $crud, array $options): array return $data; } - protected function getOptions(array $options, bool $autoResetDefaultValue): array + protected function getOptions(array $options): array { $resolver = new OptionsResolver(); $resolver->setDefaults([ 'before_build' => null, 'after_build' => null, - 'auto_reset' => $autoResetDefaultValue, ]); $resolver->setRequired([ 'template_generator', diff --git a/tests/Functional/Controller/TestCrudControllerTest.php b/tests/Functional/Controller/TestCrudControllerTest.php index ff40ac7..34c2ab7 100644 --- a/tests/Functional/Controller/TestCrudControllerTest.php +++ b/tests/Functional/Controller/TestCrudControllerTest.php @@ -165,21 +165,21 @@ public function testChangePage(Client $client): Client /** * @depends testChangePage */ - public function testSessionValuesAfterChangePageAndReload(Client $client): Client + public function testSessionValuesAfterChangePage(Client $client): Client { $client->request('GET', static::URL); - $this->assertSame([10, 3], $this->countRowsAndColumns($client->getCrawler())); - $this->assertSame([1, 2], $this->getPagination($client->getCrawler())); + $this->assertSame([1, 3], $this->countRowsAndColumns($client->getCrawler())); + $this->assertSame([2, 2], $this->getPagination($client->getCrawler())); $this->assertSame(['last_name', Crud::DESC], $this->getSort($client->getCrawler())); - $this->assertSame('ClémentTine', $this->getFirstUsername($client->getCrawler())); + $this->assertSame('JudieCieux', $this->getFirstUsername($client->getCrawler())); $this->checkBeforeAndAfterBuild($client->getCrawler()); return $client; } /** - * @depends testSessionValuesAfterChangePageAndReload + * @depends testSessionValuesAfterChangePage */ public function testSearch(Client $client): Client { @@ -200,21 +200,21 @@ public function testSearch(Client $client): Client /** * @depends testSearch */ - public function testSessionValuesAfterSearchAndReload(Client $client): Client + public function testSessionValuesAfterSearch(Client $client): Client { $client->request('GET', static::URL); - $this->assertSame([10, 3], $this->countRowsAndColumns($client->getCrawler())); - $this->assertSame([1, 2], $this->getPagination($client->getCrawler())); + $this->assertSame([2, 3], $this->countRowsAndColumns($client->getCrawler())); + $this->assertSame([1, 1], $this->getPagination($client->getCrawler())); $this->assertSame(['last_name', Crud::DESC], $this->getSort($client->getCrawler())); - $this->assertSame('ClémentTine', $this->getFirstUsername($client->getCrawler())); + $this->assertSame('HenriPoste', $this->getFirstUsername($client->getCrawler())); $this->checkBeforeAndAfterBuild($client->getCrawler()); return $client; } /** - * @depends testSessionValuesAfterSearchAndReload + * @depends testSessionValuesAfterSearch */ public function testSearchWithoutFilter(Client $client): Client { @@ -409,7 +409,7 @@ public function testManualResetSort(Client $client): Client } $client->request('GET', $resetUrl); - $this->assertSame([5, 3], $this->countRowsAndColumns($client->getCrawler())); // Not reset display settings and search + $this->assertSame([2, 3], $this->countRowsAndColumns($client->getCrawler())); // Not reset display settings and search $this->assertSame(['first_name', Crud::ASC], $this->getSort($client->getCrawler())); return $client; From 68039205ffbaeb1048981535dc3a0d6cecdf1af2 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Fri, 11 Aug 2023 15:40:42 +0200 Subject: [PATCH 214/264] Label: Add TranslatableInterface support --- composer.json | 1 + src/Crud/CrudColumn.php | 7 ++++--- src/Form/Type/DisplaySettingsType.php | 11 ++++++++++- src/Twig/CrudExtension.php | 3 ++- tests/Functional/App/Controller/UserController.php | 3 ++- 5 files changed, 19 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index a9a2e2e..208c158 100644 --- a/composer.json +++ b/composer.json @@ -41,6 +41,7 @@ "symfony/security-csrf": "^5.4|^6.0", "symfony/service-contracts": "^1.1.6|^2|^3", "symfony/translation": "^5.4|^6.0", + "symfony/translation-contracts": "^2.3|^3.0", "symfony/twig-bridge": "^5.4|^6.0", "symfony/twig-bundle": "^5.4|^6.0", "symfony/validator": "^5.4|^6.0", diff --git a/src/Crud/CrudColumn.php b/src/Crud/CrudColumn.php index d836345..7b7729a 100644 --- a/src/Crud/CrudColumn.php +++ b/src/Crud/CrudColumn.php @@ -17,6 +17,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Validator\Constraints\Length; use Symfony\Component\Validator\Validation; +use Symfony\Contracts\Translation\TranslatableInterface; final class CrudColumn { @@ -53,7 +54,7 @@ public function __construct(array $options) 'alias', ]); $resolver->setDefaults([ - 'label' => fn (Options $options): string => $options['id'], + 'label' => fn (Options $options): string|TranslatableInterface => $options['id'], 'sortable' => true, 'displayed_by_default' => true, 'alias_sort' => fn (Options $options): string => $options['alias'], @@ -64,7 +65,7 @@ public function __construct(array $options) new Length(max: 100, maxMessage: 'The column id "{{ value }}" is too long. It should have {{ limit }} character or less') )); $resolver->setAllowedTypes('alias', 'string'); - $resolver->setAllowedTypes('label', 'string'); + $resolver->setAllowedTypes('label', ['string', TranslatableInterface::class]); $resolver->setAllowedTypes('sortable', 'bool'); $resolver->setAllowedTypes('displayed_by_default', 'bool'); $resolver->setAllowedTypes('alias_sort', ['string', 'array']); @@ -83,7 +84,7 @@ public function getAlias(): string return $this->options['alias']; } - public function getLabel(): string + public function getLabel(): string|TranslatableInterface { return $this->options['label']; } diff --git a/src/Form/Type/DisplaySettingsType.php b/src/Form/Type/DisplaySettingsType.php index 6dff738..6af7b75 100644 --- a/src/Form/Type/DisplaySettingsType.php +++ b/src/Form/Type/DisplaySettingsType.php @@ -21,6 +21,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Validator\Constraints\Count; use Symfony\Component\Validator\Constraints\NotBlank; +use Symfony\Contracts\Translation\TranslatableInterface; class DisplaySettingsType extends AbstractType { @@ -34,8 +35,16 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'constraints' => new NotBlank(), ]); + $columnsChoices = $options['columns_choices']; $builder->add('displayedColumns', ChoiceType::class, [ - 'choices' => array_flip($options['columns_choices']), + 'choices' => array_keys($columnsChoices), + 'choice_label' => function (string $choice, string $key, mixed $value) use ($columnsChoices): string|TranslatableInterface { + if (\array_key_exists($choice, $columnsChoices)) { + return $columnsChoices[$choice]; + } + + return $choice; + }, 'multiple' => true, 'expanded' => true, 'label' => 'display_settings.displayed_columns', diff --git a/src/Twig/CrudExtension.php b/src/Twig/CrudExtension.php index 4b563cd..abaabad 100644 --- a/src/Twig/CrudExtension.php +++ b/src/Twig/CrudExtension.php @@ -19,6 +19,7 @@ use Symfony\Component\Form\FormView; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Contracts\Translation\TranslatableInterface; use Twig\Environment; use Twig\Extension\AbstractExtension; use Twig\TwigFunction; @@ -330,7 +331,7 @@ public function th(Environment $environment, string $columnId, Crud $crud, array return $value; }); - $resolver->setAllowedTypes('label', ['null', 'string']); + $resolver->setAllowedTypes('label', ['null', 'string', TranslatableInterface::class]); $options = $resolver->resolve($this->buildOptions('crud_th', $options, $crud)); // If the column is not to be shown, returns empty diff --git a/tests/Functional/App/Controller/UserController.php b/tests/Functional/App/Controller/UserController.php index d56cd8c..f3e29d7 100644 --- a/tests/Functional/App/Controller/UserController.php +++ b/tests/Functional/App/Controller/UserController.php @@ -17,6 +17,7 @@ use Ecommit\CrudBundle\Crud\Crud; use Ecommit\CrudBundle\Tests\Functional\App\Entity\TestUser; use Ecommit\CrudBundle\Tests\Functional\App\Form\Searcher\UserSearcher; +use Symfony\Component\Translation\TranslatableMessage; class UserController extends AbstractCrudController { @@ -31,7 +32,7 @@ protected function getCrudOptions(): array $crudConfig = $this->createCrudConfig(static::getCrudName()) ->addColumn('username', 'u.username', 'username', ['displayed_by_default' => false]) ->addColumn('firstName', 'u.firstName', 'first_name') - ->addColumn('lastName', 'u.lastName', 'last_name') + ->addColumn('lastName', 'u.lastName', new TranslatableMessage('last_name')) ->setQueryBuilder($queryBuilder) ->setMaxPerPage([5, 10, 50], 5) ->setDefaultSort('firstName', Crud::ASC) From bb13c581b94f611a1e3154f7acf8717f6fb2fabe Mon Sep 17 00:00:00 2001 From: hlecorche Date: Tue, 19 Sep 2023 15:56:34 +0200 Subject: [PATCH 215/264] Fix CS --- .../Functional/App/Controller/UserWithoutTraitController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Functional/App/Controller/UserWithoutTraitController.php b/tests/Functional/App/Controller/UserWithoutTraitController.php index 2a77209..706638f 100644 --- a/tests/Functional/App/Controller/UserWithoutTraitController.php +++ b/tests/Functional/App/Controller/UserWithoutTraitController.php @@ -67,7 +67,7 @@ public function crudAction(Request $request, CrudResponseGenerator $crudResponse return $data; }, 'after_build' => function (Crud $crud, array $data) { - $data['test_before_after_build'] = $data['test_before_after_build'].' AFTER'; + $data['test_before_after_build'] .= ' AFTER'; return $data; }, @@ -90,7 +90,7 @@ public function ajaxCrudAction(Request $request, CrudResponseGenerator $crudResp return $data; }, 'after_build' => function (Crud $crud, array $data) { - $data['test_before_after_build'] = $data['test_before_after_build'].' AFTER'; + $data['test_before_after_build'] .= ' AFTER'; return $data; }, From 19d03e6aff1f4ac87169025aff921365c6e2eb0f Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sun, 24 Dec 2023 14:56:28 +0100 Subject: [PATCH 216/264] [CI - Coding Standards] Use PHP 8.2 --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0a73078..66db2af 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -58,7 +58,7 @@ jobs: - php-version: '8.2' #CS - - php-version: '8.1' + - php-version: '8.2' description: 'with Coding Standards' coding-standards: true From d159a042c62cc837459ac10cb0b0783e4a2af84b Mon Sep 17 00:00:00 2001 From: hlecorche Date: Tue, 24 Oct 2023 13:11:50 +0200 Subject: [PATCH 217/264] Add Symfony 7.0 support and PHP 8.3 tests --- .github/workflows/tests.yml | 63 +++++++++++-------- composer.json | 46 +++++++------- src/DependencyInjection/Configuration.php | 2 +- .../Entity/EntitiesToChoicesTransformer.php | 4 +- .../Entity/EntitiesToIdsTransformer.php | 2 +- .../Entity/EntityToChoiceTransformer.php | 4 +- .../Entity/EntityToIdTransformer.php | 2 +- src/Form/Filter/DateFilter.php | 1 + tests/Functional/App/Kernel.php | 6 ++ tests/Functional/App/config/security.yaml | 1 - 10 files changed, 74 insertions(+), 57 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 66db2af..403bbb1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -19,43 +19,42 @@ jobs: composer-flags: '--prefer-stable --prefer-lowest' description: 'with SF 5.4.* lowest' - php-version: '8.1' - symfony-version: '6.0.*' + symfony-version: '6.4.*' composer-flags: '--prefer-stable --prefer-lowest' - description: 'with SF 6.0.* lowest' - - php-version: '8.1' - symfony-version: '6.1.*' - composer-flags: '--prefer-stable --prefer-lowest' - description: 'with SF 6.1.* lowest' - - php-version: '8.1' - symfony-version: '6.2.*' - composer-flags: '--prefer-stable --prefer-lowest' - description: 'with SF 6.2.* lowest' - - php-version: '8.1' - symfony-version: '6.3.*' + description: 'with SF 6.4.* lowest' + - php-version: '8.2' + symfony-version: '7.0.*' composer-flags: '--prefer-stable --prefer-lowest' - description: 'with SF 6.3.* lowest' + description: 'with SF 7.0.* lowest' #Symfony versions - - php-version: '8.2' + - php-version: '8.3' symfony-version: '5.4.*' description: 'with SF 5.4.*' - - php-version: '8.2' - symfony-version: '6.0.*' - description: 'with SF 6.0.*' - - php-version: '8.2' - symfony-version: '6.1.*' - description: 'with SF 6.1.*' - - php-version: '8.2' - symfony-version: '6.2.*' - description: 'with SF 6.2.*' - - php-version: '8.2' - symfony-version: '6.3.*' - description: 'with SF 6.3.*' + - php-version: '8.3' + symfony-version: '5.4.*@dev' + description: 'with SF 5.4.* dev' + - php-version: '8.3' + symfony-version: '6.4.*' + description: 'with SF 6.4.*' + - php-version: '8.3' + symfony-version: '6.4.*@dev' + description: 'with SF 6.4.* dev' + - php-version: '8.3' + symfony-version: '7.0.*' + description: 'with SF 7.0.*' + - php-version: '8.3' + symfony-version: '7.0.*@dev' + description: 'with SF 7.0.* dev' + - php-version: '8.3' + symfony-version: '7.1.*@dev' + description: 'with SF 7.1.* dev' #PHP versions - php-version: '8.1' - php-version: '8.2' + - php-version: '8.3' #CS - php-version: '8.2' @@ -114,6 +113,18 @@ jobs: key: composer-${{ runner.os }}-${{ matrix.php-version }}-${{ hashFiles('**/composer.*') }}-${{ matrix.composer-flags }}-${{ hashFiles('**/yarn.lock') }} restore-keys: composer-${{ runner.os }}-${{ matrix.php-version }}- + - name: Configure minimum stability + if: matrix.symfony-version && contains(matrix.symfony-version, '@dev') + run: composer config minimum-stability dev + + - name: Remove friendsofphp/php-cs-fixer + if: matrix.coding-standards != true + run: composer remove friendsofphp/php-cs-fixer --dev --no-update + + - name: Remove vimeo/psalm + if: matrix.static-analysis != true + run: composer remove vimeo/psalm --dev --no-update + - name: Configure Symfony Flex if: matrix.symfony-version run: composer config extra.symfony.require ${{ matrix.symfony-version }} diff --git a/composer.json b/composer.json index 208c158..28094bb 100644 --- a/composer.json +++ b/composer.json @@ -22,29 +22,29 @@ "ecommit/paginator": "^1.0", "ecommit/scalar-values": "^1.0", "psr/container": "^1.1|^2.0", - "symfony/asset": "^5.4|^6.0", - "symfony/config": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/doctrine-bridge": "^5.4.3|^6.0.3", - "symfony/form": "^5.4|^6.0", - "symfony/framework-bundle": "^5.4|^6.0", - "symfony/http-client": "^5.4|^6.0", + "symfony/asset": "^5.4|^6.0|^7.0", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/doctrine-bridge": "^5.4.3|^6.0.3|^7.0", + "symfony/form": "^5.4|^6.0|^7.0", + "symfony/framework-bundle": "^5.4|^6.0|^7.0", + "symfony/http-client": "^5.4|^6.0|^7.0", "symfony/http-client-contracts": "^2.4|^3.0", - "symfony/http-foundation": "^5.4|^6.0", - "symfony/http-kernel": "^5.4|^6.0", - "symfony/intl": "^5.4|^6.0", - "symfony/options-resolver": "^5.4|^6.0", - "symfony/property-access": "^5.4|^6.0", - "symfony/routing": "^5.4|^6.0", - "symfony/security-bundle": "^5.4|^6.0", - "symfony/security-core": "^5.4|^6.0", - "symfony/security-csrf": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/intl": "^5.4|^6.0|^7.0", + "symfony/options-resolver": "^5.4|^6.0|^7.0", + "symfony/property-access": "^5.4|^6.0|^7.0", + "symfony/routing": "^5.4|^6.0|^7.0", + "symfony/security-bundle": "^5.4|^6.0|^7.0", + "symfony/security-core": "^5.4|^6.0|^7.0", + "symfony/security-csrf": "^5.4|^6.0|^7.0", "symfony/service-contracts": "^1.1.6|^2|^3", - "symfony/translation": "^5.4|^6.0", + "symfony/translation": "^5.4|^6.0|^7.0", "symfony/translation-contracts": "^2.3|^3.0", - "symfony/twig-bridge": "^5.4|^6.0", - "symfony/twig-bundle": "^5.4|^6.0", - "symfony/validator": "^5.4|^6.0", + "symfony/twig-bridge": "^5.4|^6.0|^7.0", + "symfony/twig-bundle": "^5.4|^6.0|^7.0", + "symfony/validator": "^5.4|^6.0|^7.0", "twig/twig": "^2.12.0|^3.0" }, "require-dev": { @@ -53,10 +53,10 @@ "doctrine/doctrine-fixtures-bundle": "^3.3", "friendsofphp/php-cs-fixer": "^3.0", "phpunit/phpunit": "^9.0", - "symfony/dom-crawler": "^5.4|^6.0", + "symfony/dom-crawler": "^5.4|^6.0|^7.0", "symfony/panther": "^2.0.1", - "symfony/webpack-encore-bundle": "^1.7.3", - "symfony/yaml": "^5.4|^6.0", + "symfony/webpack-encore-bundle": "^1.7.3|^2.1.1", + "symfony/yaml": "^5.4|^6.0|^7.0", "vimeo/psalm": "^4.22" }, "autoload": { diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 50798a6..42a6f01 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -28,7 +28,7 @@ class Configuration implements ConfigurationInterface * @psalm-suppress PossiblyUndefinedMethod * @psalm-suppress PossiblyNullReference */ - public function getConfigTreeBuilder() + public function getConfigTreeBuilder(): TreeBuilder { $treeBuilder = new TreeBuilder('ecommit_crud'); /** @var ArrayNodeDefinition $rootNode */ diff --git a/src/Form/DataTransformer/Entity/EntitiesToChoicesTransformer.php b/src/Form/DataTransformer/Entity/EntitiesToChoicesTransformer.php index 4ec0baf..c133ad0 100644 --- a/src/Form/DataTransformer/Entity/EntitiesToChoicesTransformer.php +++ b/src/Form/DataTransformer/Entity/EntitiesToChoicesTransformer.php @@ -28,7 +28,7 @@ public function __construct(QueryBuilder $queryBuilder, string $identifier, mixe parent::__construct($queryBuilder, $identifier, $choiceLabel, $throwExceptionIfValueNotFoundInReverse); } - public function transform(mixed $value) + public function transform(mixed $value): mixed { if (null === $value) { return []; @@ -49,7 +49,7 @@ public function transform(mixed $value) return $results; } - public function reverseTransform(mixed $value) + public function reverseTransform(mixed $value): mixed { $collection = new ArrayCollection(); diff --git a/src/Form/DataTransformer/Entity/EntitiesToIdsTransformer.php b/src/Form/DataTransformer/Entity/EntitiesToIdsTransformer.php index c649c66..d6a8eea 100644 --- a/src/Form/DataTransformer/Entity/EntitiesToIdsTransformer.php +++ b/src/Form/DataTransformer/Entity/EntitiesToIdsTransformer.php @@ -18,7 +18,7 @@ class EntitiesToIdsTransformer extends EntitiesToChoicesTransformer { - public function transform(mixed $value) + public function transform(mixed $value): mixed { if (null === $value) { return []; diff --git a/src/Form/DataTransformer/Entity/EntityToChoiceTransformer.php b/src/Form/DataTransformer/Entity/EntityToChoiceTransformer.php index 099a271..3677596 100644 --- a/src/Form/DataTransformer/Entity/EntityToChoiceTransformer.php +++ b/src/Form/DataTransformer/Entity/EntityToChoiceTransformer.php @@ -19,7 +19,7 @@ class EntityToChoiceTransformer extends AbstractEntityTransformer { - public function transform(mixed $value) + public function transform(mixed $value): mixed { if (null === $value || '' === $value) { return null; @@ -38,7 +38,7 @@ public function transform(mixed $value) return $results; } - public function reverseTransform(mixed $value) + public function reverseTransform(mixed $value): mixed { if ('' === $value || null === $value) { return null; diff --git a/src/Form/DataTransformer/Entity/EntityToIdTransformer.php b/src/Form/DataTransformer/Entity/EntityToIdTransformer.php index 864d06b..864996a 100644 --- a/src/Form/DataTransformer/Entity/EntityToIdTransformer.php +++ b/src/Form/DataTransformer/Entity/EntityToIdTransformer.php @@ -17,7 +17,7 @@ class EntityToIdTransformer extends EntityToChoiceTransformer { - public function transform(mixed $value) + public function transform(mixed $value): mixed { if (null === $value || '' === $value) { return null; diff --git a/src/Form/Filter/DateFilter.php b/src/Form/Filter/DateFilter.php index 4090c71..3156357 100644 --- a/src/Form/Filter/DateFilter.php +++ b/src/Form/Filter/DateFilter.php @@ -30,6 +30,7 @@ public function buildForm(SearchFormBuilder $builder, string $property, array $o { $typeOptions = $this->getTypeOptions($options, [ 'input' => 'datetime', + 'widget' => 'choice', ]); $type = ($options['with_time']) ? DateTimeType::class : DateType::class; $builder->addField($property, $type, $typeOptions); diff --git a/tests/Functional/App/Kernel.php b/tests/Functional/App/Kernel.php index 3484386..045fa54 100644 --- a/tests/Functional/App/Kernel.php +++ b/tests/Functional/App/Kernel.php @@ -39,6 +39,12 @@ protected function configureContainer(ContainerBuilder $container, LoaderInterfa $loader->load($this->getProjectDir().'/config/webpack_encore.yaml'); $loader->load($this->getProjectDir().'/config/ecommit_crud.yaml'); $loader->load($this->getProjectDir().'/config/services.yaml'); + + if (5 === static::MAJOR_VERSION) { + $container->loadFromExtension('security', [ + 'enable_authenticator_manager' => true, + ]); + } } public function process(ContainerBuilder $container): void diff --git a/tests/Functional/App/config/security.yaml b/tests/Functional/App/config/security.yaml index 29a9ffa..7893b29 100644 --- a/tests/Functional/App/config/security.yaml +++ b/tests/Functional/App/config/security.yaml @@ -1,5 +1,4 @@ security: - enable_authenticator_manager: true password_hashers: Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'plaintext' From 6206b242cedc858347a90d64cc1598fc5eac7c99 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Fri, 29 Dec 2023 14:14:37 +0100 Subject: [PATCH 218/264] [TH] Add Markup support --- src/Twig/CrudExtension.php | 4 +++- templates/Theme/base.html.twig | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Twig/CrudExtension.php b/src/Twig/CrudExtension.php index abaabad..db92e44 100644 --- a/src/Twig/CrudExtension.php +++ b/src/Twig/CrudExtension.php @@ -22,6 +22,7 @@ use Symfony\Contracts\Translation\TranslatableInterface; use Twig\Environment; use Twig\Extension\AbstractExtension; +use Twig\Markup; use Twig\TwigFunction; final class CrudExtension extends AbstractExtension @@ -331,7 +332,7 @@ public function th(Environment $environment, string $columnId, Crud $crud, array return $value; }); - $resolver->setAllowedTypes('label', ['null', 'string', TranslatableInterface::class]); + $resolver->setAllowedTypes('label', ['null', 'string', TranslatableInterface::class, Markup::class]); $options = $resolver->resolve($this->buildOptions('crud_th', $options, $crud)); // If the column is not to be shown, returns empty @@ -360,6 +361,7 @@ public function th(Environment $environment, string $columnId, Crud $crud, array 'crud' => $crud, 'options' => $options, 'label' => $label, + 'translate_label' => !$label instanceof Markup, ])); } diff --git a/templates/Theme/base.html.twig b/templates/Theme/base.html.twig index 4d43dad..161f3a8 100644 --- a/templates/Theme/base.html.twig +++ b/templates/Theme/base.html.twig @@ -97,14 +97,14 @@ {% block th_not_sortable %} {% set th_attr = th_attr.not_sortable|merge({class: (th_attr.not_sortable.class|default('') ~ ' ec-crud-th ec-crud-th-not-sortable')|trim}) %} - {{ label|trans }} + {{ (translate_label) ? label|trans : label }} {% endblock %} {% block th_sortable_not_active %} {% set th_attr = th_attr.sortable_not_active|merge({class: (th_attr.sortable_not_active.class|default('') ~ ' ec-crud-th ec-crud-th-sortable-not-active')|trim}) %} {% set a_attr = a_attr.sortable_not_active %} - {{ label|trans }} + {{ (translate_label) ? label|trans : label }} {% endblock %} @@ -127,7 +127,7 @@ {% block th_sortable_active %} - {{- label|trans }} {{ crud_icon(icon_name) -}} + {{- (translate_label) ? label|trans : label }} {{ crud_icon(icon_name) -}} {% endblock %} From 4a7d56eea7829d353d5fe50321ba6ed9ff0324ea Mon Sep 17 00:00:00 2001 From: hlecorche Date: Mon, 1 Jan 2024 13:52:55 +0100 Subject: [PATCH 219/264] Update license years --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 3dfc933..890295d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2011-2023 E-COMMIT +Copyright (c) 2011-2024 E-COMMIT Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From d39a720c56f979cc0f005d2898413d9a42759ebc Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sat, 16 Nov 2024 17:49:04 +0100 Subject: [PATCH 220/264] Fix locale tests --- phpunit.xml.dist | 2 +- tests/Form/Filter/NumberFilterTest.php | 8 ++++---- tests/Functional/App/config/framework.yaml | 1 + 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index e931afd..36b3776 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -12,7 +12,7 @@ - + diff --git a/tests/Form/Filter/NumberFilterTest.php b/tests/Form/Filter/NumberFilterTest.php index 68f24e9..49e7afc 100644 --- a/tests/Form/Filter/NumberFilterTest.php +++ b/tests/Form/Filter/NumberFilterTest.php @@ -34,14 +34,14 @@ public function getTestViewAndQueryBuilderProvider(): array ['5', NumberFilter::GREATER_EQUAL, '5', 'e.name >= :value_integer_propertyName', ['value_integer_propertyName' => '5']], ['5', NumberFilter::SMALLER_THAN, '5', 'e.name < :value_integer_propertyName', ['value_integer_propertyName' => '5']], ['5', NumberFilter::SMALLER_EQUAL, '5', 'e.name <= :value_integer_propertyName', ['value_integer_propertyName' => '5']], - ['5.25', NumberFilter::EQUAL, '5,25', 'e.name = :value_integer_propertyName', ['value_integer_propertyName' => '5.25']], + ['5.25', NumberFilter::EQUAL, '5.25', 'e.name = :value_integer_propertyName', ['value_integer_propertyName' => '5.25']], // Int value [5, NumberFilter::GREATER_THAN, '5', 'e.name > :value_integer_propertyName', ['value_integer_propertyName' => '5']], [5, NumberFilter::GREATER_EQUAL, '5', 'e.name >= :value_integer_propertyName', ['value_integer_propertyName' => '5']], [5, NumberFilter::SMALLER_THAN, '5', 'e.name < :value_integer_propertyName', ['value_integer_propertyName' => '5']], [5, NumberFilter::SMALLER_EQUAL, '5', 'e.name <= :value_integer_propertyName', ['value_integer_propertyName' => '5']], - [5.25, NumberFilter::EQUAL, '5,25', 'e.name = :value_integer_propertyName', ['value_integer_propertyName' => '5.25']], + [5.25, NumberFilter::EQUAL, '5.25', 'e.name = :value_integer_propertyName', ['value_integer_propertyName' => '5.25']], ]; } @@ -51,8 +51,8 @@ public function getTestSubmitProvider(): array [null, null, ''], ['', null, ''], ['5', 5.0, '5'], - ['5.25', 5.25, '5,25'], - ['5,25', 5.25, '5,25'], + ['5.25', 5.25, '5.25'], + ['5,25', 5.25, '5.25'], ]; } } diff --git a/tests/Functional/App/config/framework.yaml b/tests/Functional/App/config/framework.yaml index b6b17c2..26a851e 100644 --- a/tests/Functional/App/config/framework.yaml +++ b/tests/Functional/App/config/framework.yaml @@ -4,3 +4,4 @@ framework: session: handler_id: null storage_factory_id: session.storage.factory.mock_file + default_locale: en From fd34aa96f54b84c354796bc8a2bae60b6e8e6e46 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sat, 16 Nov 2024 18:15:27 +0100 Subject: [PATCH 221/264] Fix CS --- src/Controller/CrudControllerTrait.php | 2 +- src/Crud/Crud.php | 12 ++++++------ src/Crud/CrudConfig.php | 6 +++--- src/Crud/CrudResponseGenerator.php | 2 +- src/Crud/CrudSession.php | 2 +- src/Crud/Http/QueryBuilder.php | 2 +- src/Crud/SearchFormBuilder.php | 8 ++++---- .../Entity/EntitiesToChoicesTransformer.php | 2 +- .../Entity/EntityToChoiceTransformer.php | 2 +- src/Form/Filter/BooleanFilter.php | 10 +++++----- src/Form/Filter/CollectionFilterTrait.php | 2 +- src/Form/Filter/DateFilter.php | 6 +++--- src/Form/Filter/IntegerFilter.php | 2 +- src/Form/Filter/NotNullFilter.php | 2 +- src/Form/Filter/NullFilter.php | 2 +- src/Form/Filter/TextFilter.php | 2 +- src/Form/Type/EntityAjaxType.php | 4 ++-- src/Twig/CrudExtension.php | 4 ++-- tests/Crud/AbstractCrudTest.php | 2 +- tests/Crud/CrudSessionTest.php | 2 +- tests/Crud/SearchFormBuilderTest.php | 2 +- .../EntitiesToChoicesTransformerTest.php | 2 +- .../DataTransformer/EntitiesToIdsTransformerTest.php | 2 +- .../EntityToChoiceTransformerTest.php | 2 +- .../DataTransformer/EntityToIdTransformerTest.php | 2 +- tests/Form/Type/EntityAjaxTypeTest.php | 2 +- tests/Functional/App/Controller/UserController.php | 2 +- .../App/Controller/UserWithoutTraitController.php | 8 ++++---- .../TestCrudControllerWithPersistentSettingsTest.php | 2 +- tests/Twig/CrudExtensionTest.php | 2 +- 30 files changed, 51 insertions(+), 51 deletions(-) diff --git a/src/Controller/CrudControllerTrait.php b/src/Controller/CrudControllerTrait.php index bc79937..21d08e7 100644 --- a/src/Controller/CrudControllerTrait.php +++ b/src/Controller/CrudControllerTrait.php @@ -26,7 +26,7 @@ abstract protected function getCrudOptions(): array; abstract protected function getTemplateName(string $action): string; - protected function createCrudConfig(string $sessionName = null): CrudConfig + protected function createCrudConfig(?string $sessionName = null): CrudConfig { return new CrudConfig($sessionName); } diff --git a/src/Crud/Crud.php b/src/Crud/Crud.php index dcceb66..ea1b556 100644 --- a/src/Crud/Crud.php +++ b/src/Crud/Crud.php @@ -115,7 +115,7 @@ public function __construct(array $options, protected ContainerInterface $contai throw new \Exception('A column must be an array or a CrudColum instance.'); } if (\array_key_exists($column->getId(), $columns)) { - throw new \Exception(sprintf('The column "column1" already exists.', $column->getId())); + throw new \Exception(\sprintf('The column "column1" already exists.', $column->getId())); } $columns[$column->getId()] = $column; } @@ -133,7 +133,7 @@ public function __construct(array $options, protected ContainerInterface $contai throw new \Exception('A column must be an array or a CrudColum instance.'); } if (\array_key_exists($column->getId(), $columns)) { - throw new \Exception(sprintf('The column "column1" already exists.', $column->getId())); + throw new \Exception(\sprintf('The column "column1" already exists.', $column->getId())); } $columns[$column->getId()] = $column; } @@ -186,7 +186,7 @@ public function __construct(array $options, protected ContainerInterface $contai // Check duplicates in columns / vitual columns $duplicates = array_intersect_key($this->options['columns'], $this->options['virtual_columns']); if (\count($duplicates) > 0) { - throw new \Exception(sprintf('The column "column1" already exists.', array_keys($duplicates)[0])); + throw new \Exception(\sprintf('The column "column1" already exists.', array_keys($duplicates)[0])); } $this->init(); @@ -262,7 +262,7 @@ public function getColumn(string $columnId): CrudColumn if (isset($this->options['columns'][$columnId])) { return $this->options['columns'][$columnId]; } - throw new \Exception(sprintf('The column "%s" does not exist.', $columnId)); + throw new \Exception(\sprintf('The column "%s" does not exist.', $columnId)); } /** @@ -296,7 +296,7 @@ public function getVirtualColumn(string $columnId): CrudColumn if (isset($this->options['virtual_columns'][$columnId])) { return $this->options['virtual_columns'][$columnId]; } - throw new \Exception(sprintf('The column "%s" does not exist.', $columnId)); + throw new \Exception(\sprintf('The column "%s" does not exist.', $columnId)); } public function getQueryBuilder(): \Doctrine\ORM\QueryBuilder|\Doctrine\DBAL\Query\QueryBuilder|QueryBuilderInterface @@ -805,7 +805,7 @@ protected function createDisplaySettingsForm(): void 'resultsPerPage' => $this->getSessionValues()->getMaxPerPage(), 'displayedColumns' => $this->getSessionValues()->getDisplayedColumns(), ]; - $formName = sprintf('crud_display_settings_%s', $this->getSessionName()); + $formName = \sprintf('crud_display_settings_%s', $this->getSessionName()); $this->displaySettingsForm = $this->container->get('form.factory')->createNamed($formName, DisplaySettingsType::class, $data, [ 'results_per_page_choices' => $resultsPerPageChoices, diff --git a/src/Crud/CrudConfig.php b/src/Crud/CrudConfig.php index 3ea5e23..0bd9bd7 100644 --- a/src/Crud/CrudConfig.php +++ b/src/Crud/CrudConfig.php @@ -19,7 +19,7 @@ final class CrudConfig implements \ArrayAccess { protected array $options = []; - public function __construct(string $sessionName = null) + public function __construct(?string $sessionName = null) { if (null !== $sessionName) { $this->setSessionName($sessionName); @@ -155,7 +155,7 @@ public function setRoute(string $name, array $parameters = []): self return $this; } - public function createSearchForm(SearcherInterface $defaultData, string $type = null, array $options = []): self + public function createSearchForm(SearcherInterface $defaultData, ?string $type = null, array $options = []): self { $this->options['search_form_data'] = $defaultData; $this->options['search_form_type'] = $type; @@ -209,7 +209,7 @@ public function setTwigFunctionsConfiguration(array $value): self return $this; } - public function resetOptions(string|array $options = null): self + public function resetOptions(string|array|null $options = null): self { if (null === $options) { $this->options = []; diff --git a/src/Crud/CrudResponseGenerator.php b/src/Crud/CrudResponseGenerator.php index 3ec2384..1b335a6 100644 --- a/src/Crud/CrudResponseGenerator.php +++ b/src/Crud/CrudResponseGenerator.php @@ -133,7 +133,7 @@ protected function renderCrudView(string $view, array $parameters = []): string return $this->container->get('twig')->render($view, $parameters); } - protected function renderCrud(string $view, array $parameters = [], Response $response = null): Response + protected function renderCrud(string $view, array $parameters = [], ?Response $response = null): Response { $content = $this->container->get('twig')->render($view, $parameters); diff --git a/src/Crud/CrudSession.php b/src/Crud/CrudSession.php index 1615e89..5e96581 100644 --- a/src/Crud/CrudSession.php +++ b/src/Crud/CrudSession.php @@ -29,7 +29,7 @@ final class CrudSession /** * @internal */ - public function __construct(protected int $maxPerPage, protected array $displayedColumns, protected string $sort, protected string $sortDirection, SearcherInterface $searchFormData = null) + public function __construct(protected int $maxPerPage, protected array $displayedColumns, protected string $sort, protected string $sortDirection, ?SearcherInterface $searchFormData = null) { $this->searchFormData = ($searchFormData) ? clone $searchFormData : null; // Avoid modification by reference (by the user or an invalid form) } diff --git a/src/Crud/Http/QueryBuilder.php b/src/Crud/Http/QueryBuilder.php index d953a53..59ee9b4 100644 --- a/src/Crud/Http/QueryBuilder.php +++ b/src/Crud/Http/QueryBuilder.php @@ -31,7 +31,7 @@ final class QueryBuilder implements QueryBuilderInterface protected array $orders = []; protected HttpClientInterface $client; - public function __construct(protected string $url, protected string $httpMethod, HttpClientInterface $client = null) + public function __construct(protected string $url, protected string $httpMethod, ?HttpClientInterface $client = null) { if (null === $client) { $client = HttpClient::create(); diff --git a/src/Crud/SearchFormBuilder.php b/src/Crud/SearchFormBuilder.php index fd84008..f8c6e62 100644 --- a/src/Crud/SearchFormBuilder.php +++ b/src/Crud/SearchFormBuilder.php @@ -56,7 +56,7 @@ protected function createFormBuilder(?string $type): void if ($type) { $this->form = $formFactory->createBuilder($type, null, $formOptions); } else { - $formName = sprintf('crud_search_%s', $this->crud->getSessionName()); + $formName = \sprintf('crud_search_%s', $this->crud->getSessionName()); $this->form = $formFactory->createNamedBuilder($formName, FormSearchType::class, null, $formOptions); } } @@ -67,7 +67,7 @@ public function addFilter(string $property, string $filter, array $options = []) throw new \Exception('The "addFilter" method cannot be called after the form creation'); } if (!$this->container->get('ecommit_crud.filters')->has($filter)) { - throw new \Exception(sprintf('The filter "%s" does not exist', $filter)); + throw new \Exception(\sprintf('The filter "%s" does not exist', $filter)); } /** @var FilterInterface $filterService */ $filterService = $this->container->get('ecommit_crud.filters')->get($filter); @@ -139,7 +139,7 @@ public function createForm(): self // Check if column exists $columnId = $filter['options']['column_id']; if (!\array_key_exists($columnId, $this->crud->getColumns()) && !\array_key_exists($columnId, $this->crud->getVirtualColumns())) { - throw new \Exception(sprintf('The column "%s" does not exist', $columnId)); + throw new \Exception(\sprintf('The column "%s" does not exist', $columnId)); } /** @var FilterInterface $filterService */ @@ -219,7 +219,7 @@ public function updateQueryBuilder(\Doctrine\ORM\QueryBuilder|\Doctrine\DBAL\Que /** @var FilterInterface $filterService */ $filterService = $this->container->get('ecommit_crud.filters')->get($filter['name']); if (!$filterService->supportsQueryBuilder($queryBuilder)) { - throw new \Exception(sprintf('The filter "%s" does not support "%s" query builder', $filter['name'], $queryBuilder::class)); + throw new \Exception(\sprintf('The filter "%s" does not support "%s" query builder', $filter['name'], $queryBuilder::class)); } $filterService->updateQueryBuilder($queryBuilder, $property, $value, $filter['options']); } diff --git a/src/Form/DataTransformer/Entity/EntitiesToChoicesTransformer.php b/src/Form/DataTransformer/Entity/EntitiesToChoicesTransformer.php index c133ad0..9fe4896 100644 --- a/src/Form/DataTransformer/Entity/EntitiesToChoicesTransformer.php +++ b/src/Form/DataTransformer/Entity/EntitiesToChoicesTransformer.php @@ -67,7 +67,7 @@ public function reverseTransform(mixed $value): mixed } $value = array_unique($value); if (\count($value) > $this->maxResults) { - throw new TransformationFailedException(sprintf('This collection should contain %s elements or less.', $this->maxResults)); + throw new TransformationFailedException(\sprintf('This collection should contain %s elements or less.', $this->maxResults)); } $hash = $this->getCacheHash($value); diff --git a/src/Form/DataTransformer/Entity/EntityToChoiceTransformer.php b/src/Form/DataTransformer/Entity/EntityToChoiceTransformer.php index 3677596..b397d5f 100644 --- a/src/Form/DataTransformer/Entity/EntityToChoiceTransformer.php +++ b/src/Form/DataTransformer/Entity/EntityToChoiceTransformer.php @@ -62,7 +62,7 @@ public function reverseTransform(mixed $value): mixed } if (1 !== \count($entities)) { if ($this->throwExceptionIfValueNotFoundInReverse) { - throw new TransformationFailedException(sprintf('The entity with key "%s" could not be found or is not unique', (string) $value)); + throw new TransformationFailedException(\sprintf('The entity with key "%s" could not be found or is not unique', (string) $value)); } return null; diff --git a/src/Form/Filter/BooleanFilter.php b/src/Form/Filter/BooleanFilter.php index 7f5744d..a54b88c 100644 --- a/src/Form/Filter/BooleanFilter.php +++ b/src/Form/Filter/BooleanFilter.php @@ -45,25 +45,25 @@ public function updateQueryBuilder(mixed $queryBuilder, string $property, mixed if (static::VALUE_TRUE === $value) { $or = $queryBuilder->expr()->orX(); - $or->add(sprintf('%s = :%s', $options['alias_search'], $parameterTrueName)); + $or->add(\sprintf('%s = :%s', $options['alias_search'], $parameterTrueName)); $queryBuilder->setParameter($parameterTrueName, $options['value_true']); if ($options['not_null_is_true']) { - $or->add(sprintf('%s IS NOT NULL AND %s != :%s', $options['alias_search'], $options['alias_search'], $parameterFalseName)); + $or->add(\sprintf('%s IS NOT NULL AND %s != :%s', $options['alias_search'], $options['alias_search'], $parameterFalseName)); $queryBuilder->setParameter($parameterFalseName, $options['value_false']); } $queryBuilder->andWhere($or); } elseif (static::VALUE_FALSE === $value) { if (null === $options['value_false']) { - $queryBuilder->andWhere(sprintf('%s IS NULL', $options['alias_search'])); + $queryBuilder->andWhere(\sprintf('%s IS NULL', $options['alias_search'])); return; } $or = $queryBuilder->expr()->orX(); - $or->add(sprintf('%s = :%s', $options['alias_search'], $parameterFalseName)); + $or->add(\sprintf('%s = :%s', $options['alias_search'], $parameterFalseName)); $queryBuilder->setParameter($parameterFalseName, $options['value_false']); if ($options['null_is_false']) { - $or->add(sprintf('%s IS NULL', $options['alias_search'])); + $or->add(\sprintf('%s IS NULL', $options['alias_search'])); } $queryBuilder->andWhere($or); } diff --git a/src/Form/Filter/CollectionFilterTrait.php b/src/Form/Filter/CollectionFilterTrait.php index 95896cf..1623844 100644 --- a/src/Form/Filter/CollectionFilterTrait.php +++ b/src/Form/Filter/CollectionFilterTrait.php @@ -62,7 +62,7 @@ protected function updateCollectionQueryBuilder(mixed $queryBuilder, string $pro if (!\is_scalar($value)) { return; } - $queryBuilder->andWhere(sprintf('%s = :%s', $options['alias_search'], $parameterName)) + $queryBuilder->andWhere(\sprintf('%s = :%s', $options['alias_search'], $parameterName)) ->setParameter($parameterName, $value); } } diff --git a/src/Form/Filter/DateFilter.php b/src/Form/Filter/DateFilter.php index 3156357..2f831d5 100644 --- a/src/Form/Filter/DateFilter.php +++ b/src/Form/Filter/DateFilter.php @@ -55,7 +55,7 @@ public function updateQueryBuilder(mixed $queryBuilder, string $property, mixed } $value = $value->format('Y-m-d H:i:s'); $queryBuilder->andWhere( - sprintf('%s %s :%s', $options['alias_search'], $options['comparator'], $parameterName) + \sprintf('%s %s :%s', $options['alias_search'], $options['comparator'], $parameterName) ) ->setParameter($parameterName, $value); break; @@ -66,7 +66,7 @@ public function updateQueryBuilder(mixed $queryBuilder, string $property, mixed } $value = $value->format('Y-m-d H:i:s'); $queryBuilder->andWhere( - sprintf('%s %s :%s', $options['alias_search'], $options['comparator'], $parameterName) + \sprintf('%s %s :%s', $options['alias_search'], $options['comparator'], $parameterName) ) ->setParameter($parameterName, $value); break; @@ -82,7 +82,7 @@ public function updateQueryBuilder(mixed $queryBuilder, string $property, mixed $parameterNameInf = 'value_date_inf_'.str_replace(' ', '', $property); $parameterNameSup = 'value_date_sup_'.str_replace(' ', '', $property); $queryBuilder->andWhere( - sprintf( + \sprintf( '%s >= :%s AND %s <= :%s', $options['alias_search'], $parameterNameInf, diff --git a/src/Form/Filter/IntegerFilter.php b/src/Form/Filter/IntegerFilter.php index 3ef4840..0834ae2 100644 --- a/src/Form/Filter/IntegerFilter.php +++ b/src/Form/Filter/IntegerFilter.php @@ -39,7 +39,7 @@ public function updateQueryBuilder(mixed $queryBuilder, string $property, mixed $parameterName = 'value_integer_'.str_replace(' ', '', $property); - $queryBuilder->andWhere(sprintf('%s %s :%s', $options['alias_search'], $options['comparator'], $parameterName)) + $queryBuilder->andWhere(\sprintf('%s %s :%s', $options['alias_search'], $options['comparator'], $parameterName)) ->setParameter($parameterName, $value); } diff --git a/src/Form/Filter/NotNullFilter.php b/src/Form/Filter/NotNullFilter.php index 52a9291..74f3607 100644 --- a/src/Form/Filter/NotNullFilter.php +++ b/src/Form/Filter/NotNullFilter.php @@ -21,6 +21,6 @@ public function updateQueryBuilder(mixed $queryBuilder, string $property, mixed return; } - $queryBuilder->andWhere(sprintf('%s IS NOT NULL', $options['alias_search'])); + $queryBuilder->andWhere(\sprintf('%s IS NOT NULL', $options['alias_search'])); } } diff --git a/src/Form/Filter/NullFilter.php b/src/Form/Filter/NullFilter.php index 63ed03e..3f7fe1f 100644 --- a/src/Form/Filter/NullFilter.php +++ b/src/Form/Filter/NullFilter.php @@ -30,6 +30,6 @@ public function updateQueryBuilder(mixed $queryBuilder, string $property, mixed return; } - $queryBuilder->andWhere(sprintf('%s IS NULL', $options['alias_search'])); + $queryBuilder->andWhere(\sprintf('%s IS NULL', $options['alias_search'])); } } diff --git a/src/Form/Filter/TextFilter.php b/src/Form/Filter/TextFilter.php index 9d6569e..8fce5af 100644 --- a/src/Form/Filter/TextFilter.php +++ b/src/Form/Filter/TextFilter.php @@ -42,7 +42,7 @@ public function updateQueryBuilder(mixed $queryBuilder, string $property, mixed $parameterName = 'value_text_'.str_replace(' ', '', $property); if ($options['must_begin'] && $options['must_end']) { - $queryBuilder->andWhere(sprintf('%s = :%s', $options['alias_search'], $parameterName)) + $queryBuilder->andWhere(\sprintf('%s = :%s', $options['alias_search'], $parameterName)) ->setParameter($parameterName, $value); } else { $after = ($options['must_begin']) ? '' : '%'; diff --git a/src/Form/Type/EntityAjaxType.php b/src/Form/Type/EntityAjaxType.php index f33546d..3fe7ec8 100644 --- a/src/Form/Type/EntityAjaxType.php +++ b/src/Form/Type/EntityAjaxType.php @@ -93,14 +93,14 @@ public function configureOptions(OptionsResolver $resolver): void $em = $this->registry->getManagerForClass($options['class']); if (null === $em) { - throw new RuntimeException(sprintf('Class "%s" : Entity manager not found', $options['class'])); + throw new RuntimeException(\sprintf('Class "%s" : Entity manager not found', $options['class'])); } return $em; }; $resolver->setNormalizer('em', $emNormalizer); - $queryBuilderNormalizer = function (Options $options, null|QueryBuilder|\Closure $queryBuilder): QueryBuilder { + $queryBuilderNormalizer = function (Options $options, QueryBuilder|\Closure|null $queryBuilder): QueryBuilder { $em = $options['em']; $class = $options['class']; diff --git a/src/Twig/CrudExtension.php b/src/Twig/CrudExtension.php index db92e44..f76b57c 100644 --- a/src/Twig/CrudExtension.php +++ b/src/Twig/CrudExtension.php @@ -704,7 +704,7 @@ protected function getAjaxAttributes(array $options): array return $attributes; } - public function crudIcon(Environment $environment, string $iconName, string $iconTheme = null): string + public function crudIcon(Environment $environment, string $iconName, ?string $iconTheme = null): string { if (null === $iconTheme) { $iconTheme = $this->iconTheme; @@ -723,7 +723,7 @@ protected function renderBlock(Environment $environment, string $templateName, s return ob_get_clean(); } - protected function buildOptions(string $function, array $inlineOptions, Crud $crud = null): array + protected function buildOptions(string $function, array $inlineOptions, ?Crud $crud = null): array { $options = []; diff --git a/tests/Crud/AbstractCrudTest.php b/tests/Crud/AbstractCrudTest.php index 1c838b0..146dcea 100644 --- a/tests/Crud/AbstractCrudTest.php +++ b/tests/Crud/AbstractCrudTest.php @@ -81,7 +81,7 @@ protected function createCrud(array|CrudConfig $crudConfig, array $filters = [], return $crud; } - protected function createValidCrudConfig(CrudConfig $crudConfig = null, bool $withSearcher = false): CrudConfig + protected function createValidCrudConfig(?CrudConfig $crudConfig = null, bool $withSearcher = false): CrudConfig { $queryBuilder = self::getContainer()->get(ManagerRegistry::class)->getRepository(TestUser::class) ->createQueryBuilder('u') diff --git a/tests/Crud/CrudSessionTest.php b/tests/Crud/CrudSessionTest.php index c095df2..6cdb020 100644 --- a/tests/Crud/CrudSessionTest.php +++ b/tests/Crud/CrudSessionTest.php @@ -239,7 +239,7 @@ protected function setPropertyWithoutGetter(CrudSession $crudSession, string $pr $reflectionMethod->setValue($crudSession, $value); } - protected function createCrudSession(UserSearcher $searchFormData = null): CrudSession + protected function createCrudSession(?UserSearcher $searchFormData = null): CrudSession { return new CrudSession(100, ['col1'], 'sort1', Crud::ASC, $searchFormData); } diff --git a/tests/Crud/SearchFormBuilderTest.php b/tests/Crud/SearchFormBuilderTest.php index b47cb35..47fa5e5 100644 --- a/tests/Crud/SearchFormBuilderTest.php +++ b/tests/Crud/SearchFormBuilderTest.php @@ -470,7 +470,7 @@ protected function createCrudContainer(array $filters = []): ContainerInterface return $container; } - protected function createSearchFormBuilder(array $filters = null, Crud $crud = null, SearcherInterface $defaultData = null, string $type = null, array $options = []): SearchFormBuilder + protected function createSearchFormBuilder(?array $filters = null, ?Crud $crud = null, ?SearcherInterface $defaultData = null, ?string $type = null, array $options = []): SearchFormBuilder { $filters = (null !== $filters) ? $filters : ['my_filter' => $this->createMock(FilterInterface::class)]; $crud = ($crud) ?: $this->createCrud($this->createValidCrudConfig()); diff --git a/tests/Form/DataTransformer/EntitiesToChoicesTransformerTest.php b/tests/Form/DataTransformer/EntitiesToChoicesTransformerTest.php index ce3f3a5..ee53710 100644 --- a/tests/Form/DataTransformer/EntitiesToChoicesTransformerTest.php +++ b/tests/Form/DataTransformer/EntitiesToChoicesTransformerTest.php @@ -45,7 +45,7 @@ public function testTransform($choiceLabel, array $expected): void public function getTestTransformProvider(): array { - $closure = fn (Tag $tag) => sprintf('name: %s', $tag->getName()); + $closure = fn (Tag $tag) => \sprintf('name: %s', $tag->getName()); return [ [null, ['1' => 'tag1', '3' => '3']], // Choice label: null diff --git a/tests/Form/DataTransformer/EntitiesToIdsTransformerTest.php b/tests/Form/DataTransformer/EntitiesToIdsTransformerTest.php index 1f2b7c3..2ea57c6 100644 --- a/tests/Form/DataTransformer/EntitiesToIdsTransformerTest.php +++ b/tests/Form/DataTransformer/EntitiesToIdsTransformerTest.php @@ -28,7 +28,7 @@ protected function createTransformer(...$args): DataTransformerInterface public function getTestTransformProvider(): array { - $closure = fn (Tag $tag) => sprintf('name: %s', $tag->getName()); + $closure = fn (Tag $tag) => \sprintf('name: %s', $tag->getName()); return [ [null, ['1', '3']], // Choice label: null diff --git a/tests/Form/DataTransformer/EntityToChoiceTransformerTest.php b/tests/Form/DataTransformer/EntityToChoiceTransformerTest.php index be28a50..8487414 100644 --- a/tests/Form/DataTransformer/EntityToChoiceTransformerTest.php +++ b/tests/Form/DataTransformer/EntityToChoiceTransformerTest.php @@ -43,7 +43,7 @@ public function testTransform($choiceLabel, $expected): void public function getTestTransformProvider(): array { - $closure = fn (Tag $tag) => sprintf('name: %s', $tag->getName()); + $closure = fn (Tag $tag) => \sprintf('name: %s', $tag->getName()); return [ [null, ['3' => '3']], // Choice label: null diff --git a/tests/Form/DataTransformer/EntityToIdTransformerTest.php b/tests/Form/DataTransformer/EntityToIdTransformerTest.php index 49fb7a4..9b666f6 100644 --- a/tests/Form/DataTransformer/EntityToIdTransformerTest.php +++ b/tests/Form/DataTransformer/EntityToIdTransformerTest.php @@ -27,7 +27,7 @@ protected function createTransformer(...$args): DataTransformerInterface public function getTestTransformProvider(): array { - $closure = fn (Tag $tag) => sprintf('name: %s', $tag->getName()); + $closure = fn (Tag $tag) => \sprintf('name: %s', $tag->getName()); return [ [null, '3'], // Choice label: null diff --git a/tests/Form/Type/EntityAjaxTypeTest.php b/tests/Form/Type/EntityAjaxTypeTest.php index 257767b..5bf4aab 100644 --- a/tests/Form/Type/EntityAjaxTypeTest.php +++ b/tests/Form/Type/EntityAjaxTypeTest.php @@ -242,7 +242,7 @@ public function testViewWithChoiceLabel($choiceLabel, $expectedViewValue): void public function getTestViewWithChoiceLabelProvider(): array { - $closure = fn (Tag $tag) => sprintf('name: %s', $tag->getName()); + $closure = fn (Tag $tag) => \sprintf('name: %s', $tag->getName()); return [ ['name', ['2' => 'tag2']], diff --git a/tests/Functional/App/Controller/UserController.php b/tests/Functional/App/Controller/UserController.php index f3e29d7..dd44fa4 100644 --- a/tests/Functional/App/Controller/UserController.php +++ b/tests/Functional/App/Controller/UserController.php @@ -78,7 +78,7 @@ public function getPersistentSettings(): bool protected function getTemplateName(string $action): string { - return sprintf('user/%s.html.twig', $action); + return \sprintf('user/%s.html.twig', $action); } public function crudAction() diff --git a/tests/Functional/App/Controller/UserWithoutTraitController.php b/tests/Functional/App/Controller/UserWithoutTraitController.php index 706638f..4e0e314 100644 --- a/tests/Functional/App/Controller/UserWithoutTraitController.php +++ b/tests/Functional/App/Controller/UserWithoutTraitController.php @@ -60,7 +60,7 @@ public function crudAction(Request $request, CrudResponseGenerator $crudResponse { if ($request->query->has('test-before-and-after-build-query')) { return $crudResponseGenerator->getResponse($this->getCrud('user_without_trait_with_data', ['test-before-and-after-build-query' => 1]), [ - 'template_generator' => fn (string $action) => sprintf('user/%s.html.twig', $action), + 'template_generator' => fn (string $action) => \sprintf('user/%s.html.twig', $action), 'before_build' => function (Crud $crud, array $data) { $data['test_before_after_build'] = 'BEFORE'; @@ -75,7 +75,7 @@ public function crudAction(Request $request, CrudResponseGenerator $crudResponse } return $crudResponseGenerator->getResponse($this->getCrud('user_without_trait'), [ - 'template_generator' => fn (string $action) => sprintf('user/%s.html.twig', $action), + 'template_generator' => fn (string $action) => \sprintf('user/%s.html.twig', $action), ]); } @@ -83,7 +83,7 @@ public function ajaxCrudAction(Request $request, CrudResponseGenerator $crudResp { if ($request->query->has('test-before-and-after-build-query')) { return $crudResponseGenerator->getAjaxResponse($this->getCrud('user_without_trait_with_data', ['test-before-and-after-build-query' => 1]), [ - 'template_generator' => fn (string $action) => sprintf('user/%s.html.twig', $action), + 'template_generator' => fn (string $action) => \sprintf('user/%s.html.twig', $action), 'before_build' => function (Crud $crud, array $data) { $data['test_before_after_build'] = 'BEFORE'; @@ -98,7 +98,7 @@ public function ajaxCrudAction(Request $request, CrudResponseGenerator $crudResp } return $crudResponseGenerator->getAjaxResponse($this->getCrud('user_without_trait'), [ - 'template_generator' => fn (string $action) => sprintf('user/%s.html.twig', $action), + 'template_generator' => fn (string $action) => \sprintf('user/%s.html.twig', $action), ]); } diff --git a/tests/Functional/Controller/TestCrudControllerWithPersistentSettingsTest.php b/tests/Functional/Controller/TestCrudControllerWithPersistentSettingsTest.php index fe14c75..ecf7fd9 100644 --- a/tests/Functional/Controller/TestCrudControllerWithPersistentSettingsTest.php +++ b/tests/Functional/Controller/TestCrudControllerWithPersistentSettingsTest.php @@ -270,7 +270,7 @@ protected function logout(Client $client): void $client->request('GET', '/logout'); } - protected function changeSettings(Client $client, array $headerColumnClicks = [], array $displayColumnClicks = [], int $resultsPerPage = null): void + protected function changeSettings(Client $client, array $headerColumnClicks = [], array $displayColumnClicks = [], ?int $resultsPerPage = null): void { foreach ($headerColumnClicks as $column) { $link = $client->getCrawler()->filterXPath('//table[@class="result"]/thead/tr/th/a[contains(text(), "'.$column.'")]'); diff --git a/tests/Twig/CrudExtensionTest.php b/tests/Twig/CrudExtensionTest.php index 765b3fe..b056ec0 100644 --- a/tests/Twig/CrudExtensionTest.php +++ b/tests/Twig/CrudExtensionTest.php @@ -248,7 +248,7 @@ public function getTestPaginatorWithTypeOptionProvider(): array $result = ''; for ($i = 1; $i <= 20; ++$i) { $class = ($i === $currentPage) ? ' class="current"' : ''; - $result .= sprintf('%s', $class, $i, $i); + $result .= \sprintf('%s', $class, $i, $i); } return $result; From 70206c24ace9c2fb5033884e3211a28e9d8a38b9 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sat, 16 Nov 2024 18:30:07 +0100 Subject: [PATCH 222/264] Require Symfony ^6.4|^7.1 --- .github/workflows/tests.yml | 24 ++++++-------------- composer.json | 44 ++++++++++++++++++------------------- 2 files changed, 29 insertions(+), 39 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 403bbb1..80c8b1c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,27 +14,17 @@ jobs: matrix: include: #Mini (for each Symfony version) - - php-version: '8.1' - symfony-version: '5.4.*' - composer-flags: '--prefer-stable --prefer-lowest' - description: 'with SF 5.4.* lowest' - php-version: '8.1' symfony-version: '6.4.*' composer-flags: '--prefer-stable --prefer-lowest' description: 'with SF 6.4.* lowest' - php-version: '8.2' - symfony-version: '7.0.*' + symfony-version: '7.1.*' composer-flags: '--prefer-stable --prefer-lowest' - description: 'with SF 7.0.* lowest' + description: 'with SF 7.1.* lowest' #Symfony versions - - php-version: '8.3' - symfony-version: '5.4.*' - description: 'with SF 5.4.*' - - php-version: '8.3' - symfony-version: '5.4.*@dev' - description: 'with SF 5.4.* dev' - php-version: '8.3' symfony-version: '6.4.*' description: 'with SF 6.4.*' @@ -42,14 +32,14 @@ jobs: symfony-version: '6.4.*@dev' description: 'with SF 6.4.* dev' - php-version: '8.3' - symfony-version: '7.0.*' - description: 'with SF 7.0.*' - - php-version: '8.3' - symfony-version: '7.0.*@dev' - description: 'with SF 7.0.* dev' + symfony-version: '7.1.*' + description: 'with SF 7.1.*' - php-version: '8.3' symfony-version: '7.1.*@dev' description: 'with SF 7.1.* dev' + - php-version: '8.3' + symfony-version: '7.2.*@dev' + description: 'with SF 7.2.* dev' #PHP versions - php-version: '8.1' diff --git a/composer.json b/composer.json index 28094bb..7664b71 100644 --- a/composer.json +++ b/composer.json @@ -22,29 +22,29 @@ "ecommit/paginator": "^1.0", "ecommit/scalar-values": "^1.0", "psr/container": "^1.1|^2.0", - "symfony/asset": "^5.4|^6.0|^7.0", - "symfony/config": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/doctrine-bridge": "^5.4.3|^6.0.3|^7.0", - "symfony/form": "^5.4|^6.0|^7.0", - "symfony/framework-bundle": "^5.4|^6.0|^7.0", - "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/asset": "^6.4|^7.1", + "symfony/config": "^6.4|^7.1", + "symfony/dependency-injection": "^6.4|^7.1", + "symfony/doctrine-bridge": "^6.4|^7.1", + "symfony/form": "^6.4|^7.1", + "symfony/framework-bundle": "^6.4|^7.1", + "symfony/http-client": "^6.4|^7.1", "symfony/http-client-contracts": "^2.4|^3.0", - "symfony/http-foundation": "^5.4|^6.0|^7.0", - "symfony/http-kernel": "^5.4|^6.0|^7.0", - "symfony/intl": "^5.4|^6.0|^7.0", - "symfony/options-resolver": "^5.4|^6.0|^7.0", - "symfony/property-access": "^5.4|^6.0|^7.0", - "symfony/routing": "^5.4|^6.0|^7.0", - "symfony/security-bundle": "^5.4|^6.0|^7.0", - "symfony/security-core": "^5.4|^6.0|^7.0", - "symfony/security-csrf": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^6.4|^7.1", + "symfony/http-kernel": "^6.4|^7.1", + "symfony/intl": "^6.4|^7.1", + "symfony/options-resolver": "^6.4|^7.1", + "symfony/property-access": "^6.4|^7.1", + "symfony/routing": "^6.4|^7.1", + "symfony/security-bundle": "^6.4|^7.1", + "symfony/security-core": "^6.4|^7.1", + "symfony/security-csrf": "^6.4|^7.1", "symfony/service-contracts": "^1.1.6|^2|^3", - "symfony/translation": "^5.4|^6.0|^7.0", + "symfony/translation": "^6.4|^7.1", "symfony/translation-contracts": "^2.3|^3.0", - "symfony/twig-bridge": "^5.4|^6.0|^7.0", - "symfony/twig-bundle": "^5.4|^6.0|^7.0", - "symfony/validator": "^5.4|^6.0|^7.0", + "symfony/twig-bridge": "^6.4|^7.1", + "symfony/twig-bundle": "^6.4|^7.1", + "symfony/validator": "^6.4|^7.1", "twig/twig": "^2.12.0|^3.0" }, "require-dev": { @@ -53,10 +53,10 @@ "doctrine/doctrine-fixtures-bundle": "^3.3", "friendsofphp/php-cs-fixer": "^3.0", "phpunit/phpunit": "^9.0", - "symfony/dom-crawler": "^5.4|^6.0|^7.0", + "symfony/dom-crawler": "^6.4|^7.1", "symfony/panther": "^2.0.1", "symfony/webpack-encore-bundle": "^1.7.3|^2.1.1", - "symfony/yaml": "^5.4|^6.0|^7.0", + "symfony/yaml": "^6.4|^7.1", "vimeo/psalm": "^4.22" }, "autoload": { From c3551f1e02787a12748fae339df5bd43aeef5213 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sat, 16 Nov 2024 21:03:58 +0100 Subject: [PATCH 223/264] Add Doctrine DBAL v4 / ORM v3 support, remove DBAL v2 support --- composer.json | 6 +++--- src/Crud/Crud.php | 5 +++-- src/EventListener/MappingEntities.php | 3 ++- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 7664b71..526ed4c 100644 --- a/composer.json +++ b/composer.json @@ -14,11 +14,11 @@ "ext-json": "*", "ext-mbstring": "*", "doctrine/collections": "^1.5|^2.0", - "doctrine/dbal": "^2.13.1|^3.2", + "doctrine/dbal": "^3.2|^4.0", "doctrine/doctrine-bundle": "^2.4.5", - "doctrine/orm": "^2.9", + "doctrine/orm": "^2.9|^3.0", "doctrine/persistence": "^1.3|^2.0|^3.0", - "ecommit/doctrine-utils": "^1.0", + "ecommit/doctrine-utils": "^1.0|^2.0", "ecommit/paginator": "^1.0", "ecommit/scalar-values": "^1.0", "psr/container": "^1.1|^2.0", diff --git a/src/Crud/Crud.php b/src/Crud/Crud.php index ea1b556..4e54997 100644 --- a/src/Crud/Crud.php +++ b/src/Crud/Crud.php @@ -584,9 +584,10 @@ protected function resetDisplaySettings(): void if ($this->options['persistent_settings']) { // Remove settings in database $qb = $this->container->get('doctrine')->getManager()->createQueryBuilder(); - $qb->delete('EcommitCrudBundle:UserCrudSettings', 's') + $qb->delete(UserCrudSettings::class, 's') ->andWhere('s.user = :user AND s.crudName = :crud_name') - ->setParameters(['user' => $this->container->get('security.token_storage')->getToken()->getUser(), 'crud_name' => $this->getSessionName()]) + ->setParameter('user', $this->container->get('security.token_storage')->getToken()->getUser()) + ->setParameter('crud_name', $this->getSessionName()) ->getQuery() ->execute(); } diff --git a/src/EventListener/MappingEntities.php b/src/EventListener/MappingEntities.php index a3a211c..8fa0d7a 100644 --- a/src/EventListener/MappingEntities.php +++ b/src/EventListener/MappingEntities.php @@ -14,6 +14,7 @@ namespace Ecommit\CrudBundle\EventListener; use Doctrine\ORM\Event\LoadClassMetadataEventArgs; +use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\ClassMetadataInfo; final class MappingEntities @@ -43,7 +44,7 @@ public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs): void } } - protected function mappUserCrudSettings(ClassMetadataInfo $userCrudSettingsMetadata, ClassMetadataInfo $userMetadata): void + protected function mappUserCrudSettings(ClassMetadataInfo|ClassMetadata $userCrudSettingsMetadata, ClassMetadataInfo|ClassMetadata $userMetadata): void { $this->isLoad = true; From 077014d6a0db5953b0d1b52dd2332b6e0649ff7c Mon Sep 17 00:00:00 2001 From: hlecorche Date: Mon, 18 Nov 2024 18:23:20 +0100 Subject: [PATCH 224/264] Upgrade psalm --- composer.json | 2 +- psalm.xml | 14 ++++++++++++++ src/Crud/Crud.php | 8 +++----- src/Crud/SearchFormBuilder.php | 2 +- src/DependencyInjection/Configuration.php | 1 + src/Form/Filter/TextFilter.php | 4 ++-- src/Twig/CrudExtension.php | 5 +++++ 7 files changed, 27 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index 526ed4c..e19a981 100644 --- a/composer.json +++ b/composer.json @@ -57,7 +57,7 @@ "symfony/panther": "^2.0.1", "symfony/webpack-encore-bundle": "^1.7.3|^2.1.1", "symfony/yaml": "^6.4|^7.1", - "vimeo/psalm": "^4.22" + "vimeo/psalm": "^5.0" }, "autoload": { "psr-4": { "Ecommit\\CrudBundle\\": "src/" } diff --git a/psalm.xml b/psalm.xml index 8280a17..95e414a 100644 --- a/psalm.xml +++ b/psalm.xml @@ -20,5 +20,19 @@ + + + + + + + + + + + + + + diff --git a/src/Crud/Crud.php b/src/Crud/Crud.php index 4e54997..272760f 100644 --- a/src/Crud/Crud.php +++ b/src/Crud/Crud.php @@ -115,7 +115,7 @@ public function __construct(array $options, protected ContainerInterface $contai throw new \Exception('A column must be an array or a CrudColum instance.'); } if (\array_key_exists($column->getId(), $columns)) { - throw new \Exception(\sprintf('The column "column1" already exists.', $column->getId())); + throw new \Exception(\sprintf('The column "%s" already exists.', $column->getId())); } $columns[$column->getId()] = $column; } @@ -133,7 +133,7 @@ public function __construct(array $options, protected ContainerInterface $contai throw new \Exception('A column must be an array or a CrudColum instance.'); } if (\array_key_exists($column->getId(), $columns)) { - throw new \Exception(\sprintf('The column "column1" already exists.', $column->getId())); + throw new \Exception(\sprintf('The column "%s" already exists.', $column->getId())); } $columns[$column->getId()] = $column; } @@ -186,12 +186,10 @@ public function __construct(array $options, protected ContainerInterface $contai // Check duplicates in columns / vitual columns $duplicates = array_intersect_key($this->options['columns'], $this->options['virtual_columns']); if (\count($duplicates) > 0) { - throw new \Exception(\sprintf('The column "column1" already exists.', array_keys($duplicates)[0])); + throw new \Exception(\sprintf('The column "%s" already exists.', array_keys($duplicates)[0])); } $this->init(); - - return $this; } protected function init(): void diff --git a/src/Crud/SearchFormBuilder.php b/src/Crud/SearchFormBuilder.php index f8c6e62..f00413a 100644 --- a/src/Crud/SearchFormBuilder.php +++ b/src/Crud/SearchFormBuilder.php @@ -53,7 +53,7 @@ protected function createFormBuilder(?string $type): void $formOptions['validation_groups'] = $this->options['validation_groups']; } - if ($type) { + if (null !== $type) { $this->form = $formFactory->createBuilder($type, null, $formOptions); } else { $formName = \sprintf('crud_search_%s', $this->crud->getSessionName()); diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 42a6f01..1a40e91 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -27,6 +27,7 @@ class Configuration implements ConfigurationInterface /** * @psalm-suppress PossiblyUndefinedMethod * @psalm-suppress PossiblyNullReference + * @psalm-suppress UndefinedInterfaceMethod */ public function getConfigTreeBuilder(): TreeBuilder { diff --git a/src/Form/Filter/TextFilter.php b/src/Form/Filter/TextFilter.php index 8fce5af..5b7a2ff 100644 --- a/src/Form/Filter/TextFilter.php +++ b/src/Form/Filter/TextFilter.php @@ -45,8 +45,8 @@ public function updateQueryBuilder(mixed $queryBuilder, string $property, mixed $queryBuilder->andWhere(\sprintf('%s = :%s', $options['alias_search'], $parameterName)) ->setParameter($parameterName, $value); } else { - $after = ($options['must_begin']) ? '' : '%'; - $before = ($options['must_end']) ? '' : '%'; + $after = (true === $options['must_begin']) ? '' : '%'; + $before = (true === $options['must_end']) ? '' : '%'; $value = addcslashes($value, '%_'); $like = $after.$value.$before; $queryBuilder->andWhere($queryBuilder->expr()->like($options['alias_search'], ':'.$parameterName)) diff --git a/src/Twig/CrudExtension.php b/src/Twig/CrudExtension.php index f76b57c..4f1483c 100644 --- a/src/Twig/CrudExtension.php +++ b/src/Twig/CrudExtension.php @@ -683,6 +683,11 @@ protected function validateAjaxOptions(array $options, array $requiredOptions = return $resolver->resolve($options); } + /** + * @param array|null> $options + * + * @return array + */ protected function getAjaxAttributes(array $options): array { $attributes = []; From 85e7909e2548ea70ae9f820798c78397c280bc66 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sun, 24 Nov 2024 12:33:14 +0100 Subject: [PATCH 225/264] Upgrade eslint v9 and replace "standard" by "neostandard" --- .eslintrc.js | 22 ---------------------- README.md | 3 +-- assets/js/ajax.js | 26 +++++++++++++------------- eslint.config.mjs | 24 ++++++++++++++++++++++++ package.json | 8 +++----- tests/assets/js/ajax.spec.js | 2 +- 6 files changed, 42 insertions(+), 43 deletions(-) delete mode 100644 .eslintrc.js create mode 100644 eslint.config.mjs diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index ee37113..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,22 +0,0 @@ -module.exports = { - env: { - 'browser': true, - 'es6': true, - 'jasmine': true, - 'node': true - }, - extends: [ - 'standard' - ], - globals: { - Atomics: 'readonly', - SharedArrayBuffer: 'readonly' - }, - parserOptions: { - ecmaVersion: 2018, - sourceType: 'module' - }, - rules: { - 'linebreak-style': ['error', 'unix'], - } -} diff --git a/README.md b/README.md index b058385..eb79480 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,7 @@ ![Tests](https://github.com/e-commit/EcommitCrudBundle/workflows/Tests/badge.svg) -[![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) - +[![neostandard javascript style](https://img.shields.io/badge/code_style-neostandard-brightgreen?style=flat)](https://github.com/neostandard/neostandard) **WARNING: This branch is under development. Not use in production. You can use stable versions or 2.6 branch.** diff --git a/assets/js/ajax.js b/assets/js/ajax.js index 419ea81..89c8342 100644 --- a/assets/js/ajax.js +++ b/assets/js/ajax.js @@ -81,7 +81,7 @@ export function sendRequest (options) { const eventBeginning = new CustomEvent('ec-crud-ajax', { cancelable: true, detail: { - options: options + options } }) document.dispatchEvent(eventBeginning) @@ -152,7 +152,7 @@ export function sendRequest (options) { const eventBeforeSend = new CustomEvent('ec-crud-ajax-before-send', { cancelable: true, detail: { - options: options + options } }) document.dispatchEvent(eventBeforeSend) @@ -282,8 +282,8 @@ export function updateDom (element, updateMode, content) { cancelable: true, detail: { element: originElement, - updateMode: updateMode, - content: content + updateMode, + content } }) element.dispatchEvent(eventBefore) @@ -313,8 +313,8 @@ export function updateDom (element, updateMode, content) { bubbles: true, detail: { element: originElement, - updateMode: updateMode, - content: content + updateMode, + content } }) element.dispatchEvent(eventAfter) @@ -364,8 +364,8 @@ function generateParameters (result, propertyPath, property, value) { function executeEventsAndCallbacksSuccess (callbacksSuccess, options, data, response) { const eventOnSuccess = new CustomEvent('ec-crud-ajax-on-success', { detail: { - data: data, - response: response + data, + response } }) document.dispatchEvent(eventOnSuccess) @@ -374,7 +374,7 @@ function executeEventsAndCallbacksSuccess (callbacksSuccess, options, data, resp const eventOnComplete = new CustomEvent('ec-crud-ajax-on-complete', { detail: { statusText: response.statusText, - response: response + response } }) document.dispatchEvent(eventOnComplete) @@ -384,8 +384,8 @@ function executeEventsAndCallbacksSuccess (callbacksSuccess, options, data, resp function executeEventsAndCallbacksError (options, statusText, response) { const eventOnError = new CustomEvent('ec-crud-ajax-on-error', { detail: { - statusText: statusText, - response: response + statusText, + response } }) document.dispatchEvent(eventOnError) @@ -393,8 +393,8 @@ function executeEventsAndCallbacksError (options, statusText, response) { const eventOnComplete = new CustomEvent('ec-crud-ajax-on-complete', { detail: { - statusText: statusText, - response: response + statusText, + response } }) document.dispatchEvent(eventOnComplete) diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..15225a4 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,24 @@ +import globals from 'globals'; +import neostandard from 'neostandard'; + +export default [ + ...neostandard(), + { + languageOptions: { + ecmaVersion: 2018, + sourceType: 'module', + globals: { + ...globals.browser, + ...globals.node, + ...globals.jasmine, + Atomics: 'readonly', + SharedArrayBuffer: 'readonly', + jasmine: true + } + }, + + rules: { + '@stylistic/linebreak-style': ['error', 'unix'], + } + } +]; diff --git a/package.json b/package.json index 9b9cf13..db4b42e 100644 --- a/package.json +++ b/package.json @@ -3,11 +3,8 @@ "@ecommit/crud-bundle": "link:assets", "@symfony/webpack-encore": "^3.0", "core-js": "^3.0.0", - "eslint": "^8.0", - "eslint-config-standard": "^16.0.3", - "eslint-plugin-import": "^2.24.2", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^6.0", + "eslint": "^9.0", + "globals": "^15.12", "install": "^0.13.0", "jasmine": "^4.0", "jquery": "^3", @@ -17,6 +14,7 @@ "karma-jasmine-ajax": "^0.1.13", "karma-spec-reporter": "^0.0.32", "karma-webpack": "^5.0", + "neostandard": "^0.11.8", "webpack-notifier": "^1.6.0", "whatwg-fetch": "^3.6.2" }, diff --git a/tests/assets/js/ajax.spec.js b/tests/assets/js/ajax.spec.js index 92f15d3..3424357 100644 --- a/tests/assets/js/ajax.spec.js +++ b/tests/assets/js/ajax.spec.js @@ -978,7 +978,7 @@ describe('Test Ajax.sendRequest', function () { priority: -99 }, update: '#ajax-result .content', - updateMode: updateMode + updateMode }) expect(callbackSuccess).toHaveBeenCalledWith(expectedContent) From c6b390bc82d369534a31f7702f5e3d43c54fde9d Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sun, 24 Nov 2024 12:46:58 +0100 Subject: [PATCH 226/264] Upgrade Encore dependencies --- package.json | 10 +++++++--- webpack-encore-config.js | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index db4b42e..10c4129 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,10 @@ { "devDependencies": { + "@babel/core": "^7.17.0", + "@babel/preset-env": "^7.16.0", "@ecommit/crud-bundle": "link:assets", - "@symfony/webpack-encore": "^3.0", - "core-js": "^3.0.0", + "@symfony/webpack-encore": "^5.0", + "core-js": "^3.38.0", "eslint": "^9.0", "globals": "^15.12", "install": "^0.13.0", @@ -15,7 +17,9 @@ "karma-spec-reporter": "^0.0.32", "karma-webpack": "^5.0", "neostandard": "^0.11.8", - "webpack-notifier": "^1.6.0", + "webpack": "^5.74.0", + "webpack-cli": "^5.1.0", + "webpack-notifier": "^1.15.0", "whatwg-fetch": "^3.6.2" }, "private": true diff --git a/webpack-encore-config.js b/webpack-encore-config.js index 3ece1a5..b8467e5 100644 --- a/webpack-encore-config.js +++ b/webpack-encore-config.js @@ -17,7 +17,7 @@ module.exports = function (outputPath) { .enableVersioning(false) .configureBabelPresetEnv((config) => { config.useBuiltIns = 'usage'; - config.corejs = 3; + config.corejs = '3.38'; }) ; From cf4c5e0774028cc514f616cde96406eb39db2173 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sun, 24 Nov 2024 20:43:56 +0100 Subject: [PATCH 227/264] [Ajax] Fix catch --- assets/js/ajax.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/assets/js/ajax.js b/assets/js/ajax.js index 89c8342..88e5e19 100644 --- a/assets/js/ajax.js +++ b/assets/js/ajax.js @@ -200,9 +200,7 @@ export function sendRequest (options) { dataPromise.then(data => { executeEventsAndCallbacksSuccess(callbacksSuccess, options, data, response) resolve(response) - }) - - dataPromise.catch(error => { + }).catch(error => { error = 'Error during fetching response body: ' + error executeEventsAndCallbacksError(options, error, response) reject(error) @@ -216,9 +214,7 @@ export function sendRequest (options) { resolve(response) } } - }) - - fetchPromise.catch(error => { + }).catch(error => { error = 'Error during query execution: ' + error executeEventsAndCallbacksError(options, error, null) reject(error) From 754e4673386b7fd3bba741210428ef4627bf3517 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sun, 24 Nov 2024 20:50:08 +0100 Subject: [PATCH 228/264] Upgrade Jasmine 5 --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 10c4129..70040de 100644 --- a/package.json +++ b/package.json @@ -8,13 +8,13 @@ "eslint": "^9.0", "globals": "^15.12", "install": "^0.13.0", - "jasmine": "^4.0", + "jasmine": "^5.0", "jquery": "^3", "karma": "^6.0", "karma-firefox-launcher": "^2.0", - "karma-jasmine": "^4.0", + "karma-jasmine": "^5.1", "karma-jasmine-ajax": "^0.1.13", - "karma-spec-reporter": "^0.0.32", + "karma-spec-reporter": "^0.0.36", "karma-webpack": "^5.0", "neostandard": "^0.11.8", "webpack": "^5.74.0", From 47fb31490849a70c32eecc8aff5ec16422a50cb0 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sat, 14 Dec 2024 17:10:20 +0100 Subject: [PATCH 229/264] [Tests] Add doctrine/data-fixtures v2 support --- tests/Functional/App/DataFixtures/EntityManyToOneFixtures.php | 2 +- tests/Functional/App/DataFixtures/TestUserFixtures.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Functional/App/DataFixtures/EntityManyToOneFixtures.php b/tests/Functional/App/DataFixtures/EntityManyToOneFixtures.php index acbbea9..19b968c 100644 --- a/tests/Functional/App/DataFixtures/EntityManyToOneFixtures.php +++ b/tests/Functional/App/DataFixtures/EntityManyToOneFixtures.php @@ -40,7 +40,7 @@ public function load(ObjectManager $manager): void $manager->flush(); } - public function getDependencies() + public function getDependencies(): array { return [ TagFixtures::class, diff --git a/tests/Functional/App/DataFixtures/TestUserFixtures.php b/tests/Functional/App/DataFixtures/TestUserFixtures.php index b8a9cac..31920cd 100644 --- a/tests/Functional/App/DataFixtures/TestUserFixtures.php +++ b/tests/Functional/App/DataFixtures/TestUserFixtures.php @@ -47,7 +47,7 @@ public function load(ObjectManager $manager): void } $userCrudSettings = new UserCrudSettings( - $this->getReference('user_EveReste'), + $this->getReference('user_EveReste', TestUser::class), 'crud_persistent_settings', 50, ['username', 'firstName'], From 316734560d6402f8fa5d48d1328a659073754a6a Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sat, 14 Dec 2024 16:56:00 +0100 Subject: [PATCH 230/264] Add PHP 8.4 tests --- .github/workflows/tests.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 80c8b1c..febc874 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -25,19 +25,19 @@ jobs: #Symfony versions - - php-version: '8.3' + - php-version: '8.4' symfony-version: '6.4.*' description: 'with SF 6.4.*' - - php-version: '8.3' + - php-version: '8.4' symfony-version: '6.4.*@dev' description: 'with SF 6.4.* dev' - - php-version: '8.3' + - php-version: '8.4' symfony-version: '7.1.*' description: 'with SF 7.1.*' - - php-version: '8.3' + - php-version: '8.4' symfony-version: '7.1.*@dev' description: 'with SF 7.1.* dev' - - php-version: '8.3' + - php-version: '8.4' symfony-version: '7.2.*@dev' description: 'with SF 7.2.* dev' @@ -45,6 +45,7 @@ jobs: - php-version: '8.1' - php-version: '8.2' - php-version: '8.3' + - php-version: '8.4' #CS - php-version: '8.2' From 00ed4f60178478e99f899172594e60a2abb634b7 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Fri, 27 Dec 2024 19:47:23 +0100 Subject: [PATCH 231/264] Fix Psalm --- src/EventListener/MappingEntities.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/EventListener/MappingEntities.php b/src/EventListener/MappingEntities.php index 8fa0d7a..2a4684c 100644 --- a/src/EventListener/MappingEntities.php +++ b/src/EventListener/MappingEntities.php @@ -51,9 +51,6 @@ protected function mappUserCrudSettings(ClassMetadataInfo|ClassMetadata $userCru $userCrudSettingsMetadata->setAssociationOverride( 'user', [ - 'targetEntity' => $userMetadata->getName(), - 'fieldName' => 'user', - 'id' => true, 'joinColumns' => [[ 'name' => 'user_id', 'referencedColumnName' => $userMetadata->getSingleIdentifierColumnName(), From fa748b907f707fe707fc9064a5fa5a6ee999e5de Mon Sep 17 00:00:00 2001 From: hlecorche Date: Fri, 27 Dec 2024 19:56:12 +0100 Subject: [PATCH 232/264] Add Symfony 7.2 tests --- .github/workflows/tests.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index febc874..a73ca59 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,6 +22,10 @@ jobs: symfony-version: '7.1.*' composer-flags: '--prefer-stable --prefer-lowest' description: 'with SF 7.1.* lowest' + - php-version: '8.2' + symfony-version: '7.2.*' + composer-flags: '--prefer-stable --prefer-lowest' + description: 'with SF 7.2.* lowest' #Symfony versions @@ -37,6 +41,9 @@ jobs: - php-version: '8.4' symfony-version: '7.1.*@dev' description: 'with SF 7.1.* dev' + - php-version: '8.4' + symfony-version: '7.2.*' + description: 'with SF 7.2.*' - php-version: '8.4' symfony-version: '7.2.*@dev' description: 'with SF 7.2.* dev' From fff778647e035d00d10b23127687589b4eda3cc3 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Fri, 27 Dec 2024 21:50:31 +0100 Subject: [PATCH 233/264] [Ajax] Add ec-crud-ajax-form-before and ec-crud-ajax-form-complete events --- assets/js/ajax.js | 34 +++++++++++ tests/assets/js/ajax.spec.js | 113 +++++++++++++++++++++++++++++++++++ 2 files changed, 147 insertions(+) diff --git a/assets/js/ajax.js b/assets/js/ajax.js index 88e5e19..a231b06 100644 --- a/assets/js/ajax.js +++ b/assets/js/ajax.js @@ -252,6 +252,21 @@ export function link (link, options) { } export function sendForm (form, options) { + const eventBefore = new CustomEvent('ec-crud-ajax-form-before', { + bubbles: true, + cancelable: true, + detail: { + form, + options + } + }) + document.dispatchEvent(eventBefore) + if (eventBefore.defaultPrevented) { + return new Promise((resolve, reject) => { + resolve(null) + }) + } + form = optionsResolver.getElement(form) // Options in data-* override options argument // Option argument override action, method and data form @@ -267,6 +282,25 @@ export function sendForm (form, options) { ) ) + const callbacksComplete = [] + callbacksComplete.push({ + priority: 10, + callback: (statusText, response) => { + const eventOnComplete = new CustomEvent('ec-crud-ajax-form-complete', { + detail: { + form, + statusText, + response + } + }) + document.dispatchEvent(eventOnComplete) + } + }) + if (optionsResolver.isNotBlank(options.onComplete)) { + callbacksComplete.push(options.onComplete) + } + options.onComplete = callbacksComplete + return sendRequest(options) } diff --git a/tests/assets/js/ajax.spec.js b/tests/assets/js/ajax.spec.js index 3424357..9676bfd 100644 --- a/tests/assets/js/ajax.spec.js +++ b/tests/assets/js/ajax.spec.js @@ -1364,12 +1364,21 @@ describe('Test Ajax.form', function () { status: 200, responseText: 'CONTENT' }) + + jasmine.Ajax.stubRequest(/error404/).andReturn({ + status: 404, + statusText: 'Not Found', + response: 'Page not found !', + responseText: 'Page not found !' + }) }) afterEach(function () { jasmine.Ajax.uninstall() $('.html-test').remove() callbackManager.clear() + $(document).off('ec-crud-ajax-form-before') + $(document).off('ec-crud-ajax-form-complete') }) it('Send request with form', async function () { @@ -1377,8 +1386,17 @@ describe('Test Ajax.form', function () { $('#formToTest input[name=var1]').val('My value 1') $('#formToTest input[name=var2]').val('My value 2') + const callbackFormBefore = jasmine.createSpy('form_before') + const callbackFormComplete = jasmine.createSpy('form_complete') const callbackSuccess = jasmine.createSpy('success') + $(document).on('ec-crud-ajax-form-before', function (event) { + callbackFormBefore() + }) + $(document).on('ec-crud-ajax-form-complete', function (event) { + callbackFormComplete() + }) + const promise = ajax.sendForm('#formToTest', { onSuccess: function (data, response) { callbackSuccess() @@ -1392,9 +1410,92 @@ describe('Test Ajax.form', function () { expect(jasmine.Ajax.requests.mostRecent().url).toMatch('/goodRequest') expect(jasmine.Ajax.requests.mostRecent().method).toBe('POST') expect(jasmine.Ajax.requests.mostRecent().data()).toEqual([['var1', 'My value 1'], ['var2', 'My value 2']]) // Parsed by addJasmineAjaxFormDataSupport + expect(callbackFormBefore).toHaveBeenCalled() + expect(callbackFormComplete).toHaveBeenCalled() expect(callbackSuccess).toHaveBeenCalled() }) + it('Send request with form canceled', async function () { + $('body').append('
') + $('#formToTest input[name=var1]').val('My value 1') + $('#formToTest input[name=var2]').val('My value 2') + + const callbackFormComplete = jasmine.createSpy('form_complete') + + $(document).on('ec-crud-ajax-form-before', function (event) { + event.preventDefault() + }) + $(document).on('ec-crud-ajax-form-complete', function (event) { + callbackFormComplete() + }) + + const promise = ajax.sendForm('#formToTest') + expect(promise).toBeInstanceOf(Promise) + + await promise + + expect(callbackFormComplete).not.toHaveBeenCalled() + }) + + it('Send request with form and complete callback', async function () { + $('body').append('
') + $('#formToTest input[name=var1]').val('My value 1') + $('#formToTest input[name=var2]').val('My value 2') + + const callbackFormBefore = jasmine.createSpy('form_before') + const callbackFormComplete = jasmine.createSpy('form_complete') + const callbackComplete = jasmine.createSpy('complete') + + $(document).on('ec-crud-ajax-form-before', function (event) { + callbackFormBefore() + }) + $(document).on('ec-crud-ajax-form-complete', function (event) { + callbackFormComplete() + }) + + const promise = ajax.sendForm('#formToTest', { + onComplete: function (statusText, response) { + callbackComplete() + } + }) + expect(promise).toBeInstanceOf(Promise) + + const response = await promise + + expect(response).toBeInstanceOf(Response) + expect(jasmine.Ajax.requests.mostRecent().url).toMatch('/goodRequest') + expect(jasmine.Ajax.requests.mostRecent().method).toBe('POST') + expect(jasmine.Ajax.requests.mostRecent().data()).toEqual([['var1', 'My value 1'], ['var2', 'My value 2']]) // Parsed by addJasmineAjaxFormDataSupport + expect(callbackFormBefore).toHaveBeenCalled() + expect(callbackFormComplete).toHaveBeenCalled() + expect(callbackComplete).toHaveBeenCalled() + }) + + it('Send request with form and error', async function () { + $('body').append('
') + $('#formToTest input[name=var1]').val('My value 1') + $('#formToTest input[name=var2]').val('My value 2') + + const callbackFormComplete = jasmine.createSpy('form_complete') + const callbackSuccess = jasmine.createSpy('success') + + $(document).on('ec-crud-ajax-form-complete', function (event) { + callbackFormComplete() + }) + + const promise = ajax.sendForm('#formToTest', { + onSuccess: function (data, response) { + callbackSuccess() + } + }) + expect(promise).toBeInstanceOf(Promise) + + await promise + + expect(callbackFormComplete).toHaveBeenCalled() + expect(callbackSuccess).not.toHaveBeenCalled() + }) + it('Send request with form with Element', async function () { $('body').append('
') $('#formToTest input[name=var1]').val('My value 1') @@ -1536,6 +1637,16 @@ describe('Test Ajax.form', function () { $('#formToTest input[name=var1]').val('My value 1') $('#formToTest input[name=var2]').val('My value 2') + const callbackFormBefore = jasmine.createSpy('form_before') + const callbackFormComplete = jasmine.createSpy('form_complete') + + $(document).on('ec-crud-ajax-form-before', function (event) { + callbackFormBefore() + }) + $(document).on('ec-crud-ajax-form-complete', function (event) { + callbackFormComplete() + }) + $('#formToTest button[type="submit"]').get(0).click() await wait(() => { @@ -1545,6 +1656,8 @@ describe('Test Ajax.form', function () { expect(jasmine.Ajax.requests.mostRecent().url).toMatch('/goodRequest') expect(jasmine.Ajax.requests.mostRecent().method).toBe('POST') expect(jasmine.Ajax.requests.mostRecent().data()).toEqual([['var1', 'My value 1'], ['var2', 'My value 2']]) // Parsed by addJasmineAjaxFormDataSupport + expect(callbackFormBefore).toHaveBeenCalled() + expect(callbackFormComplete).toHaveBeenCalled() }) it('Send auto-request with form canceled', async function () { From bf701d799b9b1d1768165018bf8375e8a3e04c35 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Mon, 30 Dec 2024 17:23:07 +0100 Subject: [PATCH 234/264] Fix CS and "Check license year" --- .github/workflows/tests.yml | 2 +- src/Twig/CrudExtension.php | 22 ++++++++++------------ tests/Crud/CrudColumnTest.php | 2 +- tests/Crud/CrudTest.php | 2 +- tests/Form/Filter/AbstractFilterTest.php | 6 ++---- tests/Form/Filter/EntityAjaxFilterTest.php | 8 ++------ tests/Form/Filter/EntityFilterTest.php | 8 ++------ tests/Form/Type/EntityAjaxTypeTest.php | 4 +--- 8 files changed, 20 insertions(+), 34 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a73ca59..494fd1a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -154,7 +154,7 @@ jobs: - name: Check license year if: matrix.coding-standards - run: cat LICENSE |grep -E "\(c\) ([0-9]+\-)*`date +%G`" + run: cat LICENSE |grep -E "\(c\) ([0-9]+\-)*`date +%Y`" - name: Run ESLint if: matrix.coding-standards diff --git a/src/Twig/CrudExtension.php b/src/Twig/CrudExtension.php index 4f1483c..ea5c72b 100644 --- a/src/Twig/CrudExtension.php +++ b/src/Twig/CrudExtension.php @@ -581,18 +581,16 @@ public function searchFormReset(Environment $environment, Crud $crud, array $opt ]); $resolver->setAllowedTypes('ajax_options', ['array']); $resolver->setAllowedTypes('button_attr', ['array']); - $resolver->addNormalizer('button_attr', function (Options $options, mixed $value) use ($crud): array { - return array_merge( - [ - 'data-ec-crud-toggle' => 'search-reset', - 'data-crud-search-id' => $crud->getDivIdSearch(), - 'data-crud-list-id' => $crud->getDivIdList(), - 'data-ec-crud-ajax-url' => $crud->getSearchUrl(['reset' => 1]), - ], - $value, - $this->getAjaxAttributes($this->validateAjaxOptions($options['ajax_options'])), - ); - }); + $resolver->addNormalizer('button_attr', fn (Options $options, mixed $value): array => array_merge( + [ + 'data-ec-crud-toggle' => 'search-reset', + 'data-crud-search-id' => $crud->getDivIdSearch(), + 'data-crud-list-id' => $crud->getDivIdList(), + 'data-ec-crud-ajax-url' => $crud->getSearchUrl(['reset' => 1]), + ], + $value, + $this->getAjaxAttributes($this->validateAjaxOptions($options['ajax_options'])), + )); $options = $resolver->resolve($this->buildOptions('crud_search_form_reset', $options, $crud)); if ($options['render']) { diff --git a/tests/Crud/CrudColumnTest.php b/tests/Crud/CrudColumnTest.php index e3ce139..e0ac639 100644 --- a/tests/Crud/CrudColumnTest.php +++ b/tests/Crud/CrudColumnTest.php @@ -32,7 +32,7 @@ public function testMissingIdOption(): void public function testIdTooLongOption(): void { $options = $this->createValidConfig(); - $options['id'] = str_pad('', 101, 'a'); + $options['id'] = mb_str_pad('', 101, 'a'); $this->expectException(ValidationFailedException::class); $this->expectExceptionMessageMatches('/The column id ".+" is too long. It should have 100 character or less/'); diff --git a/tests/Crud/CrudTest.php b/tests/Crud/CrudTest.php index 69fed9d..0eb7352 100644 --- a/tests/Crud/CrudTest.php +++ b/tests/Crud/CrudTest.php @@ -51,7 +51,7 @@ public function getTestCrudWithInvalidSessionNameProvider(): array ['', InvalidOptionsException::class, '/The option "session_name" with value ".*" is invalid/'], ['aa#bb', ValidationFailedException::class, '/Invalid session_name format/'], ['aa bb', ValidationFailedException::class, '/Invalid session_name format/'], - [str_pad('', 101, 'a'), ValidationFailedException::class, '/Invalid session_name format/'], + [mb_str_pad('', 101, 'a'), ValidationFailedException::class, '/Invalid session_name format/'], [1, InvalidOptionsException::class, '/The option "session_name" with value 1 is expected to be of type "string", but is of type "int"/'], ]; } diff --git a/tests/Form/Filter/AbstractFilterTest.php b/tests/Form/Filter/AbstractFilterTest.php index f35b1e3..26c4908 100644 --- a/tests/Form/Filter/AbstractFilterTest.php +++ b/tests/Form/Filter/AbstractFilterTest.php @@ -82,12 +82,10 @@ protected function setUp(): void $this->crudFactory = $this->getMockBuilder(CrudFactory::class) ->disableOriginalConstructor() ->getMock(); - $this->crudFactory->method('create')->willReturnCallback(function (array $options) use ($container) { - return $this->getMockBuilder(Crud::class) + $this->crudFactory->method('create')->willReturnCallback(fn (array $options) => $this->getMockBuilder(Crud::class) ->setConstructorArgs([$options, $container]) ->onlyMethods(['save', 'buildSearchForm']) // buildSearchForm: Disable search form creation - ->getMock(); - }); + ->getMock()); } protected function tearDown(): void diff --git a/tests/Form/Filter/EntityAjaxFilterTest.php b/tests/Form/Filter/EntityAjaxFilterTest.php index 2a4eb54..c1a5c37 100644 --- a/tests/Form/Filter/EntityAjaxFilterTest.php +++ b/tests/Form/Filter/EntityAjaxFilterTest.php @@ -154,11 +154,9 @@ public function getTestSubmitInvalidProvider(): array public function testViewWithQueryBuilder(bool $queryBuilderIsClosure, bool $multiple, $modelData, $expectedViewData, array $expectedIdsFound): void { if ($queryBuilderIsClosure) { - $queryBuilder = function (EntityRepository $entityRepository) { - return $entityRepository->createQueryBuilder('t') + $queryBuilder = fn (EntityRepository $entityRepository) => $entityRepository->createQueryBuilder('t') ->select('t') ->andWhere('t.id > 2'); - }; } else { $queryBuilder = $this->em->getRepository(Tag::class)->createQueryBuilder('t') ->select('t') @@ -213,11 +211,9 @@ public function getTestViewWithQueryBuilderProvider(): array public function testSubmitWithQueryBuilder(bool $queryBuilderIsClosure, bool $multiple, $submittedData, $expectedValid, $expectedModelData, $expectedViewData): void { if ($queryBuilderIsClosure) { - $queryBuilder = function (EntityRepository $entityRepository) { - return $entityRepository->createQueryBuilder('t') + $queryBuilder = fn (EntityRepository $entityRepository) => $entityRepository->createQueryBuilder('t') ->select('t') ->andWhere('t.id > 2'); - }; } else { $queryBuilder = $this->em->getRepository(Tag::class)->createQueryBuilder('t') ->select('t') diff --git a/tests/Form/Filter/EntityFilterTest.php b/tests/Form/Filter/EntityFilterTest.php index 2df7ab3..6f1bf01 100644 --- a/tests/Form/Filter/EntityFilterTest.php +++ b/tests/Form/Filter/EntityFilterTest.php @@ -165,11 +165,9 @@ public function testSubmitInvalidMaxCount(): void public function testViewWithQueryBuilder(bool $queryBuilderIsClosure, bool $multiple, $modelData, $expectedViewData, array $expectedIdsFound): void { if ($queryBuilderIsClosure) { - $queryBuilder = function (EntityRepository $entityRepository) { - return $entityRepository->createQueryBuilder('t') + $queryBuilder = fn (EntityRepository $entityRepository) => $entityRepository->createQueryBuilder('t') ->select('t') ->andWhere('t.id > 2'); - }; } else { $queryBuilder = $this->em->getRepository(Tag::class)->createQueryBuilder('t') ->select('t') @@ -223,11 +221,9 @@ public function getTestViewWithQueryBuilderProvider(): array public function testSubmitWithQueryBuilder(bool $queryBuilderIsClosure, bool $multiple, $submittedData, $expectedValid, $expectedModelData, $expectedViewData): void { if ($queryBuilderIsClosure) { - $queryBuilder = function (EntityRepository $entityRepository) { - return $entityRepository->createQueryBuilder('t') + $queryBuilder = fn (EntityRepository $entityRepository) => $entityRepository->createQueryBuilder('t') ->select('t') ->andWhere('t.id > 2'); - }; } else { $queryBuilder = $this->em->getRepository(Tag::class)->createQueryBuilder('t') ->select('t') diff --git a/tests/Form/Type/EntityAjaxTypeTest.php b/tests/Form/Type/EntityAjaxTypeTest.php index 5bf4aab..39e686e 100644 --- a/tests/Form/Type/EntityAjaxTypeTest.php +++ b/tests/Form/Type/EntityAjaxTypeTest.php @@ -174,11 +174,9 @@ public function getTestSubmitInvalidProvider(): array public function testSubmitWithQueryBuilder(bool $queryBuilderIsClosure, bool $multiple, $submittedData, $expectedValid, $expectedModelData, $expectedDataBuilderName, $expectedViewData): void { if ($queryBuilderIsClosure) { - $queryBuilder = function (EntityRepository $entityRepository) { - return $entityRepository->createQueryBuilder('t') + $queryBuilder = fn (EntityRepository $entityRepository) => $entityRepository->createQueryBuilder('t') ->select('t') ->andWhere('t.id > 2'); - }; } else { $queryBuilder = $this->em->getRepository(Tag::class)->createQueryBuilder('t') ->select('t') From 9e79e55cec72cc54fde421a18f529ed4c81b8314 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Wed, 1 Jan 2025 15:26:30 +0100 Subject: [PATCH 235/264] Update license year --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 890295d..eefc686 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2011-2024 E-COMMIT +Copyright (c) 2011-2025 E-COMMIT Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 82b16996ac72e6f5f3d1683b9438e30c097f1a4d Mon Sep 17 00:00:00 2001 From: hlecorche Date: Wed, 5 Mar 2025 21:34:17 +0100 Subject: [PATCH 236/264] Upgrade actions/cache@v3 --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 494fd1a..d190b14 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -103,7 +103,7 @@ jobs: run: echo "::set-output name=dir::$(yarn cache dir)" - name: Cache dependencies - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: | ${{ steps.composer-cache.outputs.dir }} From 56338616e27158c9f63fc7285bbf970e022c8196 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Sat, 15 Mar 2025 14:10:56 +0100 Subject: [PATCH 237/264] Add Symfony 7.3.*@dev tests --- .github/workflows/tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d190b14..7fbe54a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -38,15 +38,15 @@ jobs: - php-version: '8.4' symfony-version: '7.1.*' description: 'with SF 7.1.*' - - php-version: '8.4' - symfony-version: '7.1.*@dev' - description: 'with SF 7.1.* dev' - php-version: '8.4' symfony-version: '7.2.*' description: 'with SF 7.2.*' - php-version: '8.4' symfony-version: '7.2.*@dev' description: 'with SF 7.2.* dev' + - php-version: '8.4' + symfony-version: '7.3.*@dev' + description: 'with SF 7.3.* dev' #PHP versions - php-version: '8.1' From 104ba71abb10f1b848ac8100e2121c61bc08a519 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Mon, 9 Jun 2025 20:30:07 +0200 Subject: [PATCH 238/264] Add Symfony 7.3 and 7.4.*@dev tests --- .github/workflows/tests.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7fbe54a..7ee19e4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -26,6 +26,10 @@ jobs: symfony-version: '7.2.*' composer-flags: '--prefer-stable --prefer-lowest' description: 'with SF 7.2.* lowest' + - php-version: '8.2' + symfony-version: '7.3.*' + composer-flags: '--prefer-stable --prefer-lowest' + description: 'with SF 7.3.* lowest' #Symfony versions @@ -44,9 +48,15 @@ jobs: - php-version: '8.4' symfony-version: '7.2.*@dev' description: 'with SF 7.2.* dev' + - php-version: '8.4' + symfony-version: '7.3.*' + description: 'with SF 7.3.*' - php-version: '8.4' symfony-version: '7.3.*@dev' description: 'with SF 7.3.* dev' + - php-version: '8.4' + symfony-version: '7.4.*@dev' + description: 'with SF 7.4.* dev' #PHP versions - php-version: '8.1' From 7ddc95d6deb98a2e832e6dc783628fcd6ed759ca Mon Sep 17 00:00:00 2001 From: hlecorche Date: Wed, 15 Oct 2025 12:26:23 +0200 Subject: [PATCH 239/264] Fix modal test engine close event --- tests/assets/js/modal/engine/test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/assets/js/modal/engine/test.js b/tests/assets/js/modal/engine/test.js index 08024da..53f3120 100644 --- a/tests/assets/js/modal/engine/test.js +++ b/tests/assets/js/modal/engine/test.js @@ -13,12 +13,12 @@ import $ from 'jquery' export function openModal (options) { runCallback(options.onOpen, $(options.element)) - $(document).on('DOMNodeRemoved', options.element + ' .content', function (event) { + $(document).one('testEngineClose', options.element, function () { runCallback(options.onClose, $(options.element)) }) } export function closeModal (element) { $(element + ' .content').remove() - $(document).off('remove', element + ' .content') + $(document).find(element).trigger('testEngineClose') } From 7a68325c08f4ed88fac3a0dc8aa637db82074515 Mon Sep 17 00:00:00 2001 From: hlecorche Date: Wed, 15 Oct 2025 13:44:10 +0200 Subject: [PATCH 240/264] [EntityAjaxType] Not clear list if not valid but synchronized --- src/Form/Type/EntityAjaxType.php | 1 + templates/Form/entity_ajax.html.twig | 4 ++-- tests/Form/Type/EntityAjaxTypeTest.php | 18 ++++++++++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/Form/Type/EntityAjaxType.php b/src/Form/Type/EntityAjaxType.php index 3fe7ec8..b1d9af5 100644 --- a/src/Form/Type/EntityAjaxType.php +++ b/src/Form/Type/EntityAjaxType.php @@ -63,6 +63,7 @@ public function buildView(FormView $view, FormInterface $form, array $options): { $view->vars['url'] = $this->router->generate($options['route_name'], $options['route_parameters']); $view->vars['multiple'] = $options['multiple']; + $view->vars['list_is_synchronized'] = $form->isSynchronized(); if ($options['multiple']) { $view->vars['full_name'] .= '[]'; diff --git a/templates/Form/entity_ajax.html.twig b/templates/Form/entity_ajax.html.twig index 090f471..3d88da4 100644 --- a/templates/Form/entity_ajax.html.twig +++ b/templates/Form/entity_ajax.html.twig @@ -10,8 +10,8 @@ {% endif %}