diff --git a/CHANGELOG-WIP.md b/CHANGELOG-WIP.md
index 2a62aba9afb..fe72d2c8804 100644
--- a/CHANGELOG-WIP.md
+++ b/CHANGELOG-WIP.md
@@ -8,6 +8,7 @@
- Dropdown cells within editable tables are no longer center-aligned. ([#15742](https://github.com/craftcms/cms/issues/15742))
- Link fields marked as translatable now swap the selected element with the localized version when their value is getting propagated to a new site for a freshly-created element. ([#15821](https://github.com/craftcms/cms/issues/15821))
- Pressing Return when an inline-editable field is focused now submits the inline form. (Previously Ctrl/Command had to be pressed as well.) ([#15841](https://github.com/craftcms/cms/issues/15841))
+- Entry conditions can now have an “Entries” rule.
### Accessibility
- Improved the control panel for screen readers. ([#15665](https://github.com/craftcms/cms/pull/15665))
@@ -25,7 +26,9 @@
- Added support for passing aliased field handles into element queries’ `select()`/`addSelect()` methods. ([#15827](https://github.com/craftcms/cms/issues/15827))
### Extensibility
+- Added `craft\base\conditions\BaseElementsSelectConditionRule`.
- Added `craft\base\RequestTrait::getIsWebRequest()`. ([#15690](https://github.com/craftcms/cms/pull/15690))
+- Added `craft\elements\condition\EntriesConditionRule`.
- Added `craft\events\DefineAddressCountriesEvent`. ([#15711](https://github.com/craftcms/cms/pull/15711))
- Added `craft\filters\BasicHttpAuthLogin`. ([#15720](https://github.com/craftcms/cms/pull/15720))
- Added `craft\filters\BasicHttpAuthStatic`. ([#15720](https://github.com/craftcms/cms/pull/15720))
diff --git a/src/base/conditions/BaseElementsSelectConditionRule.php b/src/base/conditions/BaseElementsSelectConditionRule.php
new file mode 100644
index 00000000000..65a844b3b08
--- /dev/null
+++ b/src/base/conditions/BaseElementsSelectConditionRule.php
@@ -0,0 +1,237 @@
+
+ * @since 5.5.0
+ */
+abstract class BaseElementsSelectConditionRule extends BaseConditionRule
+{
+ /**
+ * @var string|array|null
+ * @see getElementIds()
+ * @see setElementIds()
+ */
+ private string|array|null $_elementIds = null;
+
+ /**
+ * @inheritdoc
+ */
+ public string $operator = self::OPERATOR_IN;
+
+ /**
+ * Returns the element type that can be selected.
+ *
+ * @return string
+ */
+ abstract protected function elementType(): string;
+
+ /**
+ * Returns the element source(s) that the element can be selected from.
+ *
+ * @return array|null
+ */
+ protected function sources(): ?array
+ {
+ return null;
+ }
+
+ /**
+ * Returns the element condition that filters which elements can be selected.
+ *
+ * @return ElementConditionInterface|null
+ */
+ protected function selectionCondition(): ?ElementConditionInterface
+ {
+ return null;
+ }
+
+ /**
+ * Returns the criteria that determines which elements can be selected.
+ *
+ * @return array|null
+ */
+ protected function criteria(): ?array
+ {
+ return null;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function operators(): array
+ {
+ return array_merge(parent::operators(), [
+ self::OPERATOR_IN,
+ self::OPERATOR_NOT_IN,
+ ]);
+ }
+
+ /**
+ * @param bool $parse Whether to parse the value for an environment variable
+ * @return array|string|null
+ * @throws Exception
+ * @throws \Throwable
+ */
+ public function getElementIds(bool $parse = true): array|string|null
+ {
+ if ($parse && is_string($this->_elementIds)) {
+ $elementId = App::parseEnv($this->_elementIds);
+ if ($this->condition instanceof ElementCondition && isset($this->condition->referenceElement)) {
+ $referenceElement = $this->condition->referenceElement;
+ } else {
+ $referenceElement = new stdClass();
+ }
+
+ $elementIds = Craft::$app->getView()->renderObjectTemplate($elementId, $referenceElement);
+
+ if (str_contains($elementIds, ',')) {
+ $elementIds = explode(',', $elementIds);
+ }
+
+ return $elementIds;
+ }
+
+ return $this->_elementIds;
+ }
+
+ /**
+ * @param array|string|null $elementIds
+ * @phpstan-param array|string|null $elementIds
+ */
+ public function setElementIds(array|string|null $elementIds): void
+ {
+ $this->_elementIds = $elementIds ?: null;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getConfig(): array
+ {
+ return array_merge(parent::getConfig(), [
+ 'elementIds' => $this->getElementIds(false),
+ ]);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function inputHtml(): string
+ {
+ if ($this->getCondition()->forProjectConfig) {
+ return Cp::autosuggestFieldHtml([
+ 'suggestEnvVars' => true,
+ 'suggestionFilter' => fn($value) => is_string($value) && strlen($value) > 0,
+ 'required' => true,
+ 'id' => 'elementIds',
+ 'class' => 'code',
+ 'name' => 'elementIds',
+ 'value' => $this->getElementIds(false),
+ 'tip' => Craft::t('app', 'This can be set to an environment variable, or a Twig template that outputs a comma separated list of IDs.'),
+ 'placeholder' => Craft::t('app', '{type} IDs', [
+ 'type' => $this->elementType()::displayName(),
+ ]),
+ ]);
+ }
+
+ $elements = $this->_elements();
+
+ return Cp::elementSelectHtml([
+ 'name' => 'elementIds',
+ 'elements' => $elements ?: [],
+ 'elementType' => $this->elementType(),
+ 'sources' => $this->sources(),
+ 'criteria' => $this->criteria(),
+ 'condition' => $this->selectionCondition(),
+ 'single' => false,
+ ]);
+ }
+
+ /**
+ * @return ElementInterface[]|null
+ * @throws Exception
+ * @throws \Throwable
+ */
+ private function _elements(): ?array
+ {
+ $elementIds = $this->getElementIds();
+ if (!$elementIds) {
+ return null;
+ }
+
+ /** @var string|ElementInterface $elementType */
+ /** @phpstan-var class-string|ElementInterface $elementType */
+ $elementType = $this->elementType();
+ return $elementType::find()
+ ->id($elementIds)
+ ->status(null)
+ ->all();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function defineRules(): array
+ {
+ $rules = parent::defineRules();
+ $rules[] = [['elementIds'], 'safe'];
+ return $rules;
+ }
+
+ /**
+ * Returns whether the condition rule matches the given value.
+ *
+ * @param ElementInterface|int|array|null $value
+ * @return bool
+ * @throws Exception
+ * @throws \Throwable
+ */
+ protected function matchValue(mixed $value): bool
+ {
+ $elementIds = $this->getElementIds();
+
+ if (!$elementIds) {
+ return true;
+ }
+
+ if (!$value) {
+ return false;
+ }
+
+ if ($value instanceof ElementInterface) {
+ $value = [$value->id];
+ } elseif (is_numeric($value)) {
+ $value = [(int)$value];
+ } elseif (is_array($value)) {
+ $values = [];
+ foreach ($value as $val) {
+ if ($val instanceof ElementInterface) {
+ $values[] = $val->id;
+ } elseif (is_numeric($val)) {
+ $values[] = (int)$val;
+ }
+ }
+ $value = $values;
+ }
+
+ return match ($this->operator) {
+ self::OPERATOR_IN => !empty(array_intersect($value, $elementIds)),
+ self::OPERATOR_NOT_IN => empty(array_intersect($value, $elementIds)),
+ default => false,
+ };
+ }
+}
diff --git a/src/elements/conditions/entries/EntriesConditionRule.php b/src/elements/conditions/entries/EntriesConditionRule.php
new file mode 100644
index 00000000000..4d76e839049
--- /dev/null
+++ b/src/elements/conditions/entries/EntriesConditionRule.php
@@ -0,0 +1,68 @@
+
+ * @since 5.5.0
+ */
+class EntriesConditionRule extends BaseElementsSelectConditionRule implements ElementConditionRuleInterface
+{
+ /**
+ * @inheritdoc
+ */
+ public function getLabel(): string
+ {
+ return Craft::t('app', 'Entries');
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function elementType(): string
+ {
+ return Entry::class;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getExclusiveQueryParams(): array
+ {
+ return ['id'];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function modifyQuery(ElementQueryInterface $query): void
+ {
+ $elementIds = $this->getElementIds();
+
+ if ($this->operator === self::OPERATOR_NOT_IN) {
+ ArrayHelper::prependOrAppend($elementIds, 'not', true);
+ }
+ /** @var EntryQuery $query */
+ $query->id($elementIds);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function matchElement(ElementInterface $element): bool
+ {
+ /** @var Entry $element */
+ return $this->matchValue($element->id);
+ }
+}
diff --git a/src/elements/conditions/entries/EntryCondition.php b/src/elements/conditions/entries/EntryCondition.php
index de929730771..fcd5f386446 100644
--- a/src/elements/conditions/entries/EntryCondition.php
+++ b/src/elements/conditions/entries/EntryCondition.php
@@ -22,6 +22,7 @@ protected function selectableConditionRules(): array
return array_merge(parent::selectableConditionRules(), [
AuthorConditionRule::class,
AuthorGroupConditionRule::class,
+ EntriesConditionRule::class,
ExpiryDateConditionRule::class,
HasDescendantsRule::class,
LevelConditionRule::class,
diff --git a/src/translations/en/app.php b/src/translations/en/app.php
index 3b37f29cfff..bd1840e4433 100644
--- a/src/translations/en/app.php
+++ b/src/translations/en/app.php
@@ -1653,6 +1653,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 a comma separated list of IDs.' => 'This can be set to an environment variable, or a Twig template that outputs a comma separated list of IDs.',
'This can be set to an environment variable, or begin with an alias.' => 'This can be set to an environment variable, or begin with an alias.',
'This can be set to an environment variable.' => 'This can be set to 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.',
@@ -2155,6 +2156,7 @@
'{type} Condition' => '{type} Condition',
'{type} Criteria' => '{type} Criteria',
'{type} ID' => '{type} ID',
+ '{type} IDs' => '{type} IDs',
'{type} Per Page' => '{type} Per Page',
'{type} Settings' => '{type} Settings',
'{type} Sources' => '{type} Sources',