Skip to content

Commit ecd89ec

Browse files
committed
Add multi-select filter for view filtering
Closes #6461
1 parent 29ce0d4 commit ecd89ec

File tree

7 files changed

+161
-10
lines changed

7 files changed

+161
-10
lines changed

wcfsetup/install/files/lib/action/GridViewFilterAction.class.php

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,19 @@ public function handle(ServerRequestInterface $request): ResponseInterface
6565
return $response;
6666
}
6767

68-
$data = $form->getData()['data'];
68+
$rawData = $form->getData();
69+
$data = $rawData['data'];
70+
71+
foreach ($view->getAvailableFilters() as $filter) {
72+
if (!isset($rawData[$filter->getFormDataId()])) {
73+
continue;
74+
}
75+
76+
$data[$filter->getId()] = $filter->serializeValue($rawData[$filter->getFormDataId()]);
77+
}
78+
6979
foreach ($data as $key => $value) {
70-
if ($value === '' || $value === null) {
80+
if ($value === '' || $value === null || $value === 0) {
7181
unset($data[$key]);
7282
}
7383
}
@@ -95,7 +105,8 @@ private function getForm(AbstractGridView $gridView, array $values): Psr15Dialog
95105
$formField = $filter->getFormField();
96106

97107
if (isset($values[$filter->getID()])) {
98-
$formField->value($values[$filter->getID()]);
108+
$value = $filter->unserializeValue($values[$filter->getID()]);
109+
$formField->value($value);
99110
}
100111

101112
$form->appendChild($formField);

wcfsetup/install/files/lib/action/ListViewFilterAction.class.php

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,16 @@ public function handle(ServerRequestInterface $request): ResponseInterface
6464
if ($response !== null) {
6565
return $response;
6666
}
67+
6768
$rawData = $form->getData();
6869
$data = $rawData['data'];
69-
// This code is required to bypass the strange behavior of the LabelFormField.
70-
if (!empty($rawData['labelIDs'])) {
71-
foreach ($rawData['labelIDs'] as $groupID => $value) {
72-
$data['labelIDs' . $groupID] = $value;
70+
71+
foreach ($view->getAvailableFilters() as $filter) {
72+
if (!isset($rawData[$filter->getFormDataId()])) {
73+
continue;
7374
}
75+
76+
$data[$filter->getId()] = $filter->serializeValue($rawData[$filter->getFormDataId()]);
7477
}
7578

7679
foreach ($data as $key => $value) {
@@ -102,7 +105,8 @@ private function getForm(AbstractListView $listView, array $values): Psr15Dialog
102105
$formField = $filter->getFormField();
103106

104107
if (isset($values[$filter->getID()])) {
105-
$formField->value($values[$filter->getID()]);
108+
$value = $filter->unserializeValue($values[$filter->getID()]);
109+
$formField->value($value);
106110
}
107111

108112
$form->appendChild($formField);

wcfsetup/install/files/lib/system/gridView/admin/ArticleGridView.class.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
use wcf\system\interaction\EditInteraction;
2727
use wcf\system\view\filter\BooleanFilter;
2828
use wcf\system\view\filter\CategoryFilter;
29+
use wcf\system\view\filter\MultipleSelectFilter;
2930
use wcf\system\view\filter\SelectFilter;
3031
use wcf\system\view\filter\TextFilter;
3132
use wcf\system\view\filter\TimeFilter;
@@ -192,7 +193,7 @@ public function applyFilter(DatabaseObjectList $list, string $value): void
192193
);
193194
}
194195
},
195-
new SelectFilter(
196+
new MultipleSelectFilter(
196197
[
197198
0 => 'wcf.acp.article.publicationStatus.unpublished',
198199
1 => 'wcf.acp.article.publicationStatus.published',
@@ -201,7 +202,7 @@ public function applyFilter(DatabaseObjectList $list, string $value): void
201202
'publicationStatus',
202203
'wcf.acp.article.publicationStatus'
203204
),
204-
new BooleanFilter('isDeleted', 'wcf.acp.article.isDeleted')
205+
new BooleanFilter('isDeleted', 'wcf.acp.article.isDeleted'),
205206
]);
206207
$provider = new ArticleInteractions();
207208
$provider->addInteractions([

wcfsetup/install/files/lib/system/view/filter/AbstractFilter.class.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,22 @@ protected function getDatabaseColumnName(DatabaseObjectList $list): string
4747
{
4848
return ($this->databaseColumn ?: $list->getDatabaseTableAlias() . '.' . $this->id);
4949
}
50+
51+
#[\Override]
52+
public function serializeValue(mixed $value): string
53+
{
54+
return (string)$value;
55+
}
56+
57+
#[\Override]
58+
public function unserializeValue(string $value): mixed
59+
{
60+
return $value;
61+
}
62+
63+
#[\Override]
64+
public function getFormDataId(): string
65+
{
66+
return $this->getId();
67+
}
5068
}

wcfsetup/install/files/lib/system/view/filter/IViewFilter.class.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,19 @@ public function getId(): string;
4242
* Returns the label of this filter.
4343
*/
4444
public function getLabel(): string;
45+
46+
/**
47+
* Serializes the given form field value to a string that can be passed in a URL.
48+
*/
49+
public function serializeValue(mixed $value): string;
50+
51+
/**
52+
* Unserializes the given string to a value that can be passed to the form field.
53+
*/
54+
public function unserializeValue(string $value): mixed;
55+
56+
/**
57+
* Returns the id of the form field used by this filter.
58+
*/
59+
public function getFormDataId(): string;
4560
}

wcfsetup/install/files/lib/system/view/filter/LabelFilter.class.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,21 @@ public function renderValue(string $value): string
6868
{
6969
return $this->labelGroup->getLabel((int)$value)->getTitle();
7070
}
71+
72+
#[\Override]
73+
public function serializeValue(mixed $value): string
74+
{
75+
if (\is_array($value) && isset($value[$this->labelGroup->groupID])) {
76+
return $value[$this->labelGroup->groupID];
77+
}
78+
79+
return '';
80+
}
81+
82+
#[\Override]
83+
public function getFormDataId(): string
84+
{
85+
// `LabelFormField` stores form data values always under the key `labelIDs`.
86+
return 'labelIDs';
87+
}
7188
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php
2+
3+
namespace wcf\system\view\filter;
4+
5+
use wcf\data\DatabaseObjectList;
6+
use wcf\system\form\builder\field\AbstractFormField;
7+
use wcf\system\form\builder\field\MultipleSelectionFormField;
8+
use wcf\system\view\filter\exception\InvalidFilterValue;
9+
use wcf\system\WCF;
10+
11+
/**
12+
* Allows a column to be filtered on the basis of multiple selected values.
13+
*
14+
* @author Marcel Werk
15+
* @copyright 2001-2025 WoltLab GmbH
16+
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
17+
* @since 6.2
18+
*/
19+
class MultipleSelectFilter extends AbstractFilter
20+
{
21+
/**
22+
* @param array<string|int, mixed> $options
23+
*/
24+
public function __construct(
25+
protected readonly array $options,
26+
string $id,
27+
string $languageItem,
28+
string $databaseColumn = '',
29+
protected readonly bool $labelLanguageItems = true
30+
) {
31+
parent::__construct($id, $languageItem, $databaseColumn);
32+
}
33+
34+
#[\Override]
35+
public function getFormField(): AbstractFormField
36+
{
37+
return MultipleSelectionFormField::create($this->id)
38+
->label($this->languageItem)
39+
->options($this->options, labelLanguageItems: $this->labelLanguageItems);
40+
}
41+
42+
#[\Override]
43+
public function applyFilter(DatabaseObjectList $list, string $value): void
44+
{
45+
$filterValues = $this->unserializeValue($value);
46+
foreach ($filterValues as $filterValue) {
47+
if (!isset($this->options[$filterValue])) {
48+
throw new InvalidFilterValue("Invalid value '{$filterValue}' for filter '{$this->id}' given.");
49+
}
50+
}
51+
52+
$columnName = $this->getDatabaseColumnName($list);
53+
54+
$list->getConditionBuilder()->add("{$columnName} IN (?)", [$filterValues]);
55+
}
56+
57+
#[\Override]
58+
public function renderValue(string $value): string
59+
{
60+
$renderedValues = \array_map(function ($filterValue): string {
61+
if ($this->labelLanguageItems) {
62+
return WCF::getLanguage()->get($this->options[$filterValue]);
63+
} else {
64+
return $this->options[$filterValue];
65+
}
66+
}, $this->unserializeValue($value));
67+
68+
return \implode(', ', $renderedValues);
69+
}
70+
71+
#[\Override]
72+
public function serializeValue(mixed $value): string
73+
{
74+
return \implode(',', $value);
75+
}
76+
77+
/**
78+
* @return array<string|int>
79+
*/
80+
#[\Override]
81+
public function unserializeValue(string $value): array
82+
{
83+
return \explode(',', $value);
84+
}
85+
}

0 commit comments

Comments
 (0)