1
0

Store checked links in bundle metadata for the long term and cache for the middle term

This commit is contained in:
Richard Dern 2024-04-24 11:39:23 +02:00
parent 7e53a39638
commit 11fc9262bf
5 changed files with 92 additions and 38 deletions

View File

@ -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;
}

View File

@ -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;

View File

@ -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;
}
}
/**

View File

@ -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;
}

View File

@ -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();
}
}