diff --git a/src/Doctrine/Orm/Filter/PartialSearchFilter.php b/src/Doctrine/Orm/Filter/PartialSearchFilter.php index 90cde75c3f..0df3ff318a 100644 --- a/src/Doctrine/Orm/Filter/PartialSearchFilter.php +++ b/src/Doctrine/Orm/Filter/PartialSearchFilter.php @@ -38,12 +38,10 @@ public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $q $values = $parameter->getValue(); if (!is_iterable($values)) { - $queryBuilder->setParameter($parameterName, '%'.strtolower($values).'%'); + $queryBuilder->setParameter($parameterName, $this->formatLikeValue(strtolower($values))); - $queryBuilder->{$context['whereClause'] ?? 'andWhere'}($queryBuilder->expr()->like( - 'LOWER('.$field.')', - ':'.$parameterName - )); + $likeExpression = 'LOWER('.$field.') LIKE :'.$parameterName.' ESCAPE \'\\\''; + $queryBuilder->{$context['whereClause'] ?? 'andWhere'}($likeExpression); return; } @@ -51,15 +49,17 @@ public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $q $likeExpressions = []; foreach ($values as $val) { $parameterName = $queryNameGenerator->generateParameterName($property); - $likeExpressions[] = $queryBuilder->expr()->like( - 'LOWER('.$field.')', - ':'.$parameterName - ); - $queryBuilder->setParameter($parameterName, '%'.strtolower($val).'%'); + $likeExpressions[] = 'LOWER('.$field.') LIKE :'.$parameterName.' ESCAPE \'\\\''; + $queryBuilder->setParameter($parameterName, $this->formatLikeValue(strtolower($val))); } $queryBuilder->{$context['whereClause'] ?? 'andWhere'}( $queryBuilder->expr()->orX(...$likeExpressions) ); } + + private function formatLikeValue(string $value): string + { + return '%'.addcslashes($value, '\\%_').'%'; + } } diff --git a/tests/Functional/Parameters/PartialSearchFilterTest.php b/tests/Functional/Parameters/PartialSearchFilterTest.php index 72f6cc744d..4dd45a6412 100644 --- a/tests/Functional/Parameters/PartialSearchFilterTest.php +++ b/tests/Functional/Parameters/PartialSearchFilterTest.php @@ -115,6 +115,30 @@ public static function partialSearchFilterProvider(): \Generator 0, [], ]; + + yield 'filter by partial name "%"' => [ + '/chickens?namePartial=%25', + 1, + ['xx_%_\\_%_xx'], + ]; + + yield 'filter by partial name "_"' => [ + '/chickens?namePartial=%5F', + 1, + ['xx_%_\\_%_xx'], + ]; + + yield 'filter by partial name "\"' => [ + '/chickens?namePartial=%5C', + 1, + ['xx_%_\\_%_xx'], + ]; + + yield 'filter by partial name "\_"' => [ + '/chickens?namePartial=%5C%5F', + 1, + ['xx_%_\\_%_xx'], + ]; } /** @@ -139,13 +163,19 @@ private function loadFixtures(): void $chicken2->setName('Henriette'); $chicken2->setChickenCoop($chickenCoop2); + $chicken3 = new $chickenClass(); + $chicken3->setName('xx_%_\\_%_xx'); + $chicken3->setChickenCoop($chickenCoop1); + $chickenCoop1->addChicken($chicken1); + $chickenCoop1->addChicken($chicken3); $chickenCoop2->addChicken($chicken2); $manager->persist($chickenCoop1); $manager->persist($chickenCoop2); $manager->persist($chicken1); $manager->persist($chicken2); + $manager->persist($chicken3); $manager->flush(); }