From e20799d79a6899ed617040123472e1f69b9827a3 Mon Sep 17 00:00:00 2001 From: brandonkelly Date: Tue, 15 Oct 2024 14:06:22 -0700 Subject: [PATCH] Fixed #15890 --- CHANGELOG.md | 1 + src/fields/BaseRelationField.php | 59 +++++++++++++++++++++----------- 2 files changed, 40 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8060be1de6..bc204fbff84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - Fixed a bug where Craft wasn’t auto-detecting interactive terminals on Windows. - Fixed a bug where element actions were allowed on nested entries when viewing a revision. ([#15879](https://github.com/craftcms/cms/pull/15879)) - Fixed a bug where element error summaries weren’t linking to recursively-nested Matrix fields properly. ([#15797](https://github.com/craftcms/cms/issues/15797)) +- Fixed a bug where eager-loaded relation fields were loading all related elements across all instances of the field. ([#15890](https://github.com/craftcms/cms/issues/15890)) - Fixed a privilege escalation vulnerability. ## 5.4.7.1 - 2024-10-09 diff --git a/src/fields/BaseRelationField.php b/src/fields/BaseRelationField.php index db062499c17..1fb2ca13f5c 100644 --- a/src/fields/BaseRelationField.php +++ b/src/fields/BaseRelationField.php @@ -958,27 +958,46 @@ public function getEagerLoadingMap(array $sourceElements): array|null|false { $sourceSiteId = $sourceElements[0]->siteId; - // Get the source element IDs - $sourceElementIds = array_map(fn(ElementInterface $element) => $element->id, $sourceElements); + $map = []; + $missingSourceElementIds = []; - // Return any relation data on these elements, defined with this field - $map = (new Query()) - ->select(['sourceId as source', 'targetId as target']) - ->from([DbTable::RELATIONS]) - ->where([ - 'and', - [ - 'fieldId' => $this->id, - 'sourceId' => $sourceElementIds, - ], - [ - 'or', - ['sourceSiteId' => $sourceSiteId], - ['sourceSiteId' => null], - ], - ]) - ->orderBy(['sortOrder' => SORT_ASC]) - ->all(); + foreach ($sourceElements as $sourceElement) { + $rawValue = $sourceElement->getBehavior('customFields')->{$this->handle} ?? null; + if ($rawValue instanceof ElementQueryInterface) { + $rawValue = $rawValue->where['elements.id'] ?? null; + } + if (is_array($rawValue)) { + foreach ($rawValue as $targetElementId) { + $map[] = ['source' => $sourceElement->id, 'target' => $targetElementId]; + } + } elseif ($this->isFirstInstance($sourceElement)) { + // The relation IDs aren't hardcoded yet and this is the first + // instance of this field in the field layout, so fetch the relations + // via the DB table + $missingSourceElementIds[] = $sourceElement->id; + } + } + + // Are there any source elements that don't have hardcoded relation IDs yet? + if (!empty($missingSourceElementIds)) { + $missingMappingsQuery = (new Query()) + ->select(['sourceId as source', 'targetId as target']) + ->from([DbTable::RELATIONS]) + ->where([ + 'and', + [ + 'fieldId' => $this->id, + 'sourceId' => $missingSourceElementIds, + ], + [ + 'or', + ['sourceSiteId' => $sourceSiteId], + ['sourceSiteId' => null], + ], + ]) + ->orderBy(['sortOrder' => SORT_ASC]); + array_push($map, ...$missingMappingsQuery->all()); + } $criteria = [];