*/ use BumpsContentVersion, HasFactory; protected function casts(): array { return [ 'status' => 'integer', 'is_enabled' => 'boolean', 'hits' => 'integer', 'last_used_at' => 'datetime', ]; } protected static function booted(): void { // A reported 404 that now has a destination is dealt with, however the // rule came to exist: written from the list of missing URLs, typed by // hand, or left behind by a slug rename. Hooked here rather than in // the panel action so no route can create a redirect and leave the // address it answers still sitting in the list of things to do. static::created(fn (Redirect $redirect) => MissingUrl::resolvedBy($redirect)); static::updated(function (Redirect $redirect): void { if ($redirect->wasChanged(['from', 'locale'])) { MissingUrl::resolvedBy($redirect); } }); } /** * What changed its address, when a record did. * * @return MorphTo */ public function source(): MorphTo { return $this->morphTo(); } public function wasAutomatic(): bool { return $this->source_type !== null; } /** * Leading slash, no trailing one, no query string: the shape the frontend * compares against, normalised on the way in so a rule typed by hand * matches one created automatically. */ public static function normalise(string $path): string { $path = trim(parse_url($path, PHP_URL_PATH) ?: $path); $path = '/'.trim($path, '/'); return $path === '/' ? '/' : rtrim($path, '/'); } /** * @param Builder<$this> $query * @return Builder<$this> */ public function scopeEnabled(Builder $query): Builder { return $query->where('is_enabled', true); } /** * Record a use, cheaply. * * Worth having because it answers the question that decides whether a * redirect can ever be removed: is anybody still following it? */ public function markUsed(): void { $this->newQuery()->whereKey($this->getKey())->update([ 'hits' => $this->hits + 1, 'last_used_at' => now(), ]); } /** * Whether this rule would send a visitor round in circles. */ public function isCircular(): bool { return Str::lower($this->from) === Str::lower($this->to); } }