Loading module/Application/src/Application/Search/Filter/SearchFilter.php +8 −3 Original line number Diff line number Diff line Loading @@ -10,8 +10,8 @@ use Webmozart\Assert\Assert; */ abstract class SearchFilter implements SearchFilterInterface { const OPERATOR_OR = 'OR'; const OPERATOR_AND = 'AND'; public const OPERATOR_OR = 'OR'; public const OPERATOR_AND = 'AND'; protected string $name; protected string $label; Loading Loading @@ -198,7 +198,7 @@ abstract class SearchFilter implements SearchFilterInterface * Cherche parmi les query params valués spécifiés si l'un correspond au nom de ce filtre, * et retourne la valeur associée le cas échéant. */ public function paramFromQueryParams(array $queryParams): ?string public function paramFromQueryParams(array $queryParams): mixed { $name = $this->getName(); Loading @@ -212,6 +212,11 @@ abstract class SearchFilter implements SearchFilterInterface return $queryParams[$name]; } public function applyDefaultsToQueryBuilder(QueryBuilder $qb): void { // Rien par défaut. À redéfinir, au besoin... } public function applyToQueryBuilder(QueryBuilder $qb): void { if ($this->applyToQueryBuilderCallable !== null) { Loading module/Application/src/Application/Search/Filter/SearchFilterInterface.php +13 −1 Original line number Diff line number Diff line Loading @@ -15,10 +15,22 @@ interface SearchFilterInterface public function processQueryParams(array $queryParams): void; /** * Applique ce filtre au query builder spécifié. * Retourne `true` si ce filtre peut être appliqué au query builder, en fonction de sa valeur courante. */ public function canApplyToQueryBuilder(): bool; /** * Applique au query builder spécifié le·s condition·s correspondant à la valeur de ce filtre. * NB : cette méthode n'est pas appelée si {@see canApplyToQueryBuilder()} retourne `false`. */ public function applyToQueryBuilder(QueryBuilder $qb): void; /** * Applique au query builder spécifié les conditions par défaut imposées par ce filtre, le cas échéant. * NB : cette méthode est appelée systématiquement. */ public function applyDefaultsToQueryBuilder(QueryBuilder $qb): void; /** * Retourne le petit nom de ce filtre (unique au sein d'un ensemble de filtres). */ Loading module/Application/src/Application/Search/Filter/SelectSearchFilter.php +146 −42 Original line number Diff line number Diff line Loading @@ -2,6 +2,8 @@ namespace Application\Search\Filter; use Doctrine\ORM\Query\Expr; use Doctrine\ORM\QueryBuilder; use InvalidArgumentException; use RuntimeException; use Throwable; Loading @@ -14,28 +16,80 @@ use Webmozart\Assert\Assert; */ class SelectSearchFilter extends SearchFilter { const VALUE_NULL = 'NULL'; public const VALUE_NONE = 'NULL'; /** * @var array|null */ protected ?array $data = null; /** * @var string[] */ protected bool $multiple = false; protected array $options = []; protected bool $allowsEmptyOption = true; protected string $emptyOptionLabel = "(Peu importe)"; protected bool $allowsNoneOption = false; protected string $noneOptionLabel = "(Non renseigné)"; protected bool $applyInValueOptionsConditionToQueryBuilder = false; /** * Active ou non l'application au query builder de la clause garantissant que la valeur de chaque "where field" * fait bien partie des valeurs présente dans la liste déroulante, **uniquement dans le cas où l'option "vide" * est sélectionnée**. * * L'activer est utile dans le cas où la liste de valeurs sélectionnables n'est qu'un sous-ensemble des valeurs * métiers possibles (déterminé par le rôle de l'utilisateur par ex) et où il faut donc empêcher que la sélection * de l'option "Peu importe" remonte des données interdites à l'utilisateur. * * NB : si {@see setQueryBuilderApplier()} a été utilisée pour fournir un callable, on ne pourra rien faire * puisqu'on ne disposera pas des "where fields". */ public function setApplyInValueOptionsConditionToQueryBuilder(bool $applyInValueOptionsConditionToQueryBuilder = true): static { $this->applyInValueOptionsConditionToQueryBuilder = $applyInValueOptionsConditionToQueryBuilder; return $this; } /** * Spécifie si ce filtre permet la sélection mutliple. */ public function setMultiple(?bool $multiple = true): static { $this->multiple = $multiple; return $this; } /** * Indique si ce filtre permet la sélection mutliple. */ public function multiple(): bool { return $this->multiple; } public function getValue(): mixed { $value = parent::getValue(); if ($this->multiple) { $value = array_filter((array)$value, fn($v) => $v !== ''); } return $value; } public function setValue($value): static { // si la liste de valeurs possibles est pré-remplie, verification que la valeur spécifiée est dedans. if ($value !== null && is_array($this->data)) { $options = $this->createValueOptionsFromData($this->data); if ($this->multiple) { $value = (array)$value; $found = count($value) === count(array_intersect( array_map(fn($item) => $item['value'], $options), $value )); } else { $found = false; foreach ($options as $item) { if ($item['value'] === $value) { Loading @@ -43,7 +97,8 @@ class SelectSearchFilter extends SearchFilter break; } } Assert::true($found, "Valeur de filtre illégale : " . $value); } Assert::true($found, "Valeur de filtre illégale : " . (is_array($value) ? implode(', ', $value) : $value)); } return parent::setValue($value); Loading Loading @@ -74,7 +129,7 @@ class SelectSearchFilter extends SearchFilter return $this; } public function loadData(): array public function fetchData(): array { if ($this->dataProvider === null) { throw new RuntimeException("Aucun 'data provider' fourni pour le filtre '{$this->getName()}'"); Loading @@ -85,30 +140,68 @@ class SelectSearchFilter extends SearchFilter return $callable($this); } public function applyDefaultsToQueryBuilder(QueryBuilder $qb): void { if ($this->applyInValueOptionsConditionToQueryBuilder && $this->getValue() === null) { $this->applyInValueOptionsConditionToQueryBuilder($qb); } } public function canApplyToQueryBuilder(): bool { return $this->getValue() !== null; // '0' est une valeur valide } // protected function generateQueryBuilderExprAndParamsFromWhereFields(array $whereFields): array // { // if ($this->getValue() === self::VALUE_NULL) { // $params = []; // aucun paramètre de requête puisqu'on compare à NULL // // $exprs = []; // foreach ($whereFields as $field) { // $exprs[] = sprintf("%s IS NULL", $field); // } // // return [$exprs, $params]; // } // // return parent::generateQueryBuilderExprAndParamsFromWhereFields($whereFields); // } if ($this->multiple) { return !empty((array)$this->getValue()); } return $this->getValue() !== null; // NB : '0' est une valeur valide } /** * Application au query builder spécifié de quoi garantir que la valeur fait partie des valeurs sélectionnables. * S'il n'y a aucune valeur sélectionnable (hors valeurs '' et 'Aucun'), on génère un IN() retournant toujours faux. */ protected function applyInValueOptionsConditionToQueryBuilder(QueryBuilder $qb): void { // valeur sélectionnée = "Aucun·e" : la vérification qui suit n'a pas de sens. if ($this->getValue() === static::VALUE_NONE) { return; } // si un callable a été spécifié, on n'a pas de "where fields" donc on ne peut rien faire : on couine ! Assert::null($this->applyToQueryBuilderCallable, 'Opération impossible si un callable a été spécifié.'); // aucune données : on est obligé de les charger. if ($this->data === null) { $this->data = $this->fetchData(); } $valueOptions = $this->getValueOptions() ?: $this->createValueOptionsFromData($this->data); // normalisation des options sélectionnables $whereValues = array_map(fn($value) => $value['value'] ?? $value, $valueOptions); // retrait des options "Aucun" et '' $whereValues = array_filter($whereValues, fn($value) => !in_array($value, ['', static::VALUE_NONE], true)); if (empty($whereValues)) { // NB : si aucune valeur sélectionnable, on génère un IN() retournant toujours faux $whereValues = [uniqid('aucune_valeur_selectionnable_')]; } if ($this->whereFields !== null) { $whereFields = current($this->whereFields); // strip OR/AND foreach ($whereFields as $whereField) { $qb->andWhere($qb->expr()->in($whereField, $whereValues)); } } else { $alias = current($qb->getRootAliases()); $whereField = sprintf('%s.%s', $alias, $this->getName()); $qb->andWhere($qb->expr()->in($whereField, $whereValues)); } } protected function generateParameterName(): ?string { if ($this->getValue() === self::VALUE_NULL) { if ($this->getValue() === self::VALUE_NONE) { // aucun paramètre DQL puisqu'on utilise 'is null' return null; } Loading @@ -118,8 +211,10 @@ class SelectSearchFilter extends SearchFilter protected function generateComparisonTemplate(string $operand): string { if ($this->getValue() === self::VALUE_NULL) { if ($this->getValue() === self::VALUE_NONE) { return "$operand is null"; } elseif ($this->multiple) { return "$operand in (:%s)"; } else { return parent::generateComparisonTemplate($operand); } Loading @@ -139,7 +234,7 @@ class SelectSearchFilter extends SearchFilter $options[] = static::valueOptionEmpty($this->getEmptyValueOptionLabel()); } if ($this->allowsNoneValueOption()) { $options[] = static::valueOptionUnknown($this->getNoneValueOptionLabel()); $options[] = static::valueOptionNone($this->getNoneValueOptionLabel()); } foreach ($data as $key => $value) { $options[] = $this->createValueOption($value, $key); Loading @@ -151,7 +246,10 @@ class SelectSearchFilter extends SearchFilter public function createValueOption($value, $key): array { if ($value instanceof SearchFilterValueInterface) { return $value->createSearchFilterValueOption(); $valueOption = $value->createSearchFilterValueOption(); Assert::keyExists($valueOption, 'value'); Assert::keyExists($valueOption, 'label'); return $valueOption; } try { Loading Loading @@ -314,7 +412,7 @@ class SelectSearchFilter extends SearchFilter } /** * Retourne true si, d'après les valeurs des paramètres GET, l'option de ce filtre select est sélectionnée. * Retourne true si, d'après les valeurs des paramètres GET, l'option spécifiée est sélectionnée. * * @param mixed $optionValue Valeur de l'option * @param string[] $queryParams valeurs des paramètres GET Loading @@ -324,9 +422,15 @@ class SelectSearchFilter extends SearchFilter { $optionName = $this->getName(); if ($this->multiple) { return ($optionValue !== '' && ((isset($queryParams[$optionName]) && $queryParams[$optionName] === $optionValue))) || ($optionValue === '' && (!isset($queryParams[$optionName]) || $queryParams[$optionName] === '')); ($optionValue !== '' && (isset($queryParams[$optionName]) && in_array($optionValue, (array) $queryParams[$optionName]))) || ($optionValue === '' && (!isset($queryParams[$optionName]) || in_array($optionValue, (array) $queryParams[$optionName]))); } else { return ($optionValue !== '' && (isset($queryParams[$optionName]) && $queryParams[$optionName] === $optionValue)) || ($optionValue === '' && (!isset($queryParams[$optionName]) || $queryParams[$optionName] === $optionValue)); } } Loading @@ -334,9 +438,9 @@ class SelectSearchFilter extends SearchFilter * @param string $label * @return array */ static public function valueOptionUnknown(string $label = "(Inconnu.e)"): array static public function valueOptionNone(string $label = "(Aucun.e)"): array { return ['value' => self::VALUE_NULL, 'label' => $label]; return ['value' => self::VALUE_NONE, 'label' => $label]; } /** Loading module/Application/src/Application/Search/SearchService.php +7 −2 Original line number Diff line number Diff line Loading @@ -8,8 +8,12 @@ use Application\Search\Filter\SearchFilterPluginManagerAwareInterface; use Application\Search\Filter\SelectSearchFilter; use Application\Search\Sorter\SearchSorter; use Doctrine\ORM\QueryBuilder; use UnicaenApp\Exception\RuntimeException; /** * Classe mère des services à injecter dans un {@see \Application\Search\Controller\SearchControllerInterface} et * fournissant le nécessaire pour gérer une page de recherche/filtrage de données à l'aide de filtres normalisés de * type {@see \Application\Search\Filter\SearchFilterInterface}. */ abstract class SearchService implements SearchServiceInterface, SearchFilterPluginManagerAwareInterface { protected bool $unpopulatedOptions = false; Loading Loading @@ -155,7 +159,7 @@ abstract class SearchService implements SearchServiceInterface, SearchFilterPlug if ($filter instanceof SelectSearchFilter) { // si des valeurs ont déjà été fournies, pas besoin de fetch. if (($data = $filter->getData()) === null) { $data = $filter->loadData(); // obtention des données $data = $filter->fetchData(); // obtention des données } $valueOptions = $filter->createValueOptionsFromData($data); $filterValueOptions[$filterName] = $valueOptions; Loading Loading @@ -246,6 +250,7 @@ abstract class SearchService implements SearchServiceInterface, SearchFilterPlug $qb = $this->createQueryBuilder(); foreach ($this->filters as $filter) { $filter->applyDefaultsToQueryBuilder($qb); if ($filter->canApplyToQueryBuilder()) { $filter->applyToQueryBuilder($qb); } Loading module/Application/src/Application/View/Helper/FiltersPanel/partial/filter-form-item-select.phtml +19 −12 Original line number Diff line number Diff line Loading @@ -9,19 +9,26 @@ use Application\View\Renderer\PhpRenderer; * @var array $queryParams */ $paramAttributes = $filter->getAttributes(); $paramName = $filter->getName(); $width = $paramAttributes['width'] ?? 'fit'; $liveSearch = (bool) ($paramAttributes['liveSearch'] ?? false); $attributes = $filter->getAttributes(); $isMultiple = $filter->multiple(); $classes = array_map('trim', explode(' ', $attributes['class'] ?? '')); $classes = array_merge($classes, ['filter', 'selectpicker', 'show-menu-arrow']); $attributes['class'] = implode(' ', $classes); $attributes['name'] = $isMultiple ? $filter->getName() . '[]' : $filter->getName(); $attributes['data-bs-live-search'] = ($attributes['liveSearch'] ?? false) ? 'true' : 'false'; $attributes['data-bs-width'] = $attributes['width'] ?? 'fit'; $attributes['data-bs-html'] = 'true'; if ($isMultiple) { $attributes['multiple'] = 'multiple'; } ?> <select title="" class="filter selectpicker show-menu-arrow" name="<?php echo $paramName ?>" data-live-search="<?php echo $liveSearch ? 'true' : 'false' ?>" data-width="<?php echo $width ?>" data-bs-html="true" <select <?php foreach ($attributes as $key => $value): ?> <?php echo $key . '="' . $value . '"' ?> <?php endforeach; ?> > <?php foreach ($filter->getValueOptions() as $data): ?> Loading Loading
module/Application/src/Application/Search/Filter/SearchFilter.php +8 −3 Original line number Diff line number Diff line Loading @@ -10,8 +10,8 @@ use Webmozart\Assert\Assert; */ abstract class SearchFilter implements SearchFilterInterface { const OPERATOR_OR = 'OR'; const OPERATOR_AND = 'AND'; public const OPERATOR_OR = 'OR'; public const OPERATOR_AND = 'AND'; protected string $name; protected string $label; Loading Loading @@ -198,7 +198,7 @@ abstract class SearchFilter implements SearchFilterInterface * Cherche parmi les query params valués spécifiés si l'un correspond au nom de ce filtre, * et retourne la valeur associée le cas échéant. */ public function paramFromQueryParams(array $queryParams): ?string public function paramFromQueryParams(array $queryParams): mixed { $name = $this->getName(); Loading @@ -212,6 +212,11 @@ abstract class SearchFilter implements SearchFilterInterface return $queryParams[$name]; } public function applyDefaultsToQueryBuilder(QueryBuilder $qb): void { // Rien par défaut. À redéfinir, au besoin... } public function applyToQueryBuilder(QueryBuilder $qb): void { if ($this->applyToQueryBuilderCallable !== null) { Loading
module/Application/src/Application/Search/Filter/SearchFilterInterface.php +13 −1 Original line number Diff line number Diff line Loading @@ -15,10 +15,22 @@ interface SearchFilterInterface public function processQueryParams(array $queryParams): void; /** * Applique ce filtre au query builder spécifié. * Retourne `true` si ce filtre peut être appliqué au query builder, en fonction de sa valeur courante. */ public function canApplyToQueryBuilder(): bool; /** * Applique au query builder spécifié le·s condition·s correspondant à la valeur de ce filtre. * NB : cette méthode n'est pas appelée si {@see canApplyToQueryBuilder()} retourne `false`. */ public function applyToQueryBuilder(QueryBuilder $qb): void; /** * Applique au query builder spécifié les conditions par défaut imposées par ce filtre, le cas échéant. * NB : cette méthode est appelée systématiquement. */ public function applyDefaultsToQueryBuilder(QueryBuilder $qb): void; /** * Retourne le petit nom de ce filtre (unique au sein d'un ensemble de filtres). */ Loading
module/Application/src/Application/Search/Filter/SelectSearchFilter.php +146 −42 Original line number Diff line number Diff line Loading @@ -2,6 +2,8 @@ namespace Application\Search\Filter; use Doctrine\ORM\Query\Expr; use Doctrine\ORM\QueryBuilder; use InvalidArgumentException; use RuntimeException; use Throwable; Loading @@ -14,28 +16,80 @@ use Webmozart\Assert\Assert; */ class SelectSearchFilter extends SearchFilter { const VALUE_NULL = 'NULL'; public const VALUE_NONE = 'NULL'; /** * @var array|null */ protected ?array $data = null; /** * @var string[] */ protected bool $multiple = false; protected array $options = []; protected bool $allowsEmptyOption = true; protected string $emptyOptionLabel = "(Peu importe)"; protected bool $allowsNoneOption = false; protected string $noneOptionLabel = "(Non renseigné)"; protected bool $applyInValueOptionsConditionToQueryBuilder = false; /** * Active ou non l'application au query builder de la clause garantissant que la valeur de chaque "where field" * fait bien partie des valeurs présente dans la liste déroulante, **uniquement dans le cas où l'option "vide" * est sélectionnée**. * * L'activer est utile dans le cas où la liste de valeurs sélectionnables n'est qu'un sous-ensemble des valeurs * métiers possibles (déterminé par le rôle de l'utilisateur par ex) et où il faut donc empêcher que la sélection * de l'option "Peu importe" remonte des données interdites à l'utilisateur. * * NB : si {@see setQueryBuilderApplier()} a été utilisée pour fournir un callable, on ne pourra rien faire * puisqu'on ne disposera pas des "where fields". */ public function setApplyInValueOptionsConditionToQueryBuilder(bool $applyInValueOptionsConditionToQueryBuilder = true): static { $this->applyInValueOptionsConditionToQueryBuilder = $applyInValueOptionsConditionToQueryBuilder; return $this; } /** * Spécifie si ce filtre permet la sélection mutliple. */ public function setMultiple(?bool $multiple = true): static { $this->multiple = $multiple; return $this; } /** * Indique si ce filtre permet la sélection mutliple. */ public function multiple(): bool { return $this->multiple; } public function getValue(): mixed { $value = parent::getValue(); if ($this->multiple) { $value = array_filter((array)$value, fn($v) => $v !== ''); } return $value; } public function setValue($value): static { // si la liste de valeurs possibles est pré-remplie, verification que la valeur spécifiée est dedans. if ($value !== null && is_array($this->data)) { $options = $this->createValueOptionsFromData($this->data); if ($this->multiple) { $value = (array)$value; $found = count($value) === count(array_intersect( array_map(fn($item) => $item['value'], $options), $value )); } else { $found = false; foreach ($options as $item) { if ($item['value'] === $value) { Loading @@ -43,7 +97,8 @@ class SelectSearchFilter extends SearchFilter break; } } Assert::true($found, "Valeur de filtre illégale : " . $value); } Assert::true($found, "Valeur de filtre illégale : " . (is_array($value) ? implode(', ', $value) : $value)); } return parent::setValue($value); Loading Loading @@ -74,7 +129,7 @@ class SelectSearchFilter extends SearchFilter return $this; } public function loadData(): array public function fetchData(): array { if ($this->dataProvider === null) { throw new RuntimeException("Aucun 'data provider' fourni pour le filtre '{$this->getName()}'"); Loading @@ -85,30 +140,68 @@ class SelectSearchFilter extends SearchFilter return $callable($this); } public function applyDefaultsToQueryBuilder(QueryBuilder $qb): void { if ($this->applyInValueOptionsConditionToQueryBuilder && $this->getValue() === null) { $this->applyInValueOptionsConditionToQueryBuilder($qb); } } public function canApplyToQueryBuilder(): bool { return $this->getValue() !== null; // '0' est une valeur valide } // protected function generateQueryBuilderExprAndParamsFromWhereFields(array $whereFields): array // { // if ($this->getValue() === self::VALUE_NULL) { // $params = []; // aucun paramètre de requête puisqu'on compare à NULL // // $exprs = []; // foreach ($whereFields as $field) { // $exprs[] = sprintf("%s IS NULL", $field); // } // // return [$exprs, $params]; // } // // return parent::generateQueryBuilderExprAndParamsFromWhereFields($whereFields); // } if ($this->multiple) { return !empty((array)$this->getValue()); } return $this->getValue() !== null; // NB : '0' est une valeur valide } /** * Application au query builder spécifié de quoi garantir que la valeur fait partie des valeurs sélectionnables. * S'il n'y a aucune valeur sélectionnable (hors valeurs '' et 'Aucun'), on génère un IN() retournant toujours faux. */ protected function applyInValueOptionsConditionToQueryBuilder(QueryBuilder $qb): void { // valeur sélectionnée = "Aucun·e" : la vérification qui suit n'a pas de sens. if ($this->getValue() === static::VALUE_NONE) { return; } // si un callable a été spécifié, on n'a pas de "where fields" donc on ne peut rien faire : on couine ! Assert::null($this->applyToQueryBuilderCallable, 'Opération impossible si un callable a été spécifié.'); // aucune données : on est obligé de les charger. if ($this->data === null) { $this->data = $this->fetchData(); } $valueOptions = $this->getValueOptions() ?: $this->createValueOptionsFromData($this->data); // normalisation des options sélectionnables $whereValues = array_map(fn($value) => $value['value'] ?? $value, $valueOptions); // retrait des options "Aucun" et '' $whereValues = array_filter($whereValues, fn($value) => !in_array($value, ['', static::VALUE_NONE], true)); if (empty($whereValues)) { // NB : si aucune valeur sélectionnable, on génère un IN() retournant toujours faux $whereValues = [uniqid('aucune_valeur_selectionnable_')]; } if ($this->whereFields !== null) { $whereFields = current($this->whereFields); // strip OR/AND foreach ($whereFields as $whereField) { $qb->andWhere($qb->expr()->in($whereField, $whereValues)); } } else { $alias = current($qb->getRootAliases()); $whereField = sprintf('%s.%s', $alias, $this->getName()); $qb->andWhere($qb->expr()->in($whereField, $whereValues)); } } protected function generateParameterName(): ?string { if ($this->getValue() === self::VALUE_NULL) { if ($this->getValue() === self::VALUE_NONE) { // aucun paramètre DQL puisqu'on utilise 'is null' return null; } Loading @@ -118,8 +211,10 @@ class SelectSearchFilter extends SearchFilter protected function generateComparisonTemplate(string $operand): string { if ($this->getValue() === self::VALUE_NULL) { if ($this->getValue() === self::VALUE_NONE) { return "$operand is null"; } elseif ($this->multiple) { return "$operand in (:%s)"; } else { return parent::generateComparisonTemplate($operand); } Loading @@ -139,7 +234,7 @@ class SelectSearchFilter extends SearchFilter $options[] = static::valueOptionEmpty($this->getEmptyValueOptionLabel()); } if ($this->allowsNoneValueOption()) { $options[] = static::valueOptionUnknown($this->getNoneValueOptionLabel()); $options[] = static::valueOptionNone($this->getNoneValueOptionLabel()); } foreach ($data as $key => $value) { $options[] = $this->createValueOption($value, $key); Loading @@ -151,7 +246,10 @@ class SelectSearchFilter extends SearchFilter public function createValueOption($value, $key): array { if ($value instanceof SearchFilterValueInterface) { return $value->createSearchFilterValueOption(); $valueOption = $value->createSearchFilterValueOption(); Assert::keyExists($valueOption, 'value'); Assert::keyExists($valueOption, 'label'); return $valueOption; } try { Loading Loading @@ -314,7 +412,7 @@ class SelectSearchFilter extends SearchFilter } /** * Retourne true si, d'après les valeurs des paramètres GET, l'option de ce filtre select est sélectionnée. * Retourne true si, d'après les valeurs des paramètres GET, l'option spécifiée est sélectionnée. * * @param mixed $optionValue Valeur de l'option * @param string[] $queryParams valeurs des paramètres GET Loading @@ -324,9 +422,15 @@ class SelectSearchFilter extends SearchFilter { $optionName = $this->getName(); if ($this->multiple) { return ($optionValue !== '' && ((isset($queryParams[$optionName]) && $queryParams[$optionName] === $optionValue))) || ($optionValue === '' && (!isset($queryParams[$optionName]) || $queryParams[$optionName] === '')); ($optionValue !== '' && (isset($queryParams[$optionName]) && in_array($optionValue, (array) $queryParams[$optionName]))) || ($optionValue === '' && (!isset($queryParams[$optionName]) || in_array($optionValue, (array) $queryParams[$optionName]))); } else { return ($optionValue !== '' && (isset($queryParams[$optionName]) && $queryParams[$optionName] === $optionValue)) || ($optionValue === '' && (!isset($queryParams[$optionName]) || $queryParams[$optionName] === $optionValue)); } } Loading @@ -334,9 +438,9 @@ class SelectSearchFilter extends SearchFilter * @param string $label * @return array */ static public function valueOptionUnknown(string $label = "(Inconnu.e)"): array static public function valueOptionNone(string $label = "(Aucun.e)"): array { return ['value' => self::VALUE_NULL, 'label' => $label]; return ['value' => self::VALUE_NONE, 'label' => $label]; } /** Loading
module/Application/src/Application/Search/SearchService.php +7 −2 Original line number Diff line number Diff line Loading @@ -8,8 +8,12 @@ use Application\Search\Filter\SearchFilterPluginManagerAwareInterface; use Application\Search\Filter\SelectSearchFilter; use Application\Search\Sorter\SearchSorter; use Doctrine\ORM\QueryBuilder; use UnicaenApp\Exception\RuntimeException; /** * Classe mère des services à injecter dans un {@see \Application\Search\Controller\SearchControllerInterface} et * fournissant le nécessaire pour gérer une page de recherche/filtrage de données à l'aide de filtres normalisés de * type {@see \Application\Search\Filter\SearchFilterInterface}. */ abstract class SearchService implements SearchServiceInterface, SearchFilterPluginManagerAwareInterface { protected bool $unpopulatedOptions = false; Loading Loading @@ -155,7 +159,7 @@ abstract class SearchService implements SearchServiceInterface, SearchFilterPlug if ($filter instanceof SelectSearchFilter) { // si des valeurs ont déjà été fournies, pas besoin de fetch. if (($data = $filter->getData()) === null) { $data = $filter->loadData(); // obtention des données $data = $filter->fetchData(); // obtention des données } $valueOptions = $filter->createValueOptionsFromData($data); $filterValueOptions[$filterName] = $valueOptions; Loading Loading @@ -246,6 +250,7 @@ abstract class SearchService implements SearchServiceInterface, SearchFilterPlug $qb = $this->createQueryBuilder(); foreach ($this->filters as $filter) { $filter->applyDefaultsToQueryBuilder($qb); if ($filter->canApplyToQueryBuilder()) { $filter->applyToQueryBuilder($qb); } Loading
module/Application/src/Application/View/Helper/FiltersPanel/partial/filter-form-item-select.phtml +19 −12 Original line number Diff line number Diff line Loading @@ -9,19 +9,26 @@ use Application\View\Renderer\PhpRenderer; * @var array $queryParams */ $paramAttributes = $filter->getAttributes(); $paramName = $filter->getName(); $width = $paramAttributes['width'] ?? 'fit'; $liveSearch = (bool) ($paramAttributes['liveSearch'] ?? false); $attributes = $filter->getAttributes(); $isMultiple = $filter->multiple(); $classes = array_map('trim', explode(' ', $attributes['class'] ?? '')); $classes = array_merge($classes, ['filter', 'selectpicker', 'show-menu-arrow']); $attributes['class'] = implode(' ', $classes); $attributes['name'] = $isMultiple ? $filter->getName() . '[]' : $filter->getName(); $attributes['data-bs-live-search'] = ($attributes['liveSearch'] ?? false) ? 'true' : 'false'; $attributes['data-bs-width'] = $attributes['width'] ?? 'fit'; $attributes['data-bs-html'] = 'true'; if ($isMultiple) { $attributes['multiple'] = 'multiple'; } ?> <select title="" class="filter selectpicker show-menu-arrow" name="<?php echo $paramName ?>" data-live-search="<?php echo $liveSearch ? 'true' : 'false' ?>" data-width="<?php echo $width ?>" data-bs-html="true" <select <?php foreach ($attributes as $key => $value): ?> <?php echo $key . '="' . $value . '"' ?> <?php endforeach; ?> > <?php foreach ($filter->getValueOptions() as $data): ?> Loading