url, '#'); } /** * Return a boolean value indicating if the URL is either internal or * external to current website */ public function isExternal(): bool { if (!isset($this->isExternal)) { $this->isExternal = Str::startsWith($this->url, [ 'http://', 'https://', ]); } return $this->isExternal; } /** * Return a boolean value indicating if URL can be reached */ public function isDead(): bool { if (!isset($this->isDead)) { $this->isDead = $this->fetchIsDead()['isDead']; } return $this->isDead; } /** * If URL cannot be reached, return the reason why */ public function reason(): ?string { if (!isset($this->reason)) { $this->reason = $this->fetchIsDead()['reason'] ?? null; } return $this->reason; } /** * Return the last date/time the URL was checked */ public function checked(): ?Carbon { if (!isset($this->checked)) { $this->checked = $this->fetchIsDead()['checked'] ?? null; } return $this->checked; } /** * Return a partner associated with URL, if any */ public function partner(): ?Partner { if (!isset($this->partner)) { if (!$this->isExternal()) { $this->partner = null; } else { try { $this->partner = PartnerFactory::getPartner($this->url); } catch (PartnerCannotBeFound $ex) { $this->partner = null; } } } return $this->partner; } /** * Return the final URL of the link, after any kind of modifications we can * make */ public function finalUrl(): string { if (!isset($this->finalUrl)) { $this->finalUrl = $this->fetchFinalUrl(); } return $this->finalUrl; } /** * Return a suitable title for the link */ public function title(): string { if (!isset($this->title)) { $this->title = $this->fetchTitle(); } return $this->title; } /** * Return the link as a HtmlElement */ public function toHtmlElement(): HtmlElement { $rel = $this->fetchRel(); $classes = $this->fetchCssClasses(); return new HtmlElement('a', [ 'href' => $this->finalUrl(), 'rel' => implode(' ', $rel), 'title' => $this->title(), 'class' => implode(' ', $classes), ], $this->innerHtml); } /** * Tries HEAD and GET requests to find out if URL can be reached */ private function fetchIsDead() { $cacheKey = sprintf('link_status_%s', md5($this->url)); if (Cache::has($cacheKey)) { return Cache::get($cacheKey); } $isDead = true; $reason = null; $checked = now(); foreach (['head', 'get'] as $method) { try { $result = Http::{$method}($this->url); if ($result->ok()) { $isDead = false; break; } 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; } /** * Return the final URL */ private function fetchFinalUrl(): string { if ($this->isAnchor()) { return $this->url; } if (!empty($this->partner())) { return $this->partner()->getAffiliateLink(); } return $this->url; } /** * Return an array of appropriate rel attributes */ private function fetchRel(): array { $rel = []; if (!empty($this->partner())) { $rel[] = 'nofollow'; } elseif ($this->isExternal()) { $rel = [ 'nofollow', 'noreferrer', 'noopener', ]; } return $rel; } /** * Return applicable CSS classes */ private function fetchCssClasses(): array { $classes = []; if ($this->isExternal()) { $classes = [ config('markdown.external_link.html_class'), ]; if (!empty($this->partner())) { $classes[] = 'affiliate'; } if ($this->isDead()) { $classes[] = 'dead'; } } return $classes; } /** * Return a suitable title for this link */ private function fetchTitle(): string { if ($this->isAnchor()) { return sprintf('Lien direct vers %s', $this->url); } if ($this->isExternal()) { $title = 'Lien '; if (!empty($this->partner())) { $title .= 'affilié '; } else { $title .= 'externe '; } if ($this->isDead()) { $title .= sprintf( ' (mort depuis le %s - %s) ', $this->checked()->format('d/m/Y'), $this->reason() ); } else { $title .= sprintf( ' (vérifié le %s) ', $this->checked()->format('d/m/Y') ); } $title .= sprintf(' : %s', $this->url); return $title; } else { return $this->fetchBundleTitle(); } } /** * Build link title from internal bundle */ private function fetchBundleTitle(): string { $bundle = new Bundle($this->url, Storage::disk(env('CONTENT_DISK'))); $bundle->load(); $date = $bundle->metadata()->get('date'); $title = $bundle->getArticleTitle(); $section = $bundle->getSection(); if (!empty($date)) { return sprintf( 'Lien interne : [%s] %s | Publié le %s', $section->getArticleTitle(), $title, Carbon::parse($date)->format('d/m/Y') ); } else { return sprintf( 'Lien interne : [%s] %s', $section->getArticleTitle(), $title, ); } } }