Skip to content

Commit

Permalink
Allow multiple selections in element select condition rules
Browse files Browse the repository at this point in the history
Resolves #16121
  • Loading branch information
brandonkelly committed Nov 18, 2024
1 parent b2d1593 commit 6bf0270
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 57 deletions.
131 changes: 89 additions & 42 deletions src/base/conditions/BaseElementSelectConditionRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,16 @@
abstract class BaseElementSelectConditionRule extends BaseConditionRule
{
/**
* @var int|string|null
* @see getElementId()
* @see setElementId()
* @var int[]|string|null
* @see getElementIds()
* @see setElementIds()
*/
private int|string|null $_elementId = null;
private array|string|null $_elementIds = null;

/**
* Returns the element type that can be selected.
*
* @return string
* @return class-string<ElementInterface>
*/
abstract protected function elementType(): string;

Expand Down Expand Up @@ -63,6 +63,17 @@ protected function criteria(): ?array
return null;
}

/**
* Returns whether multiple elements can be selected.
*
* @return bool
* @since 5.6.0
*/
protected function allowMultiple(): bool
{
return false;
}

/**
* Defines the element select config.
*
Expand All @@ -71,34 +82,65 @@ protected function criteria(): ?array
*/
protected function elementSelectConfig(): array
{
$element = $this->_element();
$elements = $this->_elements();
return [
'name' => 'elementId',
'elements' => $element ? [$element] : [],
'name' => 'elementIds',
'elements' => $elements,
'elementType' => $this->elementType(),
'sources' => $this->sources(),
'criteria' => $this->criteria(),
'condition' => $this->selectionCondition(),
'single' => true,
'single' => !$this->allowMultiple(),
];
}

