Skip to content

Commit 57bfe51

Browse files
authored
Merge pull request #1553 from MetaModels/hotfix/add_gate_filter_condition
Filter expression rule
2 parents 3b73cde + af2a82e commit 57bfe51

File tree

13 files changed

+449
-14
lines changed

13 files changed

+449
-14
lines changed

.composer-require-checker.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"Contao\\ManagerPlugin\\Bundle\\Config\\BundleConfig",
99
"Contao\\ManagerPlugin\\Bundle\\Parser\\ParserInterface",
1010
"Contao\\ManagerPlugin\\Routing\\RoutingPluginInterface",
11-
"InspiredMinds\\ContaoFileUsage\\Result\\ResultInterface"
11+
"InspiredMinds\\ContaoFileUsage\\Result\\ResultInterface",
12+
"Symfony\\Component\\ExpressionLanguage\\ExpressionLanguage"
1213
]
1314
}

src/CoreBundle/EventListener/DcGeneral/Table/FilterSetting/FilterSettingTypeRendererCore.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
/**
44
* This file is part of MetaModels/core.
55
*
6-
* (c) 2012-2024 The MetaModels team.
6+
* (c) 2012-2026 The MetaModels team.
77
*
88
* For the full copyright and license information, please view the LICENSE
99
* file that was distributed with this source code.
@@ -14,7 +14,7 @@
1414
* @author Christian Schiffler <c.schiffler@cyberspectrum.de>
1515
* @author Sven Baumann <baumann.sv@gmail.com>
1616
* @author Ingolf Steinhardt <info@e-spin.de>
17-
* @copyright 2012-2024 The MetaModels team.
17+
* @copyright 2012-2026 The MetaModels team.
1818
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
1919
* @filesource
2020
*/
@@ -37,24 +37,24 @@ class FilterSettingTypeRendererCore extends AbstractFilterSettingTypeRenderer
3737
#[\Override]
3838
protected function getTypes()
3939
{
40-
return ['idlist', 'simplelookup', 'customsql', 'conditionand', 'conditionor'];
40+
return ['idlist', 'simplelookup', 'customsql', 'conditionand', 'conditionor', 'expression_rule'];
4141
}
4242

4343
/**
4444
* Retrieve the parameters for the label.
4545
*
4646
* @param EnvironmentInterface $environment The translator in use.
47-
*
4847
* @param ModelInterface $model The model.
4948
*
5049
* @return array
5150
*/
5251
#[\Override]
5352
protected function getLabelParameters(EnvironmentInterface $environment, ModelInterface $model)
5453
{
55-
if ($model->getProperty('type') == 'simplelookup') {
54+
if ($model->getProperty('type') === 'simplelookup') {
5655
return $this->getLabelParametersWithAttributeAndUrlParam($environment, $model);
5756
}
57+
5858
return $this->getLabelParametersNormal($environment, $model);
5959
}
6060
}

src/CoreBundle/EventListener/DcGeneral/Table/FilterSetting/PasteButtonListener.php

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
/**
44
* This file is part of MetaModels/core.
55
*
6-
* (c) 2012-2023 The MetaModels team.
6+
* (c) 2012-2026 The MetaModels team.
77
*
88
* For the full copyright and license information, please view the LICENSE
99
* file that was distributed with this source code.
@@ -14,7 +14,7 @@
1414
* @author Christian Schiffler <c.schiffler@cyberspectrum.de>
1515
* @author Sven Baumann <baumann.sv@gmail.com>
1616
* @author Ingolf Steinhardt <info@e-spin.de>
17-
* @copyright 2012-2023 The MetaModels team.
17+
* @copyright 2012-2026 The MetaModels team.
1818
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
1919
* @filesource
2020
*/
@@ -25,9 +25,11 @@
2525
use ContaoCommunityAlliance\DcGeneral\Clipboard\Filter;
2626
use ContaoCommunityAlliance\DcGeneral\Clipboard\ItemInterface;
2727
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\GetPasteButtonEvent;
28+
use ContaoCommunityAlliance\DcGeneral\Controller\ModelCollector;
2829
use ContaoCommunityAlliance\DcGeneral\Data\ModelId;
2930
use ContaoCommunityAlliance\DcGeneral\Data\ModelInterface;
3031
use MetaModels\Filter\Setting\IFilterSettingFactory;
32+
use MetaModels\Filter\Setting\IFilterSettingTypeFactory;
3133

3234
/**
3335
* This class takes care of enabling and disabling of the paste button.
@@ -41,6 +43,8 @@ class PasteButtonListener
4143
*/
4244
private IFilterSettingFactory $filterFactory;
4345

46+
private \SplObjectStorage $parents;
47+
4448
/**
4549
* Create a new instance.
4650
*
@@ -49,6 +53,7 @@ class PasteButtonListener
4953
public function __construct(IFilterSettingFactory $filterFactory)
5054
{
5155
$this->filterFactory = $filterFactory;
56+
$this->parents = new \SplObjectStorage();
5257
}
5358

5459
/**
@@ -57,6 +62,9 @@ public function __construct(IFilterSettingFactory $filterFactory)
5762
* @param GetPasteButtonEvent $event The event.
5863
*
5964
* @return void
65+
*
66+
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
67+
* @SuppressWarnings(PHPMD.NPathComplexity)
6068
*/
6169
public function handle(GetPasteButtonEvent $event)
6270
{
@@ -79,11 +87,50 @@ public function handle(GetPasteButtonEvent $event)
7987

8088
return;
8189
}
82-
$factory = $this->filterFactory->getTypeFactory($model->getProperty('type'));
90+
91+
$factory = $this->getFactoryFor($model);
92+
if (null === $factory) {
93+
// Unknown type, disallow paste.
94+
$event->setPasteIntoDisabled(true);
95+
$event->setPasteAfterDisabled(true);
96+
return;
97+
}
8398

8499
// If setting does not support children, omit them.
85-
if ($model->getId() && !($factory && $factory->isNestedType())) {
100+
if ($model->getId() && !($factory->isNestedType())) {
86101
$event->setPasteIntoDisabled(true);
87102
}
103+
104+
$collector = new ModelCollector($event->getEnvironment());
105+
if ($factory->isNestedType() && (null !== ($maxChildren = $factory->getMaxChildren()))) {
106+
if ($maxChildren < count($collector->collectDirectChildrenOf($model))) {
107+
$event->setPasteIntoDisabled(true);
108+
}
109+
}
110+
111+
if (!$this->parents->contains($model)) {
112+
$this->parents[$model] = $collector->searchParentOf($model);
113+
}
114+
115+
$parent = $this->parents[$model];
116+
if (!$parent) {
117+
return;
118+
}
119+
120+
$parentFactory = $this->getFactoryFor($parent);
121+
if (!$parentFactory?->isNestedType() || (null === ($maxChildren = $parentFactory->getMaxChildren()))) {
122+
return;
123+
}
124+
125+
$siblings = $collector->collectSiblingsOf($model, $parent->getId());
126+
// FIXME: Except, if we are already contained and just get moved within parent :(
127+
if ($maxChildren <= $siblings->length()) {
128+
$event->setPasteAfterDisabled(true);
129+
}
130+
}
131+
132+
private function getFactoryFor(ModelInterface $model): ?IFilterSettingTypeFactory
133+
{
134+
return $this->filterFactory->getTypeFactory($model->getProperty('type'));
88135
}
89136
}

src/CoreBundle/Resources/config/filter-settings.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,11 @@ services:
5454
- '@translator'
5555
tags:
5656
- { name: metamodels.filter_factory }
57+
58+
MetaModels\Filter\Setting\ExpressionRuleFilterSettingTypeFactory:
59+
arguments:
60+
- '@metamodels.expression-language'
61+
- '@request_stack'
62+
- '@translator'
63+
tags:
64+
- { name: metamodels.filter_factory }

src/CoreBundle/Resources/config/services.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,3 +356,6 @@ services:
356356
decorates: 'cca.backend-help-provider'
357357
arguments:
358358
$previous: '@.inner'
359+
360+
metamodels.expression-language:
361+
class: Symfony\Component\ExpressionLanguage\ExpressionLanguage

src/CoreBundle/Resources/contao/dca/tl_metamodel_filtersetting.php

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,14 @@
9494
'remote' => 'id',
9595
'operation' => '=',
9696
],
97-
]
97+
],
98+
'inverse' => [
99+
[
100+
'local' => 'pid',
101+
'remote' => 'id',
102+
'operation' => '=',
103+
],
104+
],
98105
]
99106
],
100107
'rootEntries' => [
@@ -223,6 +230,14 @@
223230
'stop_after_match'
224231
]
225232
],
233+
'expression_rule extends default' => [
234+
'config' => [
235+
'expression_rule'
236+
],
237+
'+fefilter' => [
238+
'onlypossible',
239+
],
240+
],
226241
'idlist extends default' => [
227242
'+config' => [
228243
'items'
@@ -450,6 +465,19 @@
450465
],
451466
'sql' => "char(1) NOT NULL default ''"
452467
],
468+
'expression_rule' => [
469+
'label' => 'expression_rule.label',
470+
'description' => 'expression_rule.description',
471+
'exclude' => true,
472+
'inputType' => 'text',
473+
'eval' => [
474+
'alwaysSave' => true,
475+
'decodeEntities' => true,
476+
'mandatory' => true,
477+
'tl_class' => 'clr',
478+
],
479+
'sql' => "text NOT NULL default ''"
480+
],
453481
'label' => [
454482
'label' => 'label.label',
455483
'description' => 'label.description',
654 Bytes
Loading
4.62 KB
Loading

src/CoreBundle/Resources/translations/tl_metamodel_filtersetting.en.xlf

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,12 @@
298298
<trans-unit id="typenames.conditionand" resname="typenames.conditionand">
299299
<source>AND condition</source>
300300
</trans-unit>
301+
<trans-unit id="typenames.expression_rule" resname="typenames.expression_rule">
302+
<source>Expression rule</source>
303+
</trans-unit>
304+
<trans-unit id="error.condition_max_children" resname="error.condition_max_children">
305+
<source>The condition or rule "%name%" can only have "%max%" children max.</source>
306+
</trans-unit>
301307
<trans-unit id="typedesc._attribute_" resname="typedesc._attribute_">
302308
<source>&lt;em&gt;[%colName%, "%name%"]&lt;/em&gt;</source>
303309
</trans-unit>
@@ -322,12 +328,22 @@
322328
<trans-unit id="typedesc.conditionand" resname="typedesc.conditionand">
323329
<source>%1$s &lt;strong&gt;%2$s&lt;/strong&gt; %4$s</source>
324330
</trans-unit>
331+
<trans-unit id="typedesc.expression_rule" resname="typedesc.expression_rule">
332+
<source>%1$s &lt;strong&gt;%2$s&lt;/strong&gt; %4$s</source>
333+
</trans-unit>
325334
<trans-unit id="items.label" resname="items.label">
326335
<source>Items</source>
327336
</trans-unit>
328337
<trans-unit id="items.description" resname="items.description">
329338
<source>Please enter the IDs of the items for filtering as comma-separated list.</source>
330339
</trans-unit>
340+
<trans-unit id="expression_rule.label" resname="expression_rule.label">
341+
<source>Expression</source>
342+
</trans-unit>
343+
<trans-unit id="expression_rule.description" resname="expression_rule.description">
344+
<source>Enter an expression here that is to be evaluated. If the condition is met, the
345+
first filter rule is executed; if not, the second one is executed.</source>
346+
</trans-unit>
331347
<trans-unit id="deleteConfirm" resname="deleteConfirm">
332348
<source>Do you really want to delete filter setting ID %id%?</source>
333349
</trans-unit>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MetaModels\Filter\Rules;
6+
7+
use MetaModels\Filter\IFilter;
8+
use MetaModels\Filter\IFilterRule;
9+
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
10+
11+
/**
12+
* This is the MetaModel filter interface.
13+
*/
14+
final readonly class ExpressionRule implements IFilterRule
15+
{
16+
public function __construct(
17+
private string $expression,
18+
private array $parameters,
19+
private ExpressionLanguage $expressionLanguage,
20+
private ?IFilter $ifTrue,
21+
private ?IFilter $ifFalse,
22+
) {
23+
}
24+
25+
/** @return list<string>|null */
26+
#[\Override]
27+
public function getMatchingIds(): ?array
28+
{
29+
if ((bool) $this->expressionLanguage->evaluate($this->expression, $this->parameters)) {
30+
return $this->ifTrue?->getMatchingIds();
31+
}
32+
33+
if (null !== $this->ifFalse) {
34+
return $this->ifFalse->getMatchingIds();
35+
}
36+
37+
return [];
38+
}
39+
}

0 commit comments

Comments
 (0)