. */ declare(strict_types=1); namespace Fisharebest\Webtrees\Elements; use Fisharebest\Webtrees\Contracts\ElementInterface; use Fisharebest\Webtrees\Html; use Fisharebest\Webtrees\I18N; use Fisharebest\Webtrees\Tree; use League\CommonMark\Block\Element\Document; use League\CommonMark\Block\Element\Paragraph; use League\CommonMark\Block\Renderer\DocumentRenderer; use League\CommonMark\Block\Renderer\ParagraphRenderer; use League\CommonMark\CommonMarkConverter; use League\CommonMark\Environment; use League\CommonMark\Inline\Element\Link; use League\CommonMark\Inline\Element\Text; use League\CommonMark\Inline\Parser\AutolinkParser; use League\CommonMark\Inline\Renderer\LinkRenderer; use League\CommonMark\Inline\Renderer\TextRenderer; use function array_key_exists; use function array_map; use function e; use function is_numeric; use function preg_replace; use function strpos; use function trim; use function view; /** * A GEDCOM element is a tag/primitive in a GEDCOM file. */ abstract class AbstractElement implements ElementInterface { protected const REGEX_URL = '~((https?|ftp]):)(//([^\s/?#<>]*))?([^\s?#<>]*)(\?([^\s#<>]*))?(#[^\s?#<>]+)?~'; // HTML attributes for an protected const MAX_LENGTH = false; // Which child elements can appear under this element. protected const SUBTAGS = []; /** @var string A label to describe this element */ private $label; /** @var array */ private $subtags; /** * AbstractGedcomElement constructor. * * @param string $label * @param array|null $subtags */ public function __construct(string $label, array $subtags = null) { $this->label = $label; $this->subtags = $subtags ?? static::SUBTAGS; } /** * Convert a value to a canonical form. * * @param string $value * * @return string */ public function canonical(string $value): string { $value = strtr($value, ["\t" => ' ', "\r" => ' ', "\n" => ' ']); while (strpos($value, ' ') !== false) { $value = strtr($value, [' ' => ' ']); } return trim($value); } /** * Create a default value for this element. * * @param Tree $tree * * @return string */ public function default(Tree $tree): string { return ''; } /** * An edit control for this data. * * @param string $id * @param string $name * @param string $value * @param Tree $tree * * @return string */ public function edit(string $id, string $name, string $value, Tree $tree): string { $values = $this->values(); if ($values !== []) { $value = $this->canonical($value); // Ensure the current data is in the list. if (!array_key_exists($value, $values)) { $values = [$value => $value] + $values; } // We may use markup to display values, but not when editing them. $values = array_map('strip_tags', $values); return view('components/select', [ 'id' => $id, 'name' => $name, 'options' => $values, 'selected' => $value, ]); } $attributes = [ 'class' => 'form-control', 'type' => 'text', 'id' => $id, 'name' => $name, 'value' => $value, 'maxvalue' => static::MAX_LENGTH, ]; return ''; } /** * An edit control for this data. * * @param string $id * @param string $name * @param string $value * * @return string */ public function editHidden(string $id, string $name, string $value): string { return ''; } /** * An edit control for this data. * * @param string $id * @param string $name * @param string $value * * @return string */ public function editTextArea(string $id, string $name, string $value): string { return ''; } /** * Escape @ signs in a GEDCOM export. * * @param string $value * * @return string */ public function escape(string $value): string { return strtr($value, ['@' => '@@']); } /** * Create a label for this element. * * @return string */ public function label(): string { return $this->label; } /** * Create a label/value pair for this element. * * @param string $value * @param Tree $tree * * @return string */ public function labelValue(string $value, Tree $tree): string { $label = '' . $this->label() . ''; $value = '' . $this->value($value, $tree) . ''; $html = I18N::translate(/* I18N: e.g. "Occupation: farmer" */ '%1$s: %2$s', $label, $value); return '
' . $html . '
'; } /** * @param Tree $tree * * @return array */ public function subtags(Tree $tree): array { return $this->subtags; } /** * Display the value of this type of element. * * @param string $value * @param Tree $tree * * @return string */ public function value(string $value, Tree $tree): string { $values = $this->values(); if ($values === []) { if (str_contains($value, "\n")) { return '' . e($value) . ''; } return '' . e($value) . ''; } $canonical = $this->canonical($value); return $values[$canonical] ?? '' . e($value) . ''; } /** * A list of controlled values for this element * * @return array */ public function values(): array { return []; } /** * Display the value of this type of element - convert URLs to links * * @param string $value * * @return string */ protected function valueAutoLink(string $value): string { // Convert URLs into markdown auto-links. $value = preg_replace(self::REGEX_URL, '<$0>', $value); // Create a minimal commonmark processor - just add support for autolinks. $environment = new Environment(); $environment ->addBlockRenderer(Document::class, new DocumentRenderer()) ->addBlockRenderer(Paragraph::class, new ParagraphRenderer()) ->addInlineRenderer(Text::class, new TextRenderer()) ->addInlineRenderer(Link::class, new LinkRenderer()) ->addInlineParser(new AutolinkParser()); $converter = new CommonMarkConverter(['html_input' => Environment::HTML_INPUT_ESCAPE], $environment); return $converter->convertToHtml($value); } /** * Display the value of this type of element. * * @param string $value * * @return string */ public function valueNumeric(string $value): string { $canonical = $this->canonical($value); if (is_numeric($canonical)) { return I18N::number((int) $canonical); } return e($value); } }