wasChanged('slug')) { return; } $model->recordSlugChange(); }); } protected function recordSlugChange(): void { $type = $this->publicPathType(); $previous = (array) json_decode((string) json_encode($this->getOriginal('slug')), true); $current = (array) $this->getTranslations('slug'); foreach (array_keys((array) config('locales.supported')) as $locale) { $was = (string) ($previous[$locale] ?? ''); $now = (string) ($current[$locale] ?? ''); // Nothing to redirect from when the record had no address in this // language, and nowhere to redirect to when it no longer has one. if (blank($was) || blank($now) || $was === $now) { continue; } $from = Redirect::normalise(SiteUrls::path($type, $locale, $was)); $to = Redirect::normalise(SiteUrls::path($type, $locale, $now)); if ($from === $to) { continue; } $this->writeRedirect($from, $to, $locale); } } protected function writeRedirect(string $from, string $to, string $locale): void { $scope = fn () => Redirect::query()->where('locale', $locale); // The new address resolves to this record again, so any rule pointing // away from it is now wrong — this is what renaming something back to // its original slug looks like. $scope()->where('from', $to)->delete(); // Whoever pointed at the old address now points at the new one, so a // frontend never has to follow two hops. $scope()->where('to', $from)->update(['to' => $to]); $redirect = Redirect::query()->firstOrNew(['from' => $from, 'locale' => $locale]); // `source` is set here rather than through mass assignment: it records // what the application did, not anything somebody submitted. $redirect->forceFill([ 'to' => $to, 'status' => 301, 'is_enabled' => true, 'source_type' => $this->getMorphClass(), 'source_id' => $this->getKey(), ])->save(); // Belt and braces: a rule pointing at its own address would send a // browser round for ever. Redirect::query()->whereColumn('from', 'to')->delete(); } }