From c6edb411349fea6f158fef71245615fd3d3f7d0a Mon Sep 17 00:00:00 2001 From: Samuel Georges Date: Tue, 20 Feb 2024 17:58:42 +1100 Subject: [PATCH] Detect and preserve shared multisite relationships --- src/Database/Concerns/HasRelationships.php | 51 +++++++++++++--------- src/Database/Traits/Multisite.php | 26 ++++++++++- 2 files changed, 55 insertions(+), 22 deletions(-) diff --git a/src/Database/Concerns/HasRelationships.php b/src/Database/Concerns/HasRelationships.php index 60f132402..6e65fb53f 100644 --- a/src/Database/Concerns/HasRelationships.php +++ b/src/Database/Concerns/HasRelationships.php @@ -876,42 +876,51 @@ protected function setRelationSimpleValue($relationName, $value) } /** - * performDeleteOnRelations locates relations with delete flag and cascades - * the delete event. + * performDeleteOnRelations locates relations with delete flag and cascades the + * delete event. This is called before the parent model is deleted. This method + * checks in with the Multisite trait to preserve shared relations. + * + * @see \October\Rain\Database\Traits\Multisite::canDeleteMultisiteRelation */ protected function performDeleteOnRelations() { $definitions = $this->getRelationDefinitions(); + $useMultisite = $this->isClassInstanceOf(\October\Contracts\Database\MultisiteInterface::class) && $this->isMultisiteEnabled(); + foreach ($definitions as $type => $relations) { - // Hard 'delete' definition foreach ($relations as $name => $options) { - if (!Arr::get($options, 'delete', false)) { - continue; - } - - if (!$relation = $this->{$name}) { + // Detect and preserve shared multisite relationships + if ($useMultisite && !$this->canDeleteMultisiteRelation($name, $type)) { continue; } - if ($relation instanceof EloquentModel) { - $relation->forceDelete(); - } - elseif ($relation instanceof CollectionBase) { - $relation->each(function ($model) { - $model->forceDelete(); - }); - } - } - - // Belongs-To-Many should clean up after itself by default - if ($type === 'belongsToMany') { - foreach ($relations as $name => $options) { + // Belongs-To-Many should clean up after itself by default + if ($type === 'belongsToMany') { if (!Arr::get($options, 'detach', true)) { return; } $this->{$name}()->detach(); } + // Hard 'delete' definition + else { + if (!Arr::get($options, 'delete', false)) { + continue; + } + + if (!$relation = $this->{$name}) { + continue; + } + + if ($relation instanceof EloquentModel) { + $relation->forceDelete(); + } + elseif ($relation instanceof CollectionBase) { + $relation->each(function ($model) { + $model->forceDelete(); + }); + } + } } } } diff --git a/src/Database/Traits/Multisite.php b/src/Database/Traits/Multisite.php index 815702078..b25fc9450 100644 --- a/src/Database/Traits/Multisite.php +++ b/src/Database/Traits/Multisite.php @@ -158,7 +158,31 @@ protected function defineMultisiteRelations() } /** - * defineMultisiteRelation + * canDeleteMultisiteRelation checks if a relation has the potential to be shared with + * the current model. If there are 2 or more records in existence, then this method + * will prevent the cascading deletion of relations. + * + * @see \October\Rain\Database\Concerns\HasRelationships::performDeleteOnRelations + */ + public function canDeleteMultisiteRelation($name, $type = null): bool + { + if ($type === null) { + $type = $this->getRelationType($name); + } + + if (!in_array($type, ['belongsToMany', 'belongsTo', 'hasOne', 'hasMany', 'attachOne', 'attachMany'])) { + return false; + } + + // The current record counts for one so halt if we find more + return !($this->newOtherSiteQuery()->count() > 1); + } + + /** + * defineMultisiteRelation will modify defined relations on this model so they share + * their association using the shared identifier (`site_root_id`). Only these relation + * types support relation sharing: `belongsToMany`, `belongsTo`, `hasOne`, `hasMany`, + * `attachOne`, `attachMany`. */ protected function defineMultisiteRelation($name, $type = null) {