diff --git a/CHANGELOG.md b/CHANGELOG.md
index 903d17dd8..947649b59 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,8 +11,28 @@ and this project adheres to
## Unreleased
+
+* [task] Select2 field in task form to allow search for a user (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/167)
+* remove "search by phone configuration option": search by phone is now executed by default
+* remplacer le classement par ordre alphabétique par un classement par ordre de pertinence, qui tient compte:
+ * de la présence d'une string avec le nom de la ville;
+ * de la similarité;
+ * du fait que la recherche commence par une partie du mot recherché
+* ajouter la recherche par numéro de téléphone directement dans la barre de recherche et dans le formulaire recherche avancée;
+* ajouter la recherche par date de naissance directement dans la barre de recherche;
+* ajouter la recherche par ville dans la recherche avancée
+* ajouter un lien vers le ménage dans les résultats de recherche
+* ajouter l'id du parcours dans les résultats de recherche
+* ajouter le demandeur dans les résultats de recherche
+* ajout d'un bouton "recherche avancée" sur la page d'accueil
* [person] create an accompanying course: add client-side validation if no origin (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/210)
* [person] fix bounds for computing current person address: the new address appears immediatly
+
+
+## Test releases
+
+### Test release 2021-11-15
+
* [main] fix adding multiple AddresseDeRelais (combine PickAddressType with ChillCollection)
* [person]: do not suggest the current household of the person (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/51)
* [person]: display other phone numbers in view + add message in case no others phone numbers (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/184)
@@ -26,11 +46,6 @@ and this project adheres to
* [person suggest] In widget "add person", improve the pertinence of persons when one of the names starts with the pattern;
* [person] do not ask for center any more on person creation
* [3party] do not ask for center any more on 3party creation
-* [task] Select2 field in task form to allow search for a user (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/167)
-* [accompanying course] Add associated persons in banner details. social-issues and associated-persons are slides in same space.
-
-
-## Test releases
### Test release 2021-11-08
diff --git a/phpstan-types.neon b/phpstan-types.neon
index f10574600..dca84e17f 100644
--- a/phpstan-types.neon
+++ b/phpstan-types.neon
@@ -705,11 +705,6 @@ parameters:
count: 1
path: src/Bundle/ChillPersonBundle/Search/SearchPersonApiProvider.php
- -
- message: "#^Method Chill\\\\PersonBundle\\\\Search\\\\SimilarityPersonSearch\\:\\:renderResult\\(\\) should return string but return statement is missing\\.$#"
- count: 1
- path: src/Bundle/ChillPersonBundle/Search/SimilarityPersonSearch.php
-
-
message: "#^Call to function in_array\\(\\) requires parameter \\#3 to be set\\.$#"
count: 1
diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/showInNotification.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/showInNotification.html.twig
index 5128e9a64..79badfe26 100644
--- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/showInNotification.html.twig
+++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/showInNotification.html.twig
@@ -1,4 +1,2 @@
-{{ dump(notification) }}
-
Go to Activity
diff --git a/src/Bundle/ChillActivityBundle/migrations/Version20211119173555.php b/src/Bundle/ChillActivityBundle/migrations/Version20211119173555.php
new file mode 100644
index 000000000..d39613455
--- /dev/null
+++ b/src/Bundle/ChillActivityBundle/migrations/Version20211119173555.php
@@ -0,0 +1,33 @@
+addSql("COMMENT ON COLUMN $col IS NULL");
+ }
+ }
+
+ public function down(Schema $schema): void
+ {
+ $this->throwIrreversibleMigrationException();
+ }
+}
diff --git a/src/Bundle/ChillCalendarBundle/migrations/Version20211119173557.php b/src/Bundle/ChillCalendarBundle/migrations/Version20211119173557.php
new file mode 100644
index 000000000..002bfd1ae
--- /dev/null
+++ b/src/Bundle/ChillCalendarBundle/migrations/Version20211119173557.php
@@ -0,0 +1,33 @@
+addSql("COMMENT ON COLUMN $col IS NULL");
+ }
+ }
+
+ public function down(Schema $schema): void
+ {
+ $this->throwIrreversibleMigrationException();
+ }
+}
diff --git a/src/Bundle/ChillDocGeneratorBundle/migrations/Version20211119173556.php b/src/Bundle/ChillDocGeneratorBundle/migrations/Version20211119173556.php
new file mode 100644
index 000000000..947628ef8
--- /dev/null
+++ b/src/Bundle/ChillDocGeneratorBundle/migrations/Version20211119173556.php
@@ -0,0 +1,32 @@
+addSql("COMMENT ON COLUMN $col IS NULL");
+ }
+ }
+
+ public function down(Schema $schema): void
+ {
+ $this->throwIrreversibleMigrationException();
+ }
+}
diff --git a/src/Bundle/ChillDocStoreBundle/migrations/Version20211119173558.php b/src/Bundle/ChillDocStoreBundle/migrations/Version20211119173558.php
new file mode 100644
index 000000000..a66ddd52e
--- /dev/null
+++ b/src/Bundle/ChillDocStoreBundle/migrations/Version20211119173558.php
@@ -0,0 +1,34 @@
+addSql("COMMENT ON COLUMN $col IS NULL");
+ }
+ }
+
+ public function down(Schema $schema): void
+ {
+ $this->throwIrreversibleMigrationException();
+ }
+}
diff --git a/src/Bundle/ChillMainBundle/ChillMainBundle.php b/src/Bundle/ChillMainBundle/ChillMainBundle.php
index f9e79bc8b..c6c42d36b 100644
--- a/src/Bundle/ChillMainBundle/ChillMainBundle.php
+++ b/src/Bundle/ChillMainBundle/ChillMainBundle.php
@@ -3,6 +3,7 @@
namespace Chill\MainBundle;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
+use Chill\MainBundle\Search\SearchApiInterface;
use Chill\MainBundle\Search\SearchInterface;
use Chill\MainBundle\Security\Authorization\ChillVoterInterface;
use Chill\MainBundle\Security\ProvideRoleInterface;
@@ -41,6 +42,8 @@ class ChillMainBundle extends Bundle
->addTag('chill_main.scope_resolver');
$container->registerForAutoconfiguration(ChillEntityRenderInterface::class)
->addTag('chill.render_entity');
+ $container->registerForAutoconfiguration(SearchApiInterface::class)
+ ->addTag('chill.search_api_provider');
$container->addCompilerPass(new SearchableServicesCompilerPass());
$container->addCompilerPass(new ConfigConsistencyCompilerPass());
diff --git a/src/Bundle/ChillMainBundle/Resources/views/layout.html.twig b/src/Bundle/ChillMainBundle/Resources/views/layout.html.twig
index 1e78dbbe8..07b7970d8 100644
--- a/src/Bundle/ChillMainBundle/Resources/views/layout.html.twig
+++ b/src/Bundle/ChillMainBundle/Resources/views/layout.html.twig
@@ -5,7 +5,7 @@
-
{{ installation.name }} - {% block title %}{% endblock %}
+ {{ installation.name }} - {% block title %}{{ 'Homepage'|trans }}{% endblock %}
{{ encore_entry_link_tags('mod_bootstrap') }}
@@ -68,6 +68,9 @@
+
+ {{ 'Advanced search'|trans }}
+
diff --git a/src/Bundle/ChillMainBundle/Search/AbstractSearch.php b/src/Bundle/ChillMainBundle/Search/AbstractSearch.php
index 942819a39..1f32d3d16 100644
--- a/src/Bundle/ChillMainBundle/Search/AbstractSearch.php
+++ b/src/Bundle/ChillMainBundle/Search/AbstractSearch.php
@@ -26,10 +26,10 @@ use Chill\MainBundle\Search\ParsingException;
/**
* This class implements abstract search with most common responses.
- *
+ *
* you should use this abstract class instead of SearchInterface : if the signature of
* search interface change, the generic method will be implemented here.
- *
+ *
* @author Julien Fastré
*
*/
@@ -37,7 +37,7 @@ abstract class AbstractSearch implements SearchInterface
{
/**
* parse string expected to be a date and transform to a DateTime object
- *
+ *
* @param type $string
* @return \DateTime
* @throws ParsingException if the date is not parseable
@@ -51,14 +51,14 @@ abstract class AbstractSearch implements SearchInterface
. 'not parsable', 0, $ex);
throw $exception;
}
-
+
}
-
+
/**
* recompose a pattern, retaining only supported terms
- *
+ *
* the outputted string should be used to show users their search
- *
+ *
* @param array $terms
* @param array $supportedTerms
* @param string $domain if your domain is NULL, you should set NULL. You should set used domain instead
@@ -67,35 +67,35 @@ abstract class AbstractSearch implements SearchInterface
protected function recomposePattern(array $terms, array $supportedTerms, $domain = NULL)
{
$recomposed = '';
-
+
if ($domain !== NULL)
{
$recomposed .= '@'.$domain.' ';
}
-
+
foreach ($supportedTerms as $term) {
if (array_key_exists($term, $terms) && $term !== '_default') {
$recomposed .= ' '.$term.':';
$containsSpace = \strpos($terms[$term], " ") !== false;
if ($containsSpace) {
- $recomposed .= "(";
+ $recomposed .= '"';
}
$recomposed .= (mb_stristr(' ', $terms[$term]) === FALSE) ? $terms[$term] : '('.$terms[$term].')';
if ($containsSpace) {
- $recomposed .= ")";
+ $recomposed .= '"';
}
}
}
-
+
if ($terms['_default'] !== '') {
$recomposed .= ' '.$terms['_default'];
}
-
+
//strip first character if empty
if (mb_strcut($recomposed, 0, 1) === ' '){
$recomposed = mb_strcut($recomposed, 1);
}
-
+
return $recomposed;
}
-}
\ No newline at end of file
+}
diff --git a/src/Bundle/ChillMainBundle/Search/SearchApi.php b/src/Bundle/ChillMainBundle/Search/SearchApi.php
index 30ecb8d3d..c3bfc8d03 100644
--- a/src/Bundle/ChillMainBundle/Search/SearchApi.php
+++ b/src/Bundle/ChillMainBundle/Search/SearchApi.php
@@ -20,19 +20,15 @@ class SearchApi
private EntityManagerInterface $em;
private PaginatorFactory $paginator;
- private array $providers = [];
+ private iterable $providers = [];
public function __construct(
EntityManagerInterface $em,
- SearchPersonApiProvider $searchPerson,
- ThirdPartyApiSearch $thirdPartyApiSearch,
- SearchUserApiProvider $searchUser,
+ iterable $providers,
PaginatorFactory $paginator
) {
$this->em = $em;
- $this->providers[] = $searchPerson;
- $this->providers[] = $thirdPartyApiSearch;
- $this->providers[] = $searchUser;
+ $this->providers = $providers;
$this->paginator = $paginator;
}
@@ -68,10 +64,15 @@ class SearchApi
private function findProviders(string $pattern, array $types, array $parameters): array
{
- return \array_filter(
- $this->providers,
- fn($p) => $p->supportsTypes($pattern, $types, $parameters)
- );
+ $providers = [];
+
+ foreach ($this->providers as $provider) {
+ if ($provider->supportsTypes($pattern, $types, $parameters)) {
+ $providers[] = $provider;
+ }
+ }
+
+ return $providers;
}
private function countItems($providers, $types, $parameters): int
@@ -82,12 +83,12 @@ class SearchApi
$countNq = $this->em->createNativeQuery($countQuery, $rsmCount);
$countNq->setParameters($parameters);
- return $countNq->getSingleScalarResult();
+ return (int) $countNq->getSingleScalarResult();
}
private function buildCountQuery(array $queries, $types, $parameters)
{
- $query = "SELECT COUNT(*) AS count FROM ({union_unordered}) AS sq";
+ $query = "SELECT SUM(c) AS count FROM ({union_unordered}) AS sq";
$unions = [];
$parameters = [];
@@ -141,17 +142,20 @@ class SearchApi
private function prepareProviders(array $rawResults)
{
$metadatas = [];
+ $providers = [];
+
foreach ($rawResults as $r) {
foreach ($this->providers as $k => $p) {
if ($p->supportsResult($r['key'], $r['metadata'])) {
$metadatas[$k][] = $r['metadata'];
+ $providers[$k] = $p;
break;
}
}
}
foreach ($metadatas as $k => $m) {
- $this->providers[$k]->prepare($m);
+ $providers[$k]->prepare($m);
}
}
diff --git a/src/Bundle/ChillMainBundle/Search/SearchApiQuery.php b/src/Bundle/ChillMainBundle/Search/SearchApiQuery.php
index 095d49b43..8d656aa7c 100644
--- a/src/Bundle/ChillMainBundle/Search/SearchApiQuery.php
+++ b/src/Bundle/ChillMainBundle/Search/SearchApiQuery.php
@@ -4,6 +4,8 @@ namespace Chill\MainBundle\Search;
class SearchApiQuery
{
+ private array $select = [];
+ private array $selectParams = [];
private ?string $selectKey = null;
private array $selectKeyParams = [];
private ?string $jsonbMetadata = null;
@@ -15,6 +17,38 @@ class SearchApiQuery
private array $whereClauses = [];
private array $whereClausesParams = [];
+ public function addSelectClause(string $select, array $params = []): self
+ {
+ $this->select[] = $select;
+ $this->selectParams = [...$this->selectParams, ...$params];
+
+ return $this;
+ }
+
+ public function resetSelectClause(): self
+ {
+ $this->select = [];
+ $this->selectParams = [];
+ $this->selectKey = null;
+ $this->selectKeyParams = [];
+ $this->jsonbMetadata = null;
+ $this->jsonbMetadataParams = [];
+ $this->pertinence = null;
+ $this->pertinenceParams = [];
+
+ return $this;
+ }
+
+ public function getSelectClauses(): array
+ {
+ return $this->select;
+ }
+
+ public function getSelectParams(): array
+ {
+ return $this->selectParams;
+ }
+
public function setSelectKey(string $selectKey, array $params = []): self
{
$this->selectKey = $selectKey;
@@ -47,6 +81,16 @@ class SearchApiQuery
return $this;
}
+ public function getFromClause(): string
+ {
+ return $this->fromClause;
+ }
+
+ public function getFromParams(): array
+ {
+ return $this->fromClauseParams;
+ }
+
/**
* Set the where clause and replace all existing ones.
*
@@ -54,7 +98,7 @@ class SearchApiQuery
public function setWhereClauses(string $whereClause, array $params = []): self
{
$this->whereClauses = [$whereClause];
- $this->whereClausesParams = [$params];
+ $this->whereClausesParams = $params;
return $this;
}
@@ -71,11 +115,53 @@ class SearchApiQuery
public function andWhereClause(string $whereClause, array $params = []): self
{
$this->whereClauses[] = $whereClause;
- $this->whereClausesParams[] = $params;
+ \array_push($this->whereClausesParams, ...$params);
return $this;
}
+ private function buildSelectParams(bool $count = false): array
+ {
+ if ($count) {
+ return [];
+ }
+
+ $args = $this->getSelectParams();
+
+ if (null !== $this->selectKey) {
+ $args = [...$args, ...$this->selectKeyParams];
+ }
+ if (null !== $this->jsonbMetadata) {
+ $args = [...$args, ...$this->jsonbMetadataParams];
+ }
+ if (null !== $this->pertinence) {
+ $args = [...$args, ...$this->pertinenceParams];
+ }
+
+ return $args;
+ }
+
+ private function buildSelectClause(bool $countOnly = false): string
+ {
+ if ($countOnly) {
+ return 'count(*) AS c';
+ }
+
+ $selects = $this->getSelectClauses();
+
+ if (null !== $this->selectKey) {
+ $selects[] = \strtr("'{key}' AS key", [ '{key}' => $this->selectKey ]);
+ }
+ if (null !== $this->jsonbMetadata) {
+ $selects[] = \strtr('{metadata} AS metadata', [ '{metadata}' => $this->jsonbMetadata]);
+ }
+ if (null !== $this->pertinence) {
+ $selects[] = \strtr('{pertinence} AS pertinence', [ '{pertinence}' => $this->pertinence]);
+ }
+
+ return \implode(', ', $selects);
+ }
+
public function buildQuery(bool $countOnly = false): string
{
$isMultiple = count($this->whereClauses);
@@ -87,19 +173,8 @@ class SearchApiQuery
($isMultiple ? ')' : '')
;
- if (!$countOnly) {
- $select = \strtr("
- '{key}' AS key,
- {metadata} AS metadata,
- {pertinence} AS pertinence
- ", [
- '{key}' => $this->selectKey,
- '{metadata}' => $this->jsonbMetadata,
- '{pertinence}' => $this->pertinence,
- ]);
- } else {
- $select = "1 AS c";
- }
+ $select = $this->buildSelectClause($countOnly);
+
return \strtr("SELECT
{select}
@@ -116,18 +191,16 @@ class SearchApiQuery
public function buildParameters(bool $countOnly = false): array
{
if (!$countOnly) {
- return \array_merge(
- $this->selectKeyParams,
- $this->jsonbMetadataParams,
- $this->pertinenceParams,
- $this->fromClauseParams,
- \array_merge([], ...$this->whereClausesParams),
- );
+ return [
+ ...$this->buildSelectParams($countOnly),
+ ...$this->fromClauseParams,
+ ...$this->whereClausesParams,
+ ];
} else {
- return \array_merge(
- $this->fromClauseParams,
- \array_merge([], ...$this->whereClausesParams),
- );
+ return [
+ ...$this->fromClauseParams,
+ ...$this->whereClausesParams,
+ ];
}
}
}
diff --git a/src/Bundle/ChillMainBundle/Search/SearchProvider.php b/src/Bundle/ChillMainBundle/Search/SearchProvider.php
index ca5d169fa..3d02579bb 100644
--- a/src/Bundle/ChillMainBundle/Search/SearchProvider.php
+++ b/src/Bundle/ChillMainBundle/Search/SearchProvider.php
@@ -10,10 +10,10 @@ use Chill\MainBundle\Search\HasAdvancedSearchFormInterface;
* installed into the app.
* the service is callable from the container with
* $container->get('chill_main.search_provider')
- *
- * the syntax for search string is :
- * - domain, which begin with `@`. Example: `@person`. Restrict the search to some
- * entities. It may exists multiple search provider for the same domain (example:
+ *
+ * the syntax for search string is :
+ * - domain, which begin with `@`. Example: `@person`. Restrict the search to some
+ * entities. It may exists multiple search provider for the same domain (example:
* a search provider for people which performs regular search, and suggestion search
* with phonetical algorithms
* - terms, which are the terms of the search. There are terms with argument (example :
@@ -25,17 +25,17 @@ class SearchProvider
{
/**
- *
+ *
* @var SearchInterface[]
*/
private $searchServices = array();
-
+
/**
*
* @var HasAdvancedSearchForm[]
*/
private $hasAdvancedFormSearchServices = array();
-
+
/*
* return search services in an array, ordered by
* the order key (defined in service definition)
@@ -59,7 +59,7 @@ class SearchProvider
return $this->searchServices;
}
-
+
public function getHasAdvancedFormSearchServices()
{
//sort the array
@@ -75,7 +75,7 @@ class SearchProvider
/**
* parse the search string to extract domain and terms
- *
+ *
* @param string $pattern
* @return string[] an array where the keys are _domain, _default (residual terms) or term
*/
@@ -95,9 +95,9 @@ class SearchProvider
/**
* Extract the domain of the subject
- *
+ *
* The domain begins with `@`. Example: `@person`, `@report`, ....
- *
+ *
* @param type $subject
* @return string
* @throws ParsingException
@@ -121,14 +121,15 @@ class SearchProvider
private function extractTerms(&$subject)
{
$terms = array();
- preg_match_all('/([a-z\-]+):([\w\-]+|\([^\(\r\n]+\))/', $subject, $matches);
+ $matches = [];
+ preg_match_all('/([a-z\-]+):(([^"][\S\-]+)|"[^"]*")/', $subject, $matches);
foreach ($matches[2] as $key => $match) {
//remove from search pattern
$this->mustBeExtracted[] = $matches[0][$key];
//strip parenthesis
- if (mb_substr($match, 0, 1) === '(' &&
- mb_substr($match, mb_strlen($match) - 1) === ')') {
+ if (mb_substr($match, 0, 1) === '"' &&
+ mb_substr($match, mb_strlen($match) - 1) === '"') {
$match = trim(mb_substr($match, 1, mb_strlen($match) - 2));
}
$terms[$matches[1][$key]] = $match;
@@ -139,14 +140,14 @@ class SearchProvider
/**
* store string which must be extracted to find default arguments
- *
+ *
* @var string[]
*/
private $mustBeExtracted = array();
/**
* extract default (residual) arguments
- *
+ *
* @param string $subject
* @return string
*/
@@ -158,7 +159,7 @@ class SearchProvider
/**
* search through services which supports domain and give
* results as an array of resultsfrom different SearchInterface
- *
+ *
* @param string $pattern
* @param number $start
* @param number $limit
@@ -167,25 +168,25 @@ class SearchProvider
* @return array of results from different SearchInterface
* @throws UnknowSearchDomainException if the domain is unknow
*/
- public function getSearchResults($pattern, $start = 0, $limit = 50,
+ public function getSearchResults($pattern, $start = 0, $limit = 50,
array $options = array(), $format = 'html')
{
$terms = $this->parse($pattern);
$results = array();
-
+
//sort searchServices by order
$sortedSearchServices = array();
foreach($this->searchServices as $service) {
$sortedSearchServices[$service->getOrder()] = $service;
}
-
+
if ($terms['_domain'] !== NULL) {
foreach ($sortedSearchServices as $service) {
if ($service->supports($terms['_domain'], $format)) {
$results[] = $service->renderResult($terms, $start, $limit, $options);
}
}
-
+
if (count($results) === 0) {
throw new UnknowSearchDomainException($terms['_domain']);
}
@@ -196,24 +197,24 @@ class SearchProvider
}
}
}
-
+
//sort array
ksort($results);
return $results;
}
-
+
public function getResultByName($pattern, $name, $start = 0, $limit = 50,
- array $options = array(), $format = 'html')
+ array $options = array(), $format = 'html')
{
$terms = $this->parse($pattern);
$search = $this->getByName($name);
-
+
if ($terms['_domain'] !== NULL && !$search->supports($terms['_domain'], $format))
{
throw new ParsingException("The domain is not supported for the search name");
}
-
+
return $search->renderResult($terms, $start, $limit, $options, $format);
}
@@ -232,16 +233,16 @@ class SearchProvider
throw new UnknowSearchNameException($name);
}
}
-
+
/**
- * return searchservice with an advanced form, defined in service
+ * return searchservice with an advanced form, defined in service
* definition.
- *
+ *
* @param string $name
* @return HasAdvancedSearchForm
* @throws UnknowSearchNameException
*/
- public function getHasAdvancedFormByName($name)
+ public function getHasAdvancedFormByName($name)
{
if (\array_key_exists($name, $this->hasAdvancedFormSearchServices)) {
return $this->hasAdvancedFormSearchServices[$name];
@@ -253,7 +254,7 @@ class SearchProvider
public function addSearchService(SearchInterface $service, $name)
{
$this->searchServices[$name] = $service;
-
+
if ($service instanceof HasAdvancedSearchFormInterface) {
$this->hasAdvancedFormSearchServices[$name] = $service;
}
@@ -477,7 +478,7 @@ class SearchProvider
$string = strtr($string, $chars);
} /* remove from wordpress: we use only utf 8
* else {
-
+
// Assume ISO-8859-1 if not UTF-8
$chars['in'] = chr(128) . chr(131) . chr(138) . chr(142) . chr(154) . chr(158)
. chr(159) . chr(162) . chr(165) . chr(181) . chr(192) . chr(193) . chr(194)
diff --git a/src/Bundle/ChillMainBundle/Search/Utils/ExtractDateFromPattern.php b/src/Bundle/ChillMainBundle/Search/Utils/ExtractDateFromPattern.php
new file mode 100644
index 000000000..84540a708
--- /dev/null
+++ b/src/Bundle/ChillMainBundle/Search/Utils/ExtractDateFromPattern.php
@@ -0,0 +1,36 @@
+ ""]));
+ }
+ }
+ }
+
+ return new SearchExtractionResult($filteredSubject, $dates);
+ }
+}
diff --git a/src/Bundle/ChillMainBundle/Search/Utils/ExtractPhonenumberFromPattern.php b/src/Bundle/ChillMainBundle/Search/Utils/ExtractPhonenumberFromPattern.php
new file mode 100644
index 000000000..b4601a5f7
--- /dev/null
+++ b/src/Bundle/ChillMainBundle/Search/Utils/ExtractPhonenumberFromPattern.php
@@ -0,0 +1,54 @@
+ $char) {
+ switch ($char) {
+ case '0':
+ $length++;
+ if ($key === 0) { $phonenumber[] = '+32'; }
+ else { $phonenumber[] = $char; }
+ break;
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ $length++;
+ $phonenumber[] = $char;
+ break;
+ case ' ':
+ break;
+ default:
+ throw new \LogicException("should not match not alnum character");
+ }
+ }
+
+ if ($length > 5) {
+ $filtered = \trim(\strtr($subject, [$matches[0] => '']));
+
+ return new SearchExtractionResult($filtered, [\implode('', $phonenumber)] );
+ }
+ }
+
+ return new SearchExtractionResult($subject, []);
+ }
+}
diff --git a/src/Bundle/ChillMainBundle/Search/Utils/SearchExtractionResult.php b/src/Bundle/ChillMainBundle/Search/Utils/SearchExtractionResult.php
new file mode 100644
index 000000000..e9ca2a691
--- /dev/null
+++ b/src/Bundle/ChillMainBundle/Search/Utils/SearchExtractionResult.php
@@ -0,0 +1,30 @@
+filteredSubject = $filteredSubject;
+ $this->found = $found;
+ }
+
+ public function getFound(): array
+ {
+ return $this->found;
+ }
+
+ public function hasResult(): bool
+ {
+ return [] !== $this->found;
+ }
+
+ public function getFilteredSubject(): string
+ {
+ return $this->filteredSubject;
+ }
+}
diff --git a/src/Bundle/ChillMainBundle/Tests/Search/SearchApiQueryTest.php b/src/Bundle/ChillMainBundle/Tests/Search/SearchApiQueryTest.php
index 6aeb835e7..8e05f528f 100644
--- a/src/Bundle/ChillMainBundle/Tests/Search/SearchApiQueryTest.php
+++ b/src/Bundle/ChillMainBundle/Tests/Search/SearchApiQueryTest.php
@@ -12,7 +12,7 @@ class SearchApiQueryTest extends TestCase
$q = new SearchApiQuery();
$q->setSelectJsonbMetadata('boum')
->setSelectKey('bim')
- ->setSelectPertinence('1')
+ ->setSelectPertinence('?', ['gamma'])
->setFromClause('badaboum')
->andWhereClause('foo', [ 'alpha' ])
->andWhereClause('bar', [ 'beta' ])
@@ -21,12 +21,12 @@ class SearchApiQueryTest extends TestCase
$query = $q->buildQuery();
$this->assertStringContainsString('(foo) AND (bar)', $query);
- $this->assertEquals(['alpha', 'beta'], $q->buildParameters());
+ $this->assertEquals(['gamma', 'alpha', 'beta'], $q->buildParameters());
$query = $q->buildQuery(true);
$this->assertStringContainsString('(foo) AND (bar)', $query);
- $this->assertEquals(['alpha', 'beta'], $q->buildParameters());
+ $this->assertEquals(['gamma', 'alpha', 'beta'], $q->buildParameters());
}
public function testWithoutWhereClause()
@@ -42,4 +42,20 @@ class SearchApiQueryTest extends TestCase
$this->assertEquals([], $q->buildParameters());
}
+ public function testBuildParams()
+ {
+ $q = new SearchApiQuery();
+
+ $q
+ ->addSelectClause('bada', [ 'one', 'two' ])
+ ->addSelectClause('boum', ['three', 'four'])
+ ->setWhereClauses('mywhere', [ 'six', 'seven'])
+ ;
+
+ $params = $q->buildParameters();
+
+ $this->assertEquals(['six', 'seven'], $q->buildParameters(true));
+ $this->assertEquals(['one', 'two', 'three', 'four', 'six', 'seven'], $q->buildParameters());
+ }
+
}
diff --git a/src/Bundle/ChillMainBundle/Tests/Search/SearchProviderTest.php b/src/Bundle/ChillMainBundle/Tests/Search/SearchProviderTest.php
index 554b67b9a..99e5baed3 100644
--- a/src/Bundle/ChillMainBundle/Tests/Search/SearchProviderTest.php
+++ b/src/Bundle/ChillMainBundle/Tests/Search/SearchProviderTest.php
@@ -26,17 +26,17 @@ use PHPUnit\Framework\TestCase;
class SearchProviderTest extends TestCase
{
-
+
/**
*
- * @var SearchProvider
+ * @var SearchProvider
*/
private $search;
-
+
public function setUp()
{
$this->search = new SearchProvider();
-
+
//add a default service
$this->addSearchService(
$this->createDefaultSearchService('I am default', 10), 'default'
@@ -46,7 +46,7 @@ class SearchProviderTest extends TestCase
$this->createNonDefaultDomainSearchService('I am domain bar', 20, FALSE), 'bar'
);
}
-
+
/**
* @expectedException \Chill\MainBundle\Search\UnknowSearchNameException
*/
@@ -54,11 +54,11 @@ class SearchProviderTest extends TestCase
{
$this->search->getByName("invalid name");
}
-
+
public function testSimplePattern()
{
- $terms = $this->p("@person birthdate:2014-01-02 name:(my name) is not my name");
-
+ $terms = $this->p("@person birthdate:2014-01-02 name:\"my name\" is not my name");
+
$this->assertEquals(array(
'_domain' => 'person',
'birthdate' => '2014-01-02',
@@ -66,40 +66,40 @@ class SearchProviderTest extends TestCase
'name' => 'my name'
), $terms);
}
-
+
public function testWithoutDomain()
{
$terms = $this->p('foo:bar residual');
-
+
$this->assertEquals(array(
'_domain' => null,
'foo' => 'bar',
'_default' => 'residual'
), $terms);
}
-
+
public function testWithoutDefault()
{
$terms = $this->p('@person foo:bar');
-
+
$this->assertEquals(array(
'_domain' => 'person',
'foo' => 'bar',
'_default' => ''
), $terms);
}
-
+
public function testCapitalLetters()
{
$terms = $this->p('Foo:Bar LOL marCi @PERSON');
-
+
$this->assertEquals(array(
'_domain' => 'person',
'_default' => 'lol marci',
'foo' => 'bar'
), $terms);
}
-
+
/**
* @expectedException Chill\MainBundle\Search\ParsingException
*/
@@ -107,12 +107,11 @@ class SearchProviderTest extends TestCase
{
$term = $this->p("@person @report");
}
-
+
public function testDoubleParenthesis()
{
- $terms = $this->p("@papamobile name:(my beautiful name) residual "
- . "surname:(i love techno)");
-
+ $terms = $this->p('@papamobile name:"my beautiful name" residual surname:"i love techno"');
+
$this->assertEquals(array(
'_domain' => 'papamobile',
'name' => 'my beautiful name',
@@ -120,65 +119,65 @@ class SearchProviderTest extends TestCase
'surname' => 'i love techno'
), $terms);
}
-
+
public function testAccentued()
{
//$this->markTestSkipped('accentued characters must be implemented');
-
+
$terms = $this->p('manço bélier aztèque à saloù ê');
-
+
$this->assertEquals(array(
'_domain' => NULL,
'_default' => 'manco belier azteque a salou e'
), $terms);
}
-
+
public function testAccentuedCapitals()
{
//$this->markTestSkipped('accentued characters must be implemented');
-
+
$terms = $this->p('MANÉÀ oÛ lÎ À');
-
+
$this->assertEquals(array(
'_domain' => null,
'_default' => 'manea ou li a'
), $terms);
}
-
+
public function testTrimInParenthesis()
{
- $terms = $this->p('foo:(bar )');
-
+ $terms = $this->p('foo:"bar "');
+
$this->assertEquals(array(
'_domain' => null,
'foo' => 'bar',
'_default' => ''
), $terms);
}
-
+
public function testTrimInDefault()
{
$terms = $this->p(' foo bar ');
-
+
$this->assertEquals(array(
'_domain' => null,
'_default' => 'foo bar'
), $terms);
}
-
+
public function testArgumentNameWithTrait()
{
$terms = $this->p('date-from:2016-05-04');
-
+
$this->assertEquals(array(
'_domain' => null,
'date-from' => '2016-05-04',
'_default' => ''
), $terms);
}
-
+
/**
- * Test the behaviour when no domain is provided in the search pattern :
+ * Test the behaviour when no domain is provided in the search pattern :
* the default search should be enabled
*/
public function testSearchResultDefault()
@@ -186,12 +185,12 @@ class SearchProviderTest extends TestCase
$response = $this->search->getSearchResults('default search');
//$this->markTestSkipped();
-
+
$this->assertEquals(array(
"I am default"
- ), $response);
+ ), $response);
}
-
+
/**
* @expectedException \Chill\MainBundle\Search\UnknowSearchDomainException
*/
@@ -200,49 +199,49 @@ class SearchProviderTest extends TestCase
$response = $this->search->getSearchResults('@unknow domain');
//$this->markTestSkipped();
-
+
}
-
+
public function testSearchResultDomainSearch()
{
//add a search service which will be supported
$this->addSearchService(
$this->createNonDefaultDomainSearchService("I am domain foo", 100, TRUE), 'foo'
);
-
+
$response = $this->search->getSearchResults('@foo default search');
-
+
$this->assertEquals(array(
"I am domain foo"
), $response);
-
+
}
-
+
public function testSearchWithinSpecificSearchName()
{
//add a search service which will be supported
$this->addSearchService(
$this->createNonDefaultDomainSearchService("I am domain foo", 100, TRUE), 'foo'
);
-
+
$response = $this->search->getResultByName('@foo search', 'foo');
-
+
$this->assertEquals('I am domain foo', $response);
-
+
}
-
+
/**
* @expectedException \Chill\MainBundle\Search\ParsingException
*/
public function testSearchWithinSpecificSearchNameInConflictWithSupport()
{
$response = $this->search->getResultByName('@foo default search', 'bar');
-
+
}
-
+
/**
* shortcut for executing parse method
- *
+ *
* @param unknown $pattern
* @return string[]
*/
@@ -250,12 +249,12 @@ class SearchProviderTest extends TestCase
{
return $this->search->parse($pattern);
}
-
+
/**
* Add a search service to the chill.main.search_provider
- *
+ *
* Useful for mocking the SearchInterface
- *
+ *
* @param SearchInterface $search
* @param string $name
*/
@@ -264,52 +263,52 @@ class SearchProviderTest extends TestCase
$this->search
->addSearchService($search, $name);
}
-
+
private function createDefaultSearchService($result, $order)
{
$mock = $this
->getMockForAbstractClass('Chill\MainBundle\Search\AbstractSearch');
-
+
//set the mock as default
$mock->expects($this->any())
->method('isActiveByDefault')
->will($this->returnValue(TRUE));
-
+
$mock->expects($this->any())
->method('getOrder')
->will($this->returnValue($order));
-
+
//set the return value
$mock->expects($this->any())
->method('renderResult')
->will($this->returnValue($result));
-
+
return $mock;
}
-
+
private function createNonDefaultDomainSearchService($result, $order, $domain)
{
$mock = $this
->getMockForAbstractClass('Chill\MainBundle\Search\AbstractSearch');
-
+
//set the mock as default
$mock->expects($this->any())
->method('isActiveByDefault')
->will($this->returnValue(FALSE));
-
+
$mock->expects($this->any())
->method('getOrder')
->will($this->returnValue($order));
-
+
$mock->expects($this->any())
->method('supports')
->will($this->returnValue($domain));
-
+
//set the return value
$mock->expects($this->any())
->method('renderResult')
->will($this->returnValue($result));
-
+
return $mock;
}
}
diff --git a/src/Bundle/ChillMainBundle/Tests/Search/Utils/ExtractDateFromPatternTest.php b/src/Bundle/ChillMainBundle/Tests/Search/Utils/ExtractDateFromPatternTest.php
new file mode 100644
index 000000000..428996b8e
--- /dev/null
+++ b/src/Bundle/ChillMainBundle/Tests/Search/Utils/ExtractDateFromPatternTest.php
@@ -0,0 +1,45 @@
+extractDates($subject);
+
+ $this->assertCount($count, $result->getFound());
+ $this->assertEquals($filtered, $result->getFilteredSubject());
+ $this->assertContainsOnlyInstancesOf(\DateTimeImmutable::class, $result->getFound());
+
+ $dates = \array_map(
+ function (\DateTimeImmutable $d) {
+ return $d->format('Y-m-d');
+ }, $result->getFound()
+ );
+
+ foreach ($datesSearched as $date) {
+ $this->assertContains($date, $dates);
+ }
+ }
+
+ public function provideSubjects()
+ {
+ yield ["15/06/1981", "", 1, '1981-06-15'];
+ yield ["15/06/1981 30/12/1987", "", 2, '1981-06-15', '1987-12-30'];
+ yield ["diallo 15/06/1981", "diallo", 1, '1981-06-15'];
+ yield ["diallo 31/03/1981", "diallo", 1, '1981-03-31'];
+ yield ["diallo 15-06-1981", "diallo", 1, '1981-06-15'];
+ yield ["diallo 1981-12-08", "diallo", 1, '1981-12-08'];
+ yield ["diallo", "diallo", 0];
+ }
+
+}
diff --git a/src/Bundle/ChillMainBundle/Tests/Search/Utils/ExtractPhonenumberFromPatternTest.php b/src/Bundle/ChillMainBundle/Tests/Search/Utils/ExtractPhonenumberFromPatternTest.php
new file mode 100644
index 000000000..5c88efa7e
--- /dev/null
+++ b/src/Bundle/ChillMainBundle/Tests/Search/Utils/ExtractPhonenumberFromPatternTest.php
@@ -0,0 +1,33 @@
+extractPhonenumber($subject);
+
+ $this->assertCount($expectedCount, $result->getFound());
+ $this->assertEquals($filteredSubject, $result->getFilteredSubject());
+ $this->assertEquals($expected, $result->getFound());
+ }
+
+ public function provideData()
+ {
+ yield ['Diallo', 0, [], 'Diallo', "no phonenumber"];
+ yield ['Diallo 15/06/2021', 0, [], 'Diallo 15/06/2021', "no phonenumber and a date"];
+ yield ['Diallo 0486 123 456', 1, ['+32486123456'], 'Diallo', "a phonenumber and a name"];
+ yield ['Diallo 123 456', 1, ['123456'], 'Diallo', "a number and a name, without leadiing 0"];
+ yield ['123 456', 1, ['123456'], '', "only phonenumber"];
+ yield ['0123 456', 1, ['+32123456'], '', "only phonenumber with a leading 0"];
+ }
+
+}
diff --git a/src/Bundle/ChillMainBundle/config/services/search.yaml b/src/Bundle/ChillMainBundle/config/services/search.yaml
index 377b0655a..91cfb120b 100644
--- a/src/Bundle/ChillMainBundle/config/services/search.yaml
+++ b/src/Bundle/ChillMainBundle/config/services/search.yaml
@@ -8,7 +8,16 @@ services:
Chill\MainBundle\Search\SearchProvider: '@chill_main.search_provider'
- Chill\MainBundle\Search\SearchApi: ~
+ Chill\MainBundle\Search\SearchApi:
+ autowire: true
+ autoconfigure: true
+ arguments:
+ $providers: !tagged_iterator chill.search_api_provider
Chill\MainBundle\Search\Entity\:
resource: '../../Search/Entity'
+
+ Chill\MainBundle\Search\Utils\:
+ autowire: true
+ autoconfigure: true
+ resource: './../Search/Utils/'
diff --git a/src/Bundle/ChillMainBundle/migrations/Version20211119173554.php b/src/Bundle/ChillMainBundle/migrations/Version20211119173554.php
new file mode 100644
index 000000000..d8f705f39
--- /dev/null
+++ b/src/Bundle/ChillMainBundle/migrations/Version20211119173554.php
@@ -0,0 +1,35 @@
+addSql("COMMENT ON COLUMN $col IS NULL");
+ }
+ }
+
+ public function down(Schema $schema): void
+ {
+ $this->throwIrreversibleMigrationException();
+ }
+}
diff --git a/src/Bundle/ChillMainBundle/translations/messages.fr.yml b/src/Bundle/ChillMainBundle/translations/messages.fr.yml
index 598edaec6..9b9ceb204 100644
--- a/src/Bundle/ChillMainBundle/translations/messages.fr.yml
+++ b/src/Bundle/ChillMainBundle/translations/messages.fr.yml
@@ -86,6 +86,10 @@ address more:
Create a new address: Créer une nouvelle adresse
Create an address: Créer une adresse
Update address: Modifier l'adresse
+City or postal code: Ville ou code postal
+
+# contact
+Part of the phonenumber: Partie du numéro de téléphone
#serach
Your search is empty. Please provide search terms.: La recherche est vide. Merci de fournir des termes de recherche.
@@ -190,7 +194,7 @@ Location: Localisation
Location type list: Liste des types de localisation
Create a new location type: Créer un nouveau type de localisation
Available for users: Disponible aux utilisateurs
-Address required: Adresse requise?
+Address required: Adresse requise?
Contact data: Données de contact?
optional: optionnel
required: requis
diff --git a/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php b/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php
index 211765330..ce4169741 100644
--- a/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php
+++ b/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php
@@ -86,13 +86,6 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
$loader->load('services/security.yaml');
$loader->load('services/doctrineEventListener.yaml');
- // load service advanced search only if configure
- if ($config['search']['search_by_phone'] != 'never') {
- $loader->load('services/search_by_phone.yaml');
- $container->setParameter('chill_person.search.search_by_phone',
- $config['search']['search_by_phone']);
- }
-
if ($container->getParameter('chill_person.accompanying_period') !== 'hidden') {
$loader->load('services/exports_accompanying_period.yaml');
}
diff --git a/src/Bundle/ChillPersonBundle/DependencyInjection/Configuration.php b/src/Bundle/ChillPersonBundle/DependencyInjection/Configuration.php
index 2c53af0fe..4e42e3137 100644
--- a/src/Bundle/ChillPersonBundle/DependencyInjection/Configuration.php
+++ b/src/Bundle/ChillPersonBundle/DependencyInjection/Configuration.php
@@ -27,19 +27,6 @@ class Configuration implements ConfigurationInterface
$rootNode
->canBeDisabled()
->children()
- ->arrayNode('search')
- ->canBeDisabled()
- ->children()
- ->enumNode('search_by_phone')
- ->values(['always', 'on-domain', 'never'])
- ->defaultValue('on-domain')
- ->info('enable search by phone. \'always\' show the result '
- . 'on every result. \'on-domain\' will show the result '
- . 'only if the domain is given in the search box. '
- . '\'never\' disable this feature')
- ->end()
- ->end() //children for 'search', parent = array node 'search'
- ->end() // array 'search', parent = children of root
->arrayNode('validation')
->canBeDisabled()
->children()
diff --git a/src/Bundle/ChillPersonBundle/Entity/Household/Household.php b/src/Bundle/ChillPersonBundle/Entity/Household/Household.php
index 7ff90b652..cc497aeca 100644
--- a/src/Bundle/ChillPersonBundle/Entity/Household/Household.php
+++ b/src/Bundle/ChillPersonBundle/Entity/Household/Household.php
@@ -434,6 +434,5 @@ class Household
->addViolation();
}
}
- dump($cond);
}
}
diff --git a/src/Bundle/ChillPersonBundle/Entity/Person.php b/src/Bundle/ChillPersonBundle/Entity/Person.php
index 4e8e1d0e2..dcb70510b 100644
--- a/src/Bundle/ChillPersonBundle/Entity/Person.php
+++ b/src/Bundle/ChillPersonBundle/Entity/Person.php
@@ -39,10 +39,16 @@ use DateTimeInterface;
*
* @ORM\Entity
* @ORM\Table(name="chill_person_person",
- * indexes={@ORM\Index(
+ * indexes={
+ * @ORM\Index(
* name="person_names",
* columns={"firstName", "lastName"}
- * )})
+ * ),
+ * @ORM\Index(
+ * name="person_birthdate",
+ * columns={"birthdate"}
+ * )
+ * })
* @ORM\HasLifecycleCallbacks()
* @DiscriminatorMap(typeProperty="type", mapping={
* "person"=Person::class
diff --git a/src/Bundle/ChillPersonBundle/Entity/PersonPhone.php b/src/Bundle/ChillPersonBundle/Entity/PersonPhone.php
index 222495e17..c4497fce8 100644
--- a/src/Bundle/ChillPersonBundle/Entity/PersonPhone.php
+++ b/src/Bundle/ChillPersonBundle/Entity/PersonPhone.php
@@ -9,7 +9,10 @@ use Doctrine\ORM\Mapping as ORM;
* Person Phones
*
* @ORM\Entity
- * @ORM\Table(name="chill_person_phone")
+ * @ORM\Table(name="chill_person_phone",
+ * indexes={
+ * @ORM\Index(name="phonenumber", columns={"phonenumber"})
+ * })
*/
class PersonPhone
{
@@ -107,7 +110,7 @@ class PersonPhone
{
$this->date = $date;
}
-
+
public function isEmpty(): bool
{
return empty($this->getDescription()) && empty($this->getPhonenumber());
diff --git a/src/Bundle/ChillPersonBundle/Form/Type/GenderType.php b/src/Bundle/ChillPersonBundle/Form/Type/GenderType.php
index bdd31e899..5cad3b4f1 100644
--- a/src/Bundle/ChillPersonBundle/Form/Type/GenderType.php
+++ b/src/Bundle/ChillPersonBundle/Form/Type/GenderType.php
@@ -31,7 +31,7 @@ class GenderType extends AbstractType {
'choices' => $a,
'expanded' => true,
'multiple' => false,
- 'placeholder' => null
+ 'placeholder' => null,
));
}
diff --git a/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepository.php b/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepository.php
index cd4f8c9ce..af8ed1a12 100644
--- a/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepository.php
+++ b/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepository.php
@@ -2,11 +2,15 @@
namespace Chill\PersonBundle\Repository;
+use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Repository\CountryRepository;
use Chill\MainBundle\Search\ParsingException;
+use Chill\MainBundle\Search\SearchApi;
+use Chill\MainBundle\Search\SearchApiQuery;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\PersonBundle\Entity\Person;
+use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\NonUniqueResultException;
use Doctrine\ORM\NoResultException;
@@ -49,125 +53,114 @@ final class PersonACLAwareRepository implements PersonACLAwareRepositoryInterfac
string $default = null,
string $firstname = null,
string $lastname = null,
- ?\DateTime $birthdate = null,
- ?\DateTime $birthdateBefore = null,
- ?\DateTime $birthdateAfter = null,
+ ?\DateTimeInterface $birthdate = null,
+ ?\DateTimeInterface $birthdateBefore = null,
+ ?\DateTimeInterface $birthdateAfter = null,
string $gender = null,
- string $countryCode = null
+ string $countryCode = null,
+ string $phonenumber = null,
+ string $city = null
): array {
- $qb = $this->createSearchQuery($default, $firstname, $lastname,
+ $query = $this->buildAuthorizedQuery($default, $firstname, $lastname,
$birthdate, $birthdateBefore, $birthdateAfter, $gender,
- $countryCode);
- $this->addACLClauses($qb, 'p');
+ $countryCode, $phonenumber, $city);
- return $this->getQueryResult($qb, 'p', $simplify, $limit, $start);
- }
-
- /**
- * Helper method to prepare and return the search query for PersonACL.
- *
- * This method replace the select clause with required parameters, depending on the
- * "simplify" parameter. It also add query limits.
- *
- * The given alias must represent the person alias.
- *
- * @return array|Person[]
- */
- public function getQueryResult(QueryBuilder $qb, string $alias, bool $simplify, int $limit, int $start): array
- {
- if ($simplify) {
- $qb->select(
- $alias.'.id',
- $qb->expr()->concat(
- $alias.'.firstName',
- $qb->expr()->literal(' '),
- $alias.'.lastName'
- ).'AS text'
- );
- } else {
- $qb->select($alias);
- }
-
- $qb
- ->setMaxResults($limit)
- ->setFirstResult($start);
-
- //order by firstname, lastname
- $qb
- ->orderBy($alias.'.firstName')
- ->addOrderBy($alias.'.lastName');
-
- if ($simplify) {
- return $qb->getQuery()->getResult(Query::HYDRATE_ARRAY);
- } else {
- return $qb->getQuery()->getResult();
- }
+ return $this->fetchQueryPerson($query);
}
public function countBySearchCriteria(
string $default = null,
string $firstname = null,
string $lastname = null,
- ?\DateTime $birthdate = null,
- ?\DateTime $birthdateBefore = null,
- ?\DateTime $birthdateAfter = null,
+ ?\DateTimeInterface $birthdate = null,
+ ?\DateTimeInterface $birthdateBefore = null,
+ ?\DateTimeInterface $birthdateAfter = null,
string $gender = null,
- string $countryCode = null
+ string $countryCode = null,
+ string $phonenumber = null,
+ string $city = null
): int {
- $qb = $this->createSearchQuery($default, $firstname, $lastname,
+ $query = $this->buildAuthorizedQuery($default, $firstname, $lastname,
$birthdate, $birthdateBefore, $birthdateAfter, $gender,
- $countryCode);
- $this->addACLClauses($qb, 'p');
+ $countryCode, $phonenumber, $city)
+ ;
- return $this->getCountQueryResult($qb,'p');
+ return $this->fetchQueryCount($query);
+ }
+
+ public function fetchQueryCount(SearchApiQuery $query): int
+ {
+ $rsm = new Query\ResultSetMapping();
+ $rsm->addScalarResult('c', 'c');
+
+ $nql = $this->em->createNativeQuery($query->buildQuery(true), $rsm);
+ $nql->setParameters($query->buildParameters(true));
+
+ return $nql->getSingleScalarResult();
}
/**
- * Helper method to prepare and return the count for search query
- *
- * This method replace the select clause with required parameters, depending on the
- * "simplify" parameter.
- *
- * The given alias must represent the person alias in the query builder.
+ * @return array|Person[]
*/
- public function getCountQueryResult(QueryBuilder $qb, $alias): int
+ public function fetchQueryPerson(SearchApiQuery $query, ?int $start = 0, ?int $limit = 50): array
{
- $qb->select('COUNT('.$alias.'.id)');
+ $rsm = new Query\ResultSetMappingBuilder($this->em);
+ $rsm->addRootEntityFromClassMetadata(Person::class, 'person');
- return $qb->getQuery()->getSingleScalarResult();
+ $query->addSelectClause($rsm->generateSelectClause());
+
+ $nql = $this->em->createNativeQuery(
+ $query->buildQuery()." ORDER BY pertinence DESC OFFSET ? LIMIT ?", $rsm
+ )->setParameters(\array_merge($query->buildParameters(), [$start, $limit]));
+
+ return $nql->getResult();
}
- public function findBySimilaritySearch(string $pattern, int $firstResult,
- int $maxResult, bool $simplify = false)
- {
- $qb = $this->createSimilarityQuery($pattern);
- $this->addACLClauses($qb, 'sp');
+ public function buildAuthorizedQuery(
+ string $default = null,
+ string $firstname = null,
+ string $lastname = null,
+ ?\DateTimeInterface $birthdate = null,
+ ?\DateTimeInterface $birthdateBefore = null,
+ ?\DateTimeInterface $birthdateAfter = null,
+ string $gender = null,
+ string $countryCode = null,
+ string $phonenumber = null,
+ string $city = null
+ ): SearchApiQuery {
+ $query = $this->createSearchQuery($default, $firstname, $lastname,
+ $birthdate, $birthdateBefore, $birthdateAfter, $gender,
+ $countryCode, $phonenumber)
+ ;
- return $this->getQueryResult($qb, 'sp', $simplify, $maxResult, $firstResult);
+ return $this->addAuthorizations($query);
}
- public function countBySimilaritySearch(string $pattern)
+ private function addAuthorizations(SearchApiQuery $query): SearchApiQuery
{
- $qb = $this->createSimilarityQuery($pattern);
- $this->addACLClauses($qb, 'sp');
+ $authorizedCenters = $this->authorizationHelper
+ ->getReachableCenters($this->security->getUser(), PersonVoter::SEE);
- return $this->getCountQueryResult($qb, 'sp');
+ if ([] === $authorizedCenters) {
+ return $query->andWhereClause("FALSE = TRUE", []);
+ }
+
+ return $query
+ ->andWhereClause(
+ strtr(
+ "person.center_id IN ({{ center_ids }})",
+ [
+ '{{ center_ids }}' => \implode(', ',
+ \array_fill(0, count($authorizedCenters), '?')),
+ ]
+ ),
+ \array_map(function(Center $c) {return $c->getId();}, $authorizedCenters)
+ );
}
/**
* Create a search query without ACL
*
- * The person alias is a "p"
- *
- * @param string|null $default
- * @param string|null $firstname
- * @param string|null $lastname
- * @param \DateTime|null $birthdate
- * @param \DateTime|null $birthdateBefore
- * @param \DateTime|null $birthdateAfter
- * @param string|null $gender
- * @param string|null $countryCode
- * @return QueryBuilder
* @throws NonUniqueResultException
* @throws ParsingException
*/
@@ -175,118 +168,107 @@ final class PersonACLAwareRepository implements PersonACLAwareRepositoryInterfac
string $default = null,
string $firstname = null,
string $lastname = null,
- ?\DateTime $birthdate = null,
- ?\DateTime $birthdateBefore = null,
- ?\DateTime $birthdateAfter = null,
+ ?\DateTimeInterface $birthdate = null,
+ ?\DateTimeInterface $birthdateBefore = null,
+ ?\DateTimeInterface $birthdateAfter = null,
string $gender = null,
- string $countryCode = null
- ): QueryBuilder {
+ string $countryCode = null,
+ string $phonenumber = null,
+ string $city = null
+ ): SearchApiQuery {
+ $query = new SearchApiQuery();
+ $query
+ ->setFromClause("chill_person_person AS person")
+ ;
- if (!$this->security->getUser() instanceof User) {
- throw new \RuntimeException("Search must be performed by a valid user");
- }
- $qb = $this->em->createQueryBuilder();
- $qb->from(Person::class, 'p');
+ $pertinence = [];
+ $pertinenceArgs = [];
+ $orWhereSearchClause = [];
+ $orWhereSearchClauseArgs = [];
- if (NULL !== $firstname) {
- $qb->andWhere($qb->expr()->like('UNACCENT(LOWER(p.firstName))', ':firstname'))
- ->setParameter('firstname', '%'.$firstname.'%');
- }
+ if ("" !== $default) {
+ foreach (\explode(" ", $default) as $str) {
+ $pertinence[] =
+ "STRICT_WORD_SIMILARITY(LOWER(UNACCENT(?)), person.fullnamecanonical) + ".
+ "(person.fullnamecanonical LIKE '%' || LOWER(UNACCENT(?)) || '%')::int + ".
+ "(EXISTS (SELECT 1 FROM unnest(string_to_array(fullnamecanonical, ' ')) AS t WHERE starts_with(t, UNACCENT(LOWER(?)))))::int";
+ \array_push($pertinenceArgs, $str, $str, $str);
- if (NULL !== $lastname) {
- $qb->andWhere($qb->expr()->like('UNACCENT(LOWER(p.lastName))', ':lastname'))
- ->setParameter('lastname', '%'.$lastname.'%');
+ $orWhereSearchClause[] =
+ "(LOWER(UNACCENT(?)) <<% person.fullnamecanonical OR ".
+ "person.fullnamecanonical LIKE '%' || LOWER(UNACCENT(?)) || '%' )";
+ \array_push($orWhereSearchClauseArgs, $str, $str);
+ }
+
+ $query->andWhereClause(\implode(' OR ', $orWhereSearchClause),
+ $orWhereSearchClauseArgs);
+ } else {
+ $pertinence = ["1"];
+ $pertinenceArgs = [];
}
+ $query
+ ->setSelectPertinence(\implode(' + ', $pertinence), $pertinenceArgs)
+ ;
if (NULL !== $birthdate) {
- $qb->andWhere($qb->expr()->eq('p.birthdate', ':birthdate'))
- ->setParameter('birthdate', $birthdate);
+ $query->andWhereClause(
+ "person.birthdate = ?::date",
+ [$birthdate->format('Y-m-d')]
+ );
}
-
- if (NULL !== $birthdateAfter) {
- $qb->andWhere($qb->expr()->gt('p.birthdate', ':birthdateafter'))
- ->setParameter('birthdateafter', $birthdateAfter);
+ if (NULL !== $firstname) {
+ $query->andWhereClause(
+ "UNACCENT(LOWER(person.firstname)) LIKE '%' || UNACCENT(LOWER(?)) || '%'",
+ [$firstname]
+ );
+ }
+ if (NULL !== $lastname) {
+ $query->andWhereClause(
+ "UNACCENT(LOWER(person.lastname)) LIKE '%' || UNACCENT(LOWER(?)) || '%'",
+ [$lastname]
+ );
}
-
if (NULL !== $birthdateBefore) {
- $qb->andWhere($qb->expr()->lt('p.birthdate', ':birthdatebefore'))
- ->setParameter('birthdatebefore', $birthdateBefore);
+ $query->andWhereClause(
+ 'p.birthdate < ?::date',
+ [$birthdateBefore->format('Y-m-d')]
+ );
}
-
- if (NULL !== $gender) {
- $qb->andWhere($qb->expr()->eq('p.gender', ':gender'))
- ->setParameter('gender', $gender);
+ if (NULL !== $birthdateAfter) {
+ $query->andWhereClause(
+ 'p.birthdate > ?::date',
+ [$birthdateAfter->format('Y-m-d')]
+ );
}
-
- if (NULL !== $countryCode) {
- try {
- $country = $this->countryRepository->findOneBy(['countryCode' => $countryCode]);
- } catch (NoResultException $ex) {
- throw new ParsingException('The country code "'.$countryCode.'" '
- . ', used in nationality, is unknow', 0, $ex);
- } catch (NonUniqueResultException $e) {
- throw $e;
- }
-
- $qb->andWhere($qb->expr()->eq('p.nationality', ':nationality'))
- ->setParameter('nationality', $country);
+ if (NULL !== $phonenumber) {
+ $query->andWhereClause(
+ "person.phonenumber LIKE '%' || ? || '%' OR person.mobilenumber LIKE '%' || ? || '%' OR pp.phonenumber LIKE '%' || ? || '%'"
+ ,
+ [$phonenumber, $phonenumber, $phonenumber]
+ );
+ $query->setFromClause($query->getFromClause()." LEFT JOIN chill_person_phone pp ON pp.person_id = person.id");
}
+ if (null !== $city) {
+ $query->setFromClause($query->getFromClause()." ".
+ "JOIN view_chill_person_current_address vcpca ON vcpca.person_id = person.id ".
+ "JOIN chill_main_address cma ON vcpca.address_id = cma.id ".
+ "JOIN chill_main_postal_code cmpc ON cma.postcode_id = cmpc.id");
- if (NULL !== $default) {
- $grams = explode(' ', $default);
-
- foreach($grams as $key => $gram) {
- $qb->andWhere($qb->expr()
- ->like('p.fullnameCanonical', 'UNACCENT(LOWER(:default_'.$key.'))'))
- ->setParameter('default_'.$key, '%'.$gram.'%');
+ foreach (\explode(" ", $city) as $cityStr) {
+ $query->andWhereClause(
+ "(UNACCENT(LOWER(cmpc.label)) LIKE '%' || UNACCENT(LOWER(?)) || '%' OR cmpc.code LIKE '%' || UNACCENT(LOWER(?)) || '%')",
+ [$cityStr, $city]
+ );
}
}
-
- return $qb;
- }
-
- private function addACLClauses(QueryBuilder $qb, string $personAlias): void
- {
- // restrict center for security
- $reachableCenters = $this->authorizationHelper
- ->getReachableCenters($this->security->getUser(), 'CHILL_PERSON_SEE');
- $qb->andWhere(
- $qb->expr()->orX(
- $qb->expr()
- ->in($personAlias.'.center', ':centers'),
- $qb->expr()
- ->isNull($personAlias.'.center')
- )
- );
- $qb->setParameter('centers', $reachableCenters);
- }
-
- /**
- * Create a query for searching by similarity.
- *
- * The person alias is "sp".
- *
- * @param $pattern
- * @return QueryBuilder
- */
- public function createSimilarityQuery($pattern): QueryBuilder
- {
- $qb = $this->em->createQueryBuilder();
-
- $qb->from(Person::class, 'sp');
-
- $grams = explode(' ', $pattern);
-
- foreach($grams as $key => $gram) {
- $qb->andWhere('STRICT_WORD_SIMILARITY_OPS(:default_'.$key.', sp.fullnameCanonical) = TRUE')
- ->setParameter('default_'.$key, '%'.$gram.'%');
-
- // remove the perfect matches
- $qb->andWhere($qb->expr()
- ->notLike('sp.fullnameCanonical', 'UNACCENT(LOWER(:not_default_'.$key.'))'))
- ->setParameter('not_default_'.$key, '%'.$gram.'%');
+ if (null !== $countryCode) {
+ $query->setFromClause($query->getFromClause()." JOIN country ON person.nationality_id = country.id");
+ $query->andWhereClause("country.countrycode = UPPER(?)", [$countryCode]);
+ }
+ if (null !== $gender) {
+ $query->andWhereClause("person.gender = ?", [$gender]);
}
- return $qb;
+ return $query;
}
}
diff --git a/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepositoryInterface.php b/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepositoryInterface.php
index 8e83da03d..89566b5a6 100644
--- a/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepositoryInterface.php
+++ b/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepositoryInterface.php
@@ -3,16 +3,14 @@
namespace Chill\PersonBundle\Repository;
use Chill\MainBundle\Search\ParsingException;
+use Chill\MainBundle\Search\SearchApiQuery;
use Chill\PersonBundle\Entity\Person;
use Doctrine\ORM\NonUniqueResultException;
interface PersonACLAwareRepositoryInterface
{
-
/**
* @return array|Person[]
- * @throws NonUniqueResultException
- * @throws ParsingException
*/
public function findBySearchCriteria(
int $start,
@@ -21,30 +19,38 @@ interface PersonACLAwareRepositoryInterface
string $default = null,
string $firstname = null,
string $lastname = null,
- ?\DateTime $birthdate = null,
- ?\DateTime $birthdateBefore = null,
- ?\DateTime $birthdateAfter = null,
+ ?\DateTimeInterface $birthdate = null,
+ ?\DateTimeInterface $birthdateBefore = null,
+ ?\DateTimeInterface $birthdateAfter = null,
string $gender = null,
- string $countryCode = null
+ string $countryCode = null,
+ string $phonenumber = null,
+ string $city = null
): array;
public function countBySearchCriteria(
string $default = null,
string $firstname = null,
string $lastname = null,
- ?\DateTime $birthdate = null,
- ?\DateTime $birthdateBefore = null,
- ?\DateTime $birthdateAfter = null,
+ ?\DateTimeInterface $birthdate = null,
+ ?\DateTimeInterface $birthdateBefore = null,
+ ?\DateTimeInterface $birthdateAfter = null,
string $gender = null,
- string $countryCode = null
+ string $countryCode = null,
+ string $phonenumber = null,
+ string $city = null
);
- public function findBySimilaritySearch(
- string $pattern,
- int $firstResult,
- int $maxResult,
- bool $simplify = false
- );
-
- public function countBySimilaritySearch(string $pattern);
+ public function buildAuthorizedQuery(
+ string $default = null,
+ string $firstname = null,
+ string $lastname = null,
+ ?\DateTimeInterface $birthdate = null,
+ ?\DateTimeInterface $birthdateBefore = null,
+ ?\DateTimeInterface $birthdateAfter = null,
+ string $gender = null,
+ string $countryCode = null,
+ string $phonenumber = null,
+ string $city = null
+ ): SearchApiQuery;
}
diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Person/list_with_period.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Person/list_with_period.html.twig
index 98dff821a..f28115d8d 100644
--- a/src/Bundle/ChillPersonBundle/Resources/views/Person/list_with_period.html.twig
+++ b/src/Bundle/ChillPersonBundle/Resources/views/Person/list_with_period.html.twig
@@ -1,7 +1,12 @@
-{% macro button_person(person) %}
+{% macro button_person_after(person) %}
+ {% set household = person.getCurrentHousehold %}
+ {% if household is not null %}
+