/**
* @param bool $parse Whether to parse the value for an environment variable
* @return int|string|null
* @return int[]|string
* @since 5.6.0
*/
public function getElementId(bool $parse = true): int|string|null
public function getElementIds(bool $parse = true): array|string
{
if ($parse && is_string($this->_elementId)) {
$elementId = App::parseEnv($this->_elementId);
if ($parse && is_string($this->_elementIds)) {
$elementIds = App::parseEnv($this->_elementIds);
if ($this->condition instanceof ElementCondition && isset($this->condition->referenceElement)) {
$referenceElement = $this->condition->referenceElement;
} else {
$referenceElement = new stdClass();
}
return Craft::$app->getView()->renderObjectTemplate($elementId, $referenceElement);
$elementIds = Craft::$app->getView()->renderObjectTemplate($elementIds, $referenceElement);
return array_values(array_filter(array_map(
fn(string $elementId) => (int)trim($elementId),
explode(',', $elementIds),
)));
}

return $this->_elementIds ?? [];
}

/**
* @param array|int|string|null $elementIds
* @phpstan-param array<int|string>|int|string|null $elementIds
*/
public function setElementIds(array|int|string|null $elementIds): void
{
if (is_array($elementIds)) {
$elementIds = array_map(fn($id) => (int)$id, $elementIds);
} elseif (is_numeric($elementIds)) {
$elementIds = [(int)$elementIds];
}
return $this->_elementId;

$this->_elementIds = $elementIds ?: null;
}

/**
* @param bool $parse Whether to parse the value for an environment variable
* @return int|string|null
*/
public function getElementId(bool $parse = true): int|string|null
{
$elementIds = $this->getElementIds($parse);
return (is_array($elementIds) && !empty($elementIds)) ? $elementIds[0] : null;
}

/**
Expand All @@ -107,11 +149,7 @@ public function getElementId(bool $parse = true): int|string|null
*/
public function setElementId(array|int|string|null $elementId): void
{
if (is_array($elementId)) {
$elementId = reset($elementId);
}

$this->_elementId = $elementId ?: null;
$this->setElementIds($elementId);
}

/**
Expand All @@ -120,7 +158,7 @@ public function setElementId(array|int|string|null $elementId): void
public function getConfig(): array
{
return array_merge(parent::getConfig(), [
'elementId' => $this->getElementId(false),
'elementIds' => $this->getElementIds(false),
]);
}

Expand All @@ -130,32 +168,40 @@ public function getConfig(): array
protected function inputHtml(): string
{
if ($this->getCondition()->forProjectConfig) {
$value = $this->getElementIds(false);
if (is_array($value)) {
$value = join(',', $value);
}
$type = $this->elementType()::displayName();

return Cp::autosuggestFieldHtml([
'suggestEnvVars' => true,
'suggestionFilter' => fn($value) => is_int($value) && $value > 0,
'required' => true,
'id' => 'elementId',
'id' => 'elementIds',
'class' => 'code',
'name' => 'elementId',
'value' => $this->getElementId(false),
'tip' => Craft::t('app', 'This can be set to an environment variable, or a Twig template that outputs an ID.'),
'placeholder' => Craft::t('app', '{type} ID', [
'type' => $this->elementType()::displayName(),
]),
'name' => 'elementIds',
'value' => $value,
'tip' => $this->allowMultiple()
? Craft::t('app', 'This can be set to an environment variable, or a Twig template that outputs comma-separated IDs.')
: Craft::t('app', 'This can be set to an environment variable, or a Twig template that outputs an ID.'),
'placeholder' => $this->allowMultiple()
? Craft::t('app', '{type} ID(s)', ['type' => $type])
: Craft::t('app', '{type} ID', ['type' => $type]),
]);
}

return Cp::elementSelectHtml($this->elementSelectConfig());
}

/**
* @return ElementInterface|null
* @return ElementInterface[]
*/
private function _element(): ?ElementInterface
private function _elements(): array
{
$elementId = $this->getElementId();
if (!$elementId) {
return null;
$elementIds = $this->getElementIds();
if (empty($elementIds)) {
return [];
}

/** @var string|ElementInterface $elementType */
Expand All @@ -165,9 +211,10 @@ private function _element(): ?ElementInterface
->site('*')
->preferSites(array_filter([Cp::requestedSite()?->id]))
->unique()
->id($elementId)
->id($elementIds)
->status(null)
->one();
->limit($this->allowMultiple() ? null : 1)
->all();
}

/**
Expand All @@ -176,7 +223,7 @@ private function _element(): ?ElementInterface
protected function defineRules(): array
{
$rules = parent::defineRules();
$rules[] = [['elementId'], 'number'];
$rules[] = [['elementIds'], 'safe'];
return $rules;
}

Expand All @@ -189,9 +236,9 @@ protected function defineRules(): array
*/
protected function matchValue(mixed $value): bool
{
$elementId = $this->getElementId();
$elementIds = $this->getElementIds();

if (!$elementId) {
if (empty($elementIds)) {
return true;
}

Expand All @@ -200,18 +247,18 @@ protected function matchValue(mixed $value): bool
}

if ($value instanceof ElementInterface) {
return $value->id === $elementId;
return in_array($value->id, $elementIds);
}

if (is_numeric($value)) {
return (int)$value === (int)$elementId;
return in_array((int)$value, $elementIds);
}

if (is_array($value)) {
foreach ($value as $val) {
if (
$val instanceof ElementInterface && $val->id === $elementId ||
is_numeric($val) && (int)$val === (int)$elementId
$val instanceof ElementInterface && in_array($val->id, $elementIds) ||
is_numeric($val) && in_array((int)$val, $elementIds)
) {
return true;
}
Expand Down
12 changes: 6 additions & 6 deletions src/elements/conditions/NotRelatedToConditionRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ public function getLabel(): string
*/
public function modifyQuery(ElementQueryInterface $query): void
{
$elementId = $this->getElementId();
if ($elementId !== null) {
$query->andNotRelatedTo($elementId);
$elementIds = $this->getElementIds();
if (!empty($elementIds)) {
$query->andNotRelatedTo($elementIds);
}
}

Expand All @@ -39,8 +39,8 @@ public function modifyQuery(ElementQueryInterface $query): void
*/
public function matchElement(ElementInterface $element): bool
{
$elementId = $this->getElementId();
if (!$elementId) {
$elementIds = $this->getElementIds();
if (empty($elementIds)) {
return true;
}

Expand All @@ -51,7 +51,7 @@ public function matchElement(ElementInterface $element): bool
->provisionalDrafts($element->isProvisionalDraft)
->revisions($element->getIsRevision())
->status(null)
->notRelatedTo($elementId)
->notRelatedTo($elementIds)
->exists();
}
}
20 changes: 14 additions & 6 deletions src/elements/conditions/RelatedToConditionRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ protected function elementType(): string
return $this->elementType;
}

/**
* @inheritdoc
*/
protected function allowMultiple(): bool
{
return true;
}

/**
* @inheritdoc
*/
Expand All @@ -66,9 +74,9 @@ protected function elementSelectConfig(): array
*/
public function modifyQuery(ElementQueryInterface $query): void
{
$elementId = $this->getElementId();
if ($elementId !== null) {
$query->andRelatedTo($elementId);
$elementIds = $this->getElementIds();
if (!empty($elementIds)) {
$query->andRelatedTo($elementIds);
}
}

Expand Down Expand Up @@ -141,8 +149,8 @@ public function getConfig(): array
*/
public function matchElement(ElementInterface $element): bool
{
$elementId = $this->getElementId();
if (!$elementId) {
$elementIds = $this->getElementIds();
if (empty($elementIds)) {
return true;
}

Expand All @@ -153,7 +161,7 @@ public function matchElement(ElementInterface $element): bool
->provisionalDrafts($element->isProvisionalDraft)
->revisions($element->getIsRevision())
->status(null)
->relatedTo($elementId)
->relatedTo($elementIds)
->exists();
}
}
10 changes: 9 additions & 1 deletion src/elements/conditions/entries/AuthorConditionRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ protected function criteria(): ?array
];
}

/**
* @inheritdoc
*/
protected function allowMultiple(): bool
{
return true;
}

/**
* @inheritdoc
*/
Expand All @@ -59,7 +67,7 @@ public function getExclusiveQueryParams(): array
public function modifyQuery(ElementQueryInterface $query): void
{
/** @var EntryQuery $query */
$query->authorId($this->getElementId());
$query->authorId($this->getElementIds());
}

/**
Expand Down
12 changes: 10 additions & 2 deletions src/fields/conditions/RelationalFieldConditionRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,14 @@ protected function criteria(): ?array
return $field->getInputSelectionCriteria();
}

/**
* @inheritdoc
*/
protected function allowMultiple(): bool
{
return true;
}

/**
* @inheritdoc
*/
Expand Down Expand Up @@ -139,10 +147,10 @@ public function modifyQuery(ElementQueryInterface $query): void
/**
* @inheritdoc
*/
protected function elementQueryParam(): int|string|null
protected function elementQueryParam(): array|null
{
// $this->operator will always be OPERATOR_RELATED_TO at this point
return $this->getElementId();
return $this->getElementIds();
}

/**
Expand Down
2 changes: 2 additions & 0 deletions src/translations/en/app.php
Original file line number Diff line number Diff line change
Expand Up @@ -1680,6 +1680,7 @@
'This can be set to an environment variable with a valid language ID ({examples}).' => 'This can be set to an environment variable with a valid language ID ({examples}).',
'This can be set to an environment variable with a value of a [supported time zone]({url}).' => 'This can be set to an environment variable with a value of a [supported time zone]({url}).',
'This can be set to an environment variable, or a Twig template that outputs an ID.' => 'This can be set to an environment variable, or a Twig template that outputs an ID.',
'This can be set to an environment variable, or a Twig template that outputs comma-separated IDs.' => 'This can be set to an environment variable, or a Twig template that outputs comma-separated IDs.',
'This can begin with an environment variable or alias.' => 'This can begin with an environment variable or alias.',
'This can begin with an environment variable.' => 'This can begin with an environment variable.',
'This draft’s entry type is no longer available. You can still view it, but not apply it.' => 'This draft’s entry type is no longer available. You can still view it, but not apply it.',
Expand Down Expand Up @@ -2189,6 +2190,7 @@
'{type} Condition' => '{type} Condition',
'{type} Criteria' => '{type} Criteria',
'{type} ID' => '{type} ID',
'{type} ID(s)' => '{type} ID(s)',
'{type} Per Page' => '{type} Per Page',
'{type} Settings' => '{type} Settings',
'{type} Sources' => '{type} Sources',
Expand Down

0 comments on commit 6bf0270

Please sign in to comment.