diff --git a/app/Classes/Link.php b/app/Classes/Link.php index 332f7b2..b4a3832 100644 --- a/app/Classes/Link.php +++ b/app/Classes/Link.php @@ -19,7 +19,7 @@ class Link private bool $isDead; - private string $reason; + private ?string $reason; private Carbon $checked; @@ -29,7 +29,7 @@ class Link private string $title; - public function __construct(protected string $url, protected ?string $innerHtml = null) + public function __construct(protected string $url, protected ?string $innerHtml, protected ?Bundle $bundle = null) { } @@ -138,19 +138,57 @@ public function title(): string return $this->title; } + public function toArray(): array + { + if ($this->bundle !== null) { + $existingData = $this->bundle->metadata('links')->get($this->url, []); + + if (!empty($existingData['checked']) && Carbon::parse($existingData['checked'])->gt(now()->subMonth())) { + return $existingData; + } + } + + $cacheKey = sprintf('link_%s', Str::slug($this->url)); + + if (Cache::has($cacheKey)) { + $data = Cache::get($cacheKey); + } else { + $data = [ + 'isAnchor' => $this->isAnchor(), + 'isExternal' => $this->isExternal(), + 'isDead' => $this->isDead(), + 'reason' => $this->reason(), + 'checked' => $this->checked(), + 'partner' => $this->partner() ? get_class($this->partner()) : null, + 'finalUrl' => $this->finalUrl(), + 'title' => $this->title(), + 'rel' => implode(' ', $this->fetchRel()), + 'classes' => implode(' ', $this->fetchCssClasses()), + ]; + } + + if ($this->bundle !== null) { + $this->bundle->metadata('links')->set($this->url, $data, false); + $this->bundle->metadata('links')->save(); + } + + Cache::put($cacheKey, $data, now()->addWeek()); + + return $data; + } + /** * Return the link as a HtmlElement */ public function toHtmlElement(): HtmlElement { - $rel = $this->fetchRel(); - $classes = $this->fetchCssClasses(); + $data = $this->toArray(); return new HtmlElement('a', [ - 'href' => $this->finalUrl(), - 'rel' => implode(' ', $rel), - 'title' => $this->title(), - 'class' => implode(' ', $classes), + 'href' => $data['finalUrl'], + 'rel' => $data['rel'], + 'title' => $data['title'], + 'class' => $data['classes'], ], $this->innerHtml); } @@ -159,40 +197,46 @@ public function toHtmlElement(): HtmlElement */ private function fetchIsDead() { - $cacheKey = sprintf('link_status_%s', md5($this->url)); + if ($this->isAnchor()) { + $isDead = false; + $reason = null; + $checked = now(); + } elseif (!$this->isExternal()) { + $isDead = false; + $reason = null; + $checked = now(); - if (Cache::has($cacheKey)) { - return Cache::get($cacheKey); - } + $bundle = new Bundle($this->url, Storage::disk(env('CONTENT_DISK'))); - $isDead = true; - $reason = null; - $checked = now(); + $isDead = !$bundle->exists(); + } else { + $isDead = true; + $reason = null; + $checked = now(); - foreach (['head', 'get'] as $method) { - try { - $result = Http::{$method}($this->url); + foreach (['head', 'get'] as $method) { + try { + $result = Http::timeout(10)->{$method}($this->url); - if ($result->ok()) { - $isDead = false; - break; + if ($result->ok()) { + $isDead = false; + break; + } + + if ($result->status() === 403) { + $isDead = false; + break; + } + + $reason = strval($result->status()); + } catch (Exception $ex) { + $reason = $ex->getMessage(); } - - if ($result->status() === 403) { - $isDead = false; - break; - } - - $reason = strval($result->status()); - } catch (Exception $ex) { - $reason = $ex->getMessage(); } } $result = compact('isDead', 'reason', 'checked'); - Cache::put($cacheKey, $result, now()->addMonth()); - return $result; } diff --git a/app/Classes/MarkdownManager.php b/app/Classes/MarkdownManager.php index 6b90a71..bbfd93e 100644 --- a/app/Classes/MarkdownManager.php +++ b/app/Classes/MarkdownManager.php @@ -65,7 +65,7 @@ public function get(): ?string public function render() { $content = $this->bundle->replaceAttachmentsInMarkdown($this->content ?? ''); - $formatter = new Formatter($content); + $formatter = new Formatter($content, $this->bundle); $result = $formatter->render(); return $result; diff --git a/app/Classes/MetadataManager.php b/app/Classes/MetadataManager.php index 095d310..397c7d6 100644 --- a/app/Classes/MetadataManager.php +++ b/app/Classes/MetadataManager.php @@ -82,9 +82,13 @@ public function save(): bool * @param mixed $key The key under which to store the value. * @param mixed $value The value to store. */ - public function set($key, $value) + public function set($key, $value, bool $respectDots = true) { - data_set($this->content, $key, $value, true); + if ($respectDots) { + data_set($this->content, $key, $value, true); + } else { + $this->content[$key] = $value; + } } /** diff --git a/app/Services/Markdown/Formatter.php b/app/Services/Markdown/Formatter.php index 375a09f..d11d443 100644 --- a/app/Services/Markdown/Formatter.php +++ b/app/Services/Markdown/Formatter.php @@ -2,6 +2,7 @@ namespace App\Services\Markdown; +use App\Classes\Bundle; use App\Services\Markdown\Renderers\LinkRenderer; use DOMDocument; use Illuminate\Support\Facades\Blade; @@ -32,7 +33,7 @@ class Formatter * * @param string $source The markdown source content. */ - public function __construct(protected string $source) + public function __construct(protected string $source, protected ?Bundle $bundle = null) { } @@ -101,7 +102,7 @@ protected function prepareEnvironment(): Environment } // Use a custom renderer for links - $environment->addRenderer(Link::class, new LinkRenderer()); + $environment->addRenderer(Link::class, new LinkRenderer($this->bundle)); return $environment; } diff --git a/app/Services/Markdown/Renderers/LinkRenderer.php b/app/Services/Markdown/Renderers/LinkRenderer.php index e7709c6..721645c 100644 --- a/app/Services/Markdown/Renderers/LinkRenderer.php +++ b/app/Services/Markdown/Renderers/LinkRenderer.php @@ -2,6 +2,7 @@ namespace App\Services\Markdown\Renderers; +use App\Classes\Bundle; use App\Classes\Link; use League\CommonMark\Extension\CommonMark\Node\Inline\Link as CommonMarkLink; use League\CommonMark\Node\Node; @@ -11,6 +12,10 @@ class LinkRenderer implements NodeRendererInterface { + public function __construct(protected ?Bundle $bundle = null) + { + } + /** * Renders a link node into an HTML element. */ @@ -21,6 +26,6 @@ public function render(Node $node, ChildNodeRendererInterface $childRenderer): H $innerHtml = $childRenderer->renderNodes($node->children()); $url = $node->getUrl(); - return (new Link($url, $innerHtml))->toHtmlElement(); + return (new Link($url, $innerHtml, $this->bundle))->toHtmlElement(); } }