79 lines
3.0 KiB
PHP
79 lines
3.0 KiB
PHP
<?php
|
|
|
|
namespace App\Actions\Tickets\Support;
|
|
|
|
use League\CommonMark\Environment\Environment;
|
|
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
|
|
use League\CommonMark\Extension\CommonMark\Node\Block\HtmlBlock;
|
|
use League\CommonMark\Extension\CommonMark\Node\Inline\HtmlInline;
|
|
use League\CommonMark\Extension\CommonMark\Node\Inline\Image;
|
|
use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
|
|
use League\CommonMark\Extension\GithubFlavoredMarkdownExtension;
|
|
use League\CommonMark\Extension\Table\Table;
|
|
use League\CommonMark\Extension\TaskList\TaskListItemMarker;
|
|
use League\CommonMark\Parser\MarkdownParser;
|
|
use League\Uri\Uri;
|
|
|
|
class TicketDescriptionFormat
|
|
{
|
|
/** @return array<string, mixed> */
|
|
public static function contract(): array
|
|
{
|
|
return once(fn (): array => json_decode(file_get_contents(resource_path('js/lib/ticket-description-format.json')), true, flags: JSON_THROW_ON_ERROR));
|
|
}
|
|
|
|
public static function instructions(): string
|
|
{
|
|
$contract = self::contract();
|
|
|
|
return implode(' ', array_map(fn (string $key): string => $contract[$key], ['format', 'supported', 'unsupported', 'links', 'criteria']))
|
|
." Valid description example (Markdown string):\n".$contract['example'];
|
|
}
|
|
|
|
public static function criteriaInstructions(): string
|
|
{
|
|
return self::contract()['criteria'];
|
|
}
|
|
|
|
public static function safeLink(string $value): bool
|
|
{
|
|
$contract = self::contract();
|
|
$decoded = rawurldecode($value);
|
|
if (! mb_check_encoding($decoded, 'UTF-8') || preg_match('~'.$contract['unsafe_link_characters'].'~u', $decoded)
|
|
|| ! preg_match('~'.$contract['link_prefix'].'~i', $value)) {
|
|
return false;
|
|
}
|
|
if (str_starts_with($value, '/') || str_starts_with($value, '#')) {
|
|
return true;
|
|
}
|
|
$url = Uri::tryNew($decoded);
|
|
|
|
return $url !== null && ($url->getScheme() === 'mailto' || (filled($url->getHost()) && ($url->getPort() ?? 0) <= 65535));
|
|
}
|
|
|
|
/** @return list<string> */
|
|
public static function unsupportedContent(string $value): array
|
|
{
|
|
$environment = new Environment;
|
|
$environment->addExtension(new CommonMarkCoreExtension);
|
|
$environment->addExtension(new GithubFlavoredMarkdownExtension);
|
|
$document = (new MarkdownParser($environment))->parse($value);
|
|
$unsupported = [];
|
|
foreach ($document->iterator() as $node) {
|
|
$type = match (true) {
|
|
$node instanceof HtmlBlock, $node instanceof HtmlInline => 'html',
|
|
$node instanceof Table => 'table',
|
|
$node instanceof Image => 'image',
|
|
$node instanceof TaskListItemMarker => 'task',
|
|
$node instanceof Link && ! self::safeLink($node->getUrl()) => 'unsafe_link',
|
|
default => null,
|
|
};
|
|
if ($type !== null) {
|
|
$unsupported[] = self::contract()['unsupported_tokens'][$type];
|
|
}
|
|
}
|
|
|
|
return array_values(array_unique($unsupported));
|
|
}
|
|
}
|