diff --git a/composer.json b/composer.json index 6e9d855e..52e3969c 100644 --- a/composer.json +++ b/composer.json @@ -10,6 +10,7 @@ "ext-intl": "*", "ext-json": "*", "ext-mbstring": "*", + "beberlei/doctrineextensions": "^1.2", "doctrine/annotations": "^1.6", "doctrine/doctrine-bundle": "^2.0", "florianv/swap": "^4.0", @@ -20,8 +21,8 @@ "nyholm/psr7": "^1.1", "ocramius/proxy-manager": "2.2.*", "omines/datatables-bundle": "^0.4.0", - "r/u2f-two-factor-bundle": "dev-u2f-api", "php-translation/symfony-bundle": "^0.12.0", + "r/u2f-two-factor-bundle": "dev-u2f-api", "s9e/text-formatter": "^2.1", "scheb/two-factor-bundle": "^4.11", "sensio/framework-extra-bundle": "^5.1", diff --git a/composer.lock b/composer.lock index 47055f4f..85769d98 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ef069d33ff96f4e5e05b11f1d2189dfc", + "content-hash": "6ebc8e9705e901be6f00f9f6418cf900", "packages": [ { "name": "beberlei/assert", @@ -68,6 +68,60 @@ ], "time": "2019-12-19T17:51:41+00:00" }, + { + "name": "beberlei/doctrineextensions", + "version": "v1.2.6", + "source": { + "type": "git", + "url": "https://github.com/beberlei/DoctrineExtensions.git", + "reference": "af72c4a136b744f1268ca8bb4da47a2f8af78f86" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/beberlei/DoctrineExtensions/zipball/af72c4a136b744f1268ca8bb4da47a2f8af78f86", + "reference": "af72c4a136b744f1268ca8bb4da47a2f8af78f86", + "shasum": "" + }, + "require": { + "doctrine/orm": "^2.6", + "php": "^7.1" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.14", + "nesbot/carbon": "*", + "phpunit/phpunit": "^7.0 || ^8.0", + "symfony/yaml": "^4.2", + "zf1/zend-date": "^1.12", + "zf1/zend-registry": "^1.12" + }, + "type": "library", + "autoload": { + "psr-4": { + "DoctrineExtensions\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Steve Lacey", + "email": "steve@stevelacey.net" + } + ], + "description": "A set of extensions to Doctrine 2 that add support for additional query functions available in MySQL and Oracle.", + "keywords": [ + "database", + "doctrine", + "orm" + ], + "time": "2019-12-05T09:49:04+00:00" + }, { "name": "doctrine/annotations", "version": "v1.8.0", diff --git a/config/packages/doctrine.yaml b/config/packages/doctrine.yaml index 7772d817..6dac8373 100644 --- a/config/packages/doctrine.yaml +++ b/config/packages/doctrine.yaml @@ -26,3 +26,7 @@ doctrine: dir: '%kernel.project_dir%/src/Entity' prefix: 'App\Entity' alias: App + + dql: + string_functions: + regexp: DoctrineExtensions\Query\Mysql\Regexp \ No newline at end of file diff --git a/src/Controller/PartListsController.php b/src/Controller/PartListsController.php index ef03472b..232a7784 100644 --- a/src/Controller/PartListsController.php +++ b/src/Controller/PartListsController.php @@ -204,8 +204,24 @@ class PartListsController extends AbstractController public function showSearch(Request $request, DataTableFactory $dataTable) { $search = $request->query->get('keyword', ''); + $search_options = [ + 'name' => $request->query->getBoolean('name'), + 'description' => $request->query->getBoolean('description'), + 'comment' => $request->query->getBoolean('comment'), + 'category' => $request->query->getBoolean('category'), + 'store_location' => $request->query->getBoolean('storelocation'), + 'supplier' => $request->query->getBoolean('supplier'), + 'ordernr' => $request->query->getBoolean('ordernr'), + 'manufacturer' => $request->query->getBoolean('manufacturer'), + 'footprint' => $request->query->getBoolean('footprint'), + 'tags' => $request->query->getBoolean('tags'), + 'regex' => $request->query->getBoolean('regex'), + ]; - $table = $dataTable->createFromType(PartsDataTable::class, ['search' => $search]) + + $table = $dataTable->createFromType(PartsDataTable::class, [ + 'search' => $search, 'search_options' => $search_options + ]) ->handleRequest($request); if ($table->isCallback()) { diff --git a/src/DataTables/PartsDataTable.php b/src/DataTables/PartsDataTable.php index 5794aefe..01751d2f 100644 --- a/src/DataTables/PartsDataTable.php +++ b/src/DataTables/PartsDataTable.php @@ -66,6 +66,7 @@ use Omines\DataTablesBundle\Column\MapColumn; use Omines\DataTablesBundle\Column\TextColumn; use Omines\DataTablesBundle\DataTable; use Omines\DataTablesBundle\DataTableTypeInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Contracts\Translation\TranslatorInterface; final class PartsDataTable implements DataTableTypeInterface @@ -81,8 +82,8 @@ final class PartsDataTable implements DataTableTypeInterface private $urlGenerator; public function __construct(EntityURLGenerator $urlGenerator, TranslatorInterface $translator, - NodesListBuilder $treeBuilder, AmountFormatter $amountFormatter, - PartPreviewGenerator $previewGenerator, AttachmentURLGenerator $attachmentURLGenerator) + NodesListBuilder $treeBuilder, AmountFormatter $amountFormatter, + PartPreviewGenerator $previewGenerator, AttachmentURLGenerator $attachmentURLGenerator) { $this->urlGenerator = $urlGenerator; $this->translator = $translator; @@ -92,8 +93,59 @@ final class PartsDataTable implements DataTableTypeInterface $this->attachmentURLGenerator = $attachmentURLGenerator; } + public function configureOptions(OptionsResolver $optionsResolver) + { + $optionsResolver->setDefaults([ + 'category' => null, + 'footprint' => null, + 'manufacturer' => null, + 'storelocation' => null, + 'supplier' => null, + 'tag' => null, + 'search' => null, + ]); + + $optionsResolver->setAllowedTypes('category', ['null', Category::class]); + $optionsResolver->setAllowedTypes('footprint', ['null', Footprint::class]); + $optionsResolver->setAllowedTypes('manufacturer', ['null', Manufacturer::class]); + $optionsResolver->setAllowedTypes('supplier', ['null', Supplier::class]); + $optionsResolver->setAllowedTypes('tag', ['null', 'string']); + $optionsResolver->setAllowedTypes('search', ['null', 'string']); + + //Configure search options + $optionsResolver->setDefault('search_options', function (OptionsResolver $resolver) { + $resolver->setDefaults([ + 'name' => true, + 'category' => true, + 'description' => true, + 'store_location' => true, + 'comment' => true, + 'ordernr' => true, + 'supplier' => false, + 'manufacturer' => false, + 'footprint' => false, + 'tags' => false, + 'regex' => false, + ]); + $resolver->setAllowedTypes('name', 'bool'); + $resolver->setAllowedTypes('category', 'bool'); + $resolver->setAllowedTypes('description', 'bool'); + $resolver->setAllowedTypes('store_location', 'bool'); + $resolver->setAllowedTypes('comment', 'bool'); + $resolver->setAllowedTypes('supplier', 'bool'); + $resolver->setAllowedTypes('manufacturer', 'bool'); + $resolver->setAllowedTypes('footprint', 'bool'); + $resolver->setAllowedTypes('tags', 'bool'); + $resolver->setAllowedTypes('regex', 'bool'); + }); + } + public function configure(DataTable $dataTable, array $options): void { + $resolver = new OptionsResolver(); + $this->configureOptions($resolver); + $options = $resolver->resolve($options); + $dataTable ->add('picture', TextColumn::class, [ 'label' => '', @@ -270,6 +322,7 @@ final class PartsDataTable implements DataTableTypeInterface ->leftJoin('footprint.master_picture_attachment', 'footprint_attachment') ->leftJoin('part.manufacturer', 'manufacturer') ->leftJoin('part.orderdetails', 'orderdetails') + ->leftJoin('orderdetails.supplier', 'suppliers') ->leftJoin('part.attachments', 'attachments') ->leftJoin('part.partUnit', 'partUnit'); } @@ -322,9 +375,96 @@ final class PartsDataTable implements DataTableTypeInterface $builder->andWhere('part.tags LIKE :tag')->setParameter('tag', '%'.$options['tag'].'%'); } - if (isset($options['search'])) { - $builder->AndWhere('part.name LIKE :search')->orWhere('part.description LIKE :search')->orWhere('part.comment LIKE :search') - ->setParameter('search', '%'.$options['search'].'%'); + if (!empty($options['search'])) { + if (!$options['search_options']['regex']) { + //Dont show results, if no things are selected + $builder->andWhere('0=1'); + $defined = false; + if ($options['search_options']['name']) { + $builder->orWhere('part.name LIKE :search'); + $defined = true; + } + if ($options['search_options']['description']) { + $builder->orWhere('part.description LIKE :search'); + $defined = true; + } + if ($options['search_options']['comment']) { + $builder->orWhere('part.comment LIKE :search'); + $defined = true; + } + if ($options['search_options']['category']) { + $builder->orWhere('category.name LIKE :search'); + $defined = true; + } + if ($options['search_options']['manufacturer']) { + $builder->orWhere('manufacturer.name LIKE :search'); + $defined = true; + } + if ($options['search_options']['footprint']) { + $builder->orWhere('footprint.name LIKE :search'); + $defined = true; + } + if ($options['search_options']['tags']) { + $builder->orWhere('part.tags LIKE :search'); + $defined = true; + } + if ($options['search_options']['store_location']) { + $builder->orWhere('storelocations.name LIKE :search'); + $defined = true; + } + if ($options['search_options']['supplier']) { + $builder->orWhere('suppliers.name LIKE :search'); + $defined = true; + } + + if ($defined) { + $builder->setParameter('search', '%'.$options['search'].'%'); + } + } else { //Use REGEX + $builder->andWhere('0=1'); + $defined = false; + if ($options['search_options']['name']) { + $builder->orWhere('REGEXP(part.name, :search) = 1'); + $defined = true; + } + if ($options['search_options']['description']) { + $builder->orWhere('REGEXP(part.description, :search) = 1'); + $defined = true; + } + if ($options['search_options']['comment']) { + $builder->orWhere('REGEXP(part.comment, :search) = 1'); + $defined = true; + } + if ($options['search_options']['category']) { + $builder->orWhere('REGEXP(category.name, :search) = 1'); + $defined = true; + } + if ($options['search_options']['manufacturer']) { + $builder->orWhere('REGEXP(manufacturer.name, :search) = 1'); + $defined = true; + } + if ($options['search_options']['footprint']) { + $builder->orWhere('REGEXP(footprint.name, :search) = 1'); + $defined = true; + } + if ($options['search_options']['tags']) { + $builder->orWhere('REGEXP(part.tags, :search) = 1'); + $defined = true; + } + if ($options['search_options']['store_location']) { + $builder->orWhere('REGEXP(storelocations.name, :search) = 1'); + $defined = true; + } + if ($options['search_options']['supplier']) { + $builder->orWhere('REGEXP(suppliers.name, :search) = 1'); + $defined = true; + } + + if ($defined) { + $builder->setParameter('search', $options['search']); + } + + } } } } diff --git a/symfony.lock b/symfony.lock index 812896ed..c1cc58e0 100644 --- a/symfony.lock +++ b/symfony.lock @@ -8,6 +8,9 @@ "beberlei/assert": { "version": "v3.2.6" }, + "beberlei/doctrineextensions": { + "version": "v1.2.6" + }, "composer/semver": { "version": "1.5.0" }, diff --git a/templates/_navbar_search.html.twig b/templates/_navbar_search.html.twig index 1680bc06..777ea14b 100644 --- a/templates/_navbar_search.html.twig +++ b/templates/_navbar_search.html.twig @@ -4,56 +4,62 @@ {% trans %}search.options.label{% endtrans %} -