1
0
cms11/app/Classes/Link.php

313 lines
7.1 KiB
PHP

<?php
namespace App\Classes;
use App\Exceptions\PartnerCannotBeFound;
use App\Services\Partners\Contracts\Partner;
use App\Services\Partners\Facades\Partner as PartnerFactory;
use Carbon\Carbon;
use Exception;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use League\CommonMark\Util\HtmlElement;
class Link
{
private bool $isExternal;
private bool $isDead;
private string $reason;
private Carbon $checked;
private ?Partner $partner;
private string $finalUrl;
private string $title;
public function __construct(protected string $url, protected ?string $innerHtml = null)
{
}
/**
* Return a boolean value indicating if specified url is an anchor
*/
public function isAnchor(): bool
{
return Str::startsWith($this->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 {
if (Http::throw()->{$method}($this->url)->ok()) {
$isDead = false;
break;
}
} 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()
);
}
$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->metadata()->get('title');
$section = $bundle->getSection();
if (!empty($date)) {
return sprintf(
'Lien interne : [%s] %s | Publié le %s',
$section,
$title,
Carbon::parse($date)->format('d/m/Y')
);
} else {
return sprintf(
'Lien interne : [%s] %s',
$section,
$title,
);
}
}
}