diff --git a/CHANGELOG-WIP.md b/CHANGELOG-WIP.md index e6a534b26e9..7eeac69368f 100644 --- a/CHANGELOG-WIP.md +++ b/CHANGELOG-WIP.md @@ -5,3 +5,9 @@ ### Development - The `{% js %}` and `{% css %}` tags now support `.js.gz` and `.css.gz` URLs. ([#14243](https://github.com/craftcms/cms/issues/14243)) + +### Extensibility +- Added `craft\services\Relations::deleteLeftoverRelations()`. ([#13956](https://github.com/craftcms/cms/issues/13956)) + +### System +- Relations for fields that are no longer included in an element’s field layout are now deleted after element save. ([#13956](https://github.com/craftcms/cms/issues/13956)) diff --git a/src/base/Element.php b/src/base/Element.php index 30ed42beddc..542dfa9c50a 100644 --- a/src/base/Element.php +++ b/src/base/Element.php @@ -5182,6 +5182,11 @@ public function afterPropagate(bool $isNew): void $field->afterElementPropagate($this, $isNew); } + // Delete relations that don’t belong to a relational field on the element's field layout + if (!ElementHelper::isDraftOrRevision($this)) { + Craft::$app->getRelations()->deleteLeftoverRelations($this); + } + // Trigger an 'afterPropagate' event if ($this->hasEventHandlers(self::EVENT_AFTER_PROPAGATE)) { $this->trigger(self::EVENT_AFTER_PROPAGATE, new ModelEvent([ diff --git a/src/services/Relations.php b/src/services/Relations.php index eb89006903e..086733296b1 100644 --- a/src/services/Relations.php +++ b/src/services/Relations.php @@ -12,6 +12,7 @@ use craft\db\Command; use craft\db\Query; use craft\db\Table; +use craft\fieldlayoutelements\CustomField; use craft\fields\BaseRelationField; use craft\helpers\Db; use Throwable; @@ -125,4 +126,51 @@ public function saveRelations(BaseRelationField $field, ElementInterface $source } } } + + /** + * Deletes relations that don’t belong to a relational field on the given element’s field layout. + * + * @param ElementInterface $element + * @since 4.8.0 + */ + public function deleteLeftoverRelations(ElementInterface $element): void + { + if (!$element->id) { + return; + } + + $fieldLayout = $element->getFieldLayout(); + if (!$fieldLayout) { + return; + } + + $relationFieldIds = []; + foreach ($fieldLayout->getTabs() as $tab) { + foreach ($tab->getElements() as $layoutElement) { + if ($layoutElement instanceof CustomField) { + $field = $layoutElement->getField(); + if ($field instanceof BaseRelationField) { + $relationFieldIds[] = $field->id; + } + } + } + } + + // get those relations for the element that don't belong to any relational fields that are in the layout + $query = (new Query()) + ->select(['id']) + ->from(Table::RELATIONS) + ->where(['sourceId' => $element->id]); + + if (!empty($relationFieldIds)) { + $query->andWhere(['not', ['fieldId' => $relationFieldIds]]); + } + + $leftoverRelationIds = $query->column(); + + // if relations were returned - delete them + if (!empty($leftoverRelationIds)) { + Db::delete(Table::RELATIONS, ['id' => $leftoverRelationIds]); + } + } }