$this->fromAttachments($article), 'from_content', 'content_image' => $this->fromContent($article), 'generate' => $this->generator->generate($article), default => $this->fromContent($article) ?? $this->fromAttachments($article), }; } public function apply(Article $article, string $strategy = 'auto'): Article { $article->forceFill([ 'cover_status' => self::STATUS_PENDING, ])->save(); try { $resolved = $this->resolve($article, $strategy); } catch (\Throwable $exception) { $article->forceFill([ 'cover_status' => self::STATUS_FAILED, 'cover_source' => $strategy === 'generate' ? self::SOURCE_GENERATED : self::SOURCE_NONE, 'cover_generated_at' => now(), ])->save(); throw $exception; } if ($resolved === null) { $article->forceFill([ 'cover_disk' => null, 'cover_path' => null, 'cover_source' => self::SOURCE_NONE, 'cover_status' => self::STATUS_FAILED, 'cover_generated_at' => now(), ])->save(); return $article->refresh(); } $article->forceFill([ 'cover_disk' => $resolved['disk'], 'cover_path' => $resolved['path'], 'cover_source' => $resolved['source'], 'cover_status' => self::STATUS_READY, 'cover_generated_at' => now(), ])->save(); return $article->refresh(); } public function publicUrl(Article $article): ?string { if (! $article->hasCover()) { return null; } $path = (string) $article->cover_path; if ($article->cover_disk === 'url' || str_starts_with($path, 'http://') || str_starts_with($path, 'https://')) { return $path; } $disk = (string) ($article->cover_disk ?: config('larablog.attachments_disk', 'attachments')); try { return Storage::disk($disk)->url($path); } catch (\Throwable) { return url('/'.$path); } } /** * @return array{disk: ?string, path: string, source: string}|null */ protected function fromContent(Article $article): ?array { $content = (string) $article->content; if (preg_match('/!\[[^\]]*\]\(attach:(\d+)\)/i', $content, $matches) === 1 || preg_match('/\[attach=(\d+)\]/i', $content, $matches) === 1) { $attachment = Attachment::query()->find((int) $matches[1]); if ($attachment !== null && $this->isImage($attachment)) { return [ 'disk' => $attachment->disk, 'path' => $attachment->path, 'source' => self::SOURCE_CONTENT_IMAGE, ]; } } if (preg_match('/!\[[^\]]*\]\((https?:\/\/[^)\s]+)\)/i', $content, $matches) === 1) { return [ 'disk' => 'url', 'path' => $matches[1], 'source' => self::SOURCE_CONTENT_IMAGE, ]; } if (preg_match('/]+src=["\'](https?:\/\/[^"\']+)["\']/i', $content, $matches) === 1) { return [ 'disk' => 'url', 'path' => $matches[1], 'source' => self::SOURCE_CONTENT_IMAGE, ]; } return null; } /** * @return array{disk: ?string, path: string, source: string}|null */ protected function fromAttachments(Article $article): ?array { $attachment = $article->attachments() ->orderBy('id') ->get() ->first(fn (Attachment $item): bool => $this->isImage($item)); if ($attachment === null) { return null; } return [ 'disk' => $attachment->disk, 'path' => $attachment->path, 'source' => self::SOURCE_ATTACHMENT, ]; } protected function isImage(Attachment $attachment): bool { if (is_string($attachment->mime) && str_starts_with($attachment->mime, 'image/')) { return true; } $ext = strtolower(pathinfo((string) $attachment->filename, PATHINFO_EXTENSION)); return in_array($ext, ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'svg'], true); } }