1c2ed51d1SGreg Roach<?php 2c2ed51d1SGreg Roach 3c2ed51d1SGreg Roach/** 4c2ed51d1SGreg Roach * webtrees: online genealogy 5c2ed51d1SGreg Roach * Copyright (C) 2021 webtrees development team 6c2ed51d1SGreg Roach * This program is free software: you can redistribute it and/or modify 7c2ed51d1SGreg Roach * it under the terms of the GNU General Public License as published by 8c2ed51d1SGreg Roach * the Free Software Foundation, either version 3 of the License, or 9c2ed51d1SGreg Roach * (at your option) any later version. 10c2ed51d1SGreg Roach * This program is distributed in the hope that it will be useful, 11c2ed51d1SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of 12c2ed51d1SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13c2ed51d1SGreg Roach * GNU General Public License for more details. 14c2ed51d1SGreg Roach * You should have received a copy of the GNU General Public License 15c2ed51d1SGreg Roach * along with this program. If not, see <https://www.gnu.org/licenses/>. 16c2ed51d1SGreg Roach */ 17c2ed51d1SGreg Roach 18c2ed51d1SGreg Roachdeclare(strict_types=1); 19c2ed51d1SGreg Roach 20c2ed51d1SGreg Roachnamespace Fisharebest\Webtrees\Elements; 21c2ed51d1SGreg Roach 22c2ed51d1SGreg Roachuse Fisharebest\Webtrees\Contracts\ElementInterface; 23c2ed51d1SGreg Roachuse Fisharebest\Webtrees\Html; 24c2ed51d1SGreg Roachuse Fisharebest\Webtrees\I18N; 25c2ed51d1SGreg Roachuse Fisharebest\Webtrees\Tree; 26c2ed51d1SGreg Roachuse League\CommonMark\Block\Element\Document; 27c2ed51d1SGreg Roachuse League\CommonMark\Block\Element\Paragraph; 28c2ed51d1SGreg Roachuse League\CommonMark\Block\Renderer\DocumentRenderer; 29c2ed51d1SGreg Roachuse League\CommonMark\Block\Renderer\ParagraphRenderer; 30c2ed51d1SGreg Roachuse League\CommonMark\CommonMarkConverter; 31c2ed51d1SGreg Roachuse League\CommonMark\Environment; 32c2ed51d1SGreg Roachuse League\CommonMark\Inline\Element\Link; 33c2ed51d1SGreg Roachuse League\CommonMark\Inline\Element\Text; 34c2ed51d1SGreg Roachuse League\CommonMark\Inline\Parser\AutolinkParser; 35c2ed51d1SGreg Roachuse League\CommonMark\Inline\Renderer\LinkRenderer; 36c2ed51d1SGreg Roachuse League\CommonMark\Inline\Renderer\TextRenderer; 37c2ed51d1SGreg Roach 38c2ed51d1SGreg Roachuse function array_key_exists; 39c2ed51d1SGreg Roachuse function array_map; 40c2ed51d1SGreg Roachuse function e; 41c2ed51d1SGreg Roachuse function is_numeric; 42c2ed51d1SGreg Roachuse function preg_replace; 43c2ed51d1SGreg Roachuse function strpos; 44c2ed51d1SGreg Roachuse function trim; 45c2ed51d1SGreg Roachuse function view; 46c2ed51d1SGreg Roach 47c2ed51d1SGreg Roach/** 48c2ed51d1SGreg Roach * A GEDCOM element is a tag/primitive in a GEDCOM file. 49c2ed51d1SGreg Roach */ 50c2ed51d1SGreg Roachabstract class AbstractElement implements ElementInterface 51c2ed51d1SGreg Roach{ 52455a30feSGreg Roach protected const REGEX_URL = '~((https?|ftp]):)(//([^\s/?#<>]*))?([^\s?#<>]*)(\?([^\s#<>]*))?(#[^\s?#<>]+)?~'; 53c2ed51d1SGreg Roach 54c2ed51d1SGreg Roach // HTML attributes for an <input> 55ae0043b7SGreg Roach protected const MAXIMUM_LENGTH = false; 56ae0043b7SGreg Roach protected const PATTERN = false; 57c2ed51d1SGreg Roach 58c2ed51d1SGreg Roach // Which child elements can appear under this element. 59c2ed51d1SGreg Roach protected const SUBTAGS = []; 60c2ed51d1SGreg Roach 61c2ed51d1SGreg Roach /** @var string A label to describe this element */ 62c2ed51d1SGreg Roach private $label; 63c2ed51d1SGreg Roach 64c2ed51d1SGreg Roach /** @var array<string,string> */ 65c2ed51d1SGreg Roach private $subtags; 66c2ed51d1SGreg Roach 67c2ed51d1SGreg Roach /** 68c2ed51d1SGreg Roach * AbstractGedcomElement constructor. 69c2ed51d1SGreg Roach * 70c2ed51d1SGreg Roach * @param string $label 71c2ed51d1SGreg Roach * @param array<string>|null $subtags 72c2ed51d1SGreg Roach */ 73c2ed51d1SGreg Roach public function __construct(string $label, array $subtags = null) 74c2ed51d1SGreg Roach { 75c2ed51d1SGreg Roach $this->label = $label; 76c2ed51d1SGreg Roach $this->subtags = $subtags ?? static::SUBTAGS; 77c2ed51d1SGreg Roach } 78c2ed51d1SGreg Roach 79c2ed51d1SGreg Roach /** 80c2ed51d1SGreg Roach * Convert a value to a canonical form. 81c2ed51d1SGreg Roach * 82c2ed51d1SGreg Roach * @param string $value 83c2ed51d1SGreg Roach * 84c2ed51d1SGreg Roach * @return string 85c2ed51d1SGreg Roach */ 86c2ed51d1SGreg Roach public function canonical(string $value): string 87c2ed51d1SGreg Roach { 88c2ed51d1SGreg Roach $value = strtr($value, ["\t" => ' ', "\r" => ' ', "\n" => ' ']); 89c2ed51d1SGreg Roach 90c2ed51d1SGreg Roach while (strpos($value, ' ') !== false) { 91c2ed51d1SGreg Roach $value = strtr($value, [' ' => ' ']); 92c2ed51d1SGreg Roach } 93c2ed51d1SGreg Roach 94c2ed51d1SGreg Roach return trim($value); 95c2ed51d1SGreg Roach } 96c2ed51d1SGreg Roach 97c2ed51d1SGreg Roach /** 98c2ed51d1SGreg Roach * Create a default value for this element. 99c2ed51d1SGreg Roach * 100c2ed51d1SGreg Roach * @param Tree $tree 101c2ed51d1SGreg Roach * 102c2ed51d1SGreg Roach * @return string 103c2ed51d1SGreg Roach */ 104c2ed51d1SGreg Roach public function default(Tree $tree): string 105c2ed51d1SGreg Roach { 106c2ed51d1SGreg Roach return ''; 107c2ed51d1SGreg Roach } 108c2ed51d1SGreg Roach 109c2ed51d1SGreg Roach /** 110c2ed51d1SGreg Roach * An edit control for this data. 111c2ed51d1SGreg Roach * 112c2ed51d1SGreg Roach * @param string $id 113c2ed51d1SGreg Roach * @param string $name 114c2ed51d1SGreg Roach * @param string $value 115c2ed51d1SGreg Roach * @param Tree $tree 116c2ed51d1SGreg Roach * 117c2ed51d1SGreg Roach * @return string 118c2ed51d1SGreg Roach */ 119c2ed51d1SGreg Roach public function edit(string $id, string $name, string $value, Tree $tree): string 120c2ed51d1SGreg Roach { 121c2ed51d1SGreg Roach $values = $this->values(); 122c2ed51d1SGreg Roach 123c2ed51d1SGreg Roach if ($values !== []) { 124c2ed51d1SGreg Roach $value = $this->canonical($value); 125c2ed51d1SGreg Roach 126c2ed51d1SGreg Roach // Ensure the current data is in the list. 127c2ed51d1SGreg Roach if (!array_key_exists($value, $values)) { 128c2ed51d1SGreg Roach $values = [$value => $value] + $values; 129c2ed51d1SGreg Roach } 130c2ed51d1SGreg Roach 131c2ed51d1SGreg Roach // We may use markup to display values, but not when editing them. 132c2ed51d1SGreg Roach $values = array_map('strip_tags', $values); 133c2ed51d1SGreg Roach 134c2ed51d1SGreg Roach return view('components/select', [ 135c2ed51d1SGreg Roach 'id' => $id, 136c2ed51d1SGreg Roach 'name' => $name, 137c2ed51d1SGreg Roach 'options' => $values, 138c2ed51d1SGreg Roach 'selected' => $value, 139c2ed51d1SGreg Roach ]); 140c2ed51d1SGreg Roach } 141c2ed51d1SGreg Roach 142c2ed51d1SGreg Roach $attributes = [ 143c2ed51d1SGreg Roach 'class' => 'form-control', 144c2ed51d1SGreg Roach 'type' => 'text', 145c2ed51d1SGreg Roach 'id' => $id, 146c2ed51d1SGreg Roach 'name' => $name, 147c2ed51d1SGreg Roach 'value' => $value, 148ae0043b7SGreg Roach 'maxvalue' => static::MAXIMUM_LENGTH, 149ae0043b7SGreg Roach 'pattern' => static::PATTERN, 150c2ed51d1SGreg Roach ]; 151c2ed51d1SGreg Roach 152*4a213054SGreg Roach return '<input ' . Html::attributes($attributes) . ' />'; 153c2ed51d1SGreg Roach } 154c2ed51d1SGreg Roach 155c2ed51d1SGreg Roach /** 156c2ed51d1SGreg Roach * An edit control for this data. 157c2ed51d1SGreg Roach * 158c2ed51d1SGreg Roach * @param string $id 159c2ed51d1SGreg Roach * @param string $name 160c2ed51d1SGreg Roach * @param string $value 161c2ed51d1SGreg Roach * 162c2ed51d1SGreg Roach * @return string 163c2ed51d1SGreg Roach */ 164c2ed51d1SGreg Roach public function editHidden(string $id, string $name, string $value): string 165c2ed51d1SGreg Roach { 166*4a213054SGreg Roach return '<input class="form-control" type="hidden" id="' . e($id) . '" name="' . e($name) . '" value="' . e($value) . '" />'; 167c2ed51d1SGreg Roach } 168c2ed51d1SGreg Roach 169c2ed51d1SGreg Roach /** 170c2ed51d1SGreg Roach * An edit control for this data. 171c2ed51d1SGreg Roach * 172c2ed51d1SGreg Roach * @param string $id 173c2ed51d1SGreg Roach * @param string $name 174c2ed51d1SGreg Roach * @param string $value 175c2ed51d1SGreg Roach * 176c2ed51d1SGreg Roach * @return string 177c2ed51d1SGreg Roach */ 178c2ed51d1SGreg Roach public function editTextArea(string $id, string $name, string $value): string 179c2ed51d1SGreg Roach { 180c2ed51d1SGreg Roach return '<textarea class="form-control" id="' . e($id) . '" name="' . e($name) . '" rows="5" dir="auto">' . e($value) . '</textarea>'; 181c2ed51d1SGreg Roach } 182c2ed51d1SGreg Roach 183c2ed51d1SGreg Roach /** 184c2ed51d1SGreg Roach * Escape @ signs in a GEDCOM export. 185c2ed51d1SGreg Roach * 186c2ed51d1SGreg Roach * @param string $value 187c2ed51d1SGreg Roach * 188c2ed51d1SGreg Roach * @return string 189c2ed51d1SGreg Roach */ 190c2ed51d1SGreg Roach public function escape(string $value): string 191c2ed51d1SGreg Roach { 192c2ed51d1SGreg Roach return strtr($value, ['@' => '@@']); 193c2ed51d1SGreg Roach } 194c2ed51d1SGreg Roach 195c2ed51d1SGreg Roach /** 196c2ed51d1SGreg Roach * Create a label for this element. 197c2ed51d1SGreg Roach * 198c2ed51d1SGreg Roach * @return string 199c2ed51d1SGreg Roach */ 200c2ed51d1SGreg Roach public function label(): string 201c2ed51d1SGreg Roach { 202c2ed51d1SGreg Roach return $this->label; 203c2ed51d1SGreg Roach } 204c2ed51d1SGreg Roach 205c2ed51d1SGreg Roach /** 206c2ed51d1SGreg Roach * Create a label/value pair for this element. 207c2ed51d1SGreg Roach * 208c2ed51d1SGreg Roach * @param string $value 209c2ed51d1SGreg Roach * @param Tree $tree 210c2ed51d1SGreg Roach * 211c2ed51d1SGreg Roach * @return string 212c2ed51d1SGreg Roach */ 213c2ed51d1SGreg Roach public function labelValue(string $value, Tree $tree): string 214c2ed51d1SGreg Roach { 215c2ed51d1SGreg Roach $label = '<span class="label">' . $this->label() . '</span>'; 216c2ed51d1SGreg Roach $value = '<span class="value">' . $this->value($value, $tree) . '</span>'; 217c2ed51d1SGreg Roach $html = I18N::translate(/* I18N: e.g. "Occupation: farmer" */ '%1$s: %2$s', $label, $value); 218c2ed51d1SGreg Roach 219c2ed51d1SGreg Roach return '<div>' . $html . '</div>'; 220c2ed51d1SGreg Roach } 221c2ed51d1SGreg Roach 222c2ed51d1SGreg Roach /** 223c2ed51d1SGreg Roach * @return array<string,string> 224c2ed51d1SGreg Roach */ 2253d2c98d1SGreg Roach public function subtags(): array 226c2ed51d1SGreg Roach { 227c2ed51d1SGreg Roach return $this->subtags; 228c2ed51d1SGreg Roach } 229c2ed51d1SGreg Roach 230c2ed51d1SGreg Roach /** 231c2ed51d1SGreg Roach * Display the value of this type of element. 232c2ed51d1SGreg Roach * 233c2ed51d1SGreg Roach * @param string $value 234c2ed51d1SGreg Roach * @param Tree $tree 235c2ed51d1SGreg Roach * 236c2ed51d1SGreg Roach * @return string 237c2ed51d1SGreg Roach */ 238c2ed51d1SGreg Roach public function value(string $value, Tree $tree): string 239c2ed51d1SGreg Roach { 240c2ed51d1SGreg Roach $values = $this->values(); 241c2ed51d1SGreg Roach 242c2ed51d1SGreg Roach if ($values === []) { 243c2ed51d1SGreg Roach if (str_contains($value, "\n")) { 244c2ed51d1SGreg Roach return '<span dir="auto" class="d-inline-block" style="white-space: pre-wrap;">' . e($value) . '</span>'; 245c2ed51d1SGreg Roach } 246c2ed51d1SGreg Roach 247c2ed51d1SGreg Roach return '<span dir="auto">' . e($value) . '</span>'; 248c2ed51d1SGreg Roach } 249c2ed51d1SGreg Roach 250c2ed51d1SGreg Roach $canonical = $this->canonical($value); 251c2ed51d1SGreg Roach 252c2ed51d1SGreg Roach return $values[$canonical] ?? '<span dir="auto">' . e($value) . '</span>'; 253c2ed51d1SGreg Roach } 254c2ed51d1SGreg Roach 255c2ed51d1SGreg Roach /** 256c2ed51d1SGreg Roach * A list of controlled values for this element 257c2ed51d1SGreg Roach * 258c2ed51d1SGreg Roach * @return array<int|string,string> 259c2ed51d1SGreg Roach */ 260c2ed51d1SGreg Roach public function values(): array 261c2ed51d1SGreg Roach { 262c2ed51d1SGreg Roach return []; 263c2ed51d1SGreg Roach } 264c2ed51d1SGreg Roach 265c2ed51d1SGreg Roach /** 266c2ed51d1SGreg Roach * Display the value of this type of element - convert URLs to links 267c2ed51d1SGreg Roach * 268c2ed51d1SGreg Roach * @param string $value 269c2ed51d1SGreg Roach * 270c2ed51d1SGreg Roach * @return string 271c2ed51d1SGreg Roach */ 272c2ed51d1SGreg Roach protected function valueAutoLink(string $value): string 273c2ed51d1SGreg Roach { 274c2ed51d1SGreg Roach // Convert URLs into markdown auto-links. 275c2ed51d1SGreg Roach $value = preg_replace(self::REGEX_URL, '<$0>', $value); 276c2ed51d1SGreg Roach 277c2ed51d1SGreg Roach // Create a minimal commonmark processor - just add support for autolinks. 278c2ed51d1SGreg Roach $environment = new Environment(); 279c2ed51d1SGreg Roach $environment 280c2ed51d1SGreg Roach ->addBlockRenderer(Document::class, new DocumentRenderer()) 281c2ed51d1SGreg Roach ->addBlockRenderer(Paragraph::class, new ParagraphRenderer()) 282c2ed51d1SGreg Roach ->addInlineRenderer(Text::class, new TextRenderer()) 283c2ed51d1SGreg Roach ->addInlineRenderer(Link::class, new LinkRenderer()) 284c2ed51d1SGreg Roach ->addInlineParser(new AutolinkParser()); 285c2ed51d1SGreg Roach 286c2ed51d1SGreg Roach $converter = new CommonMarkConverter(['html_input' => Environment::HTML_INPUT_ESCAPE], $environment); 287c2ed51d1SGreg Roach 288c2ed51d1SGreg Roach return $converter->convertToHtml($value); 289c2ed51d1SGreg Roach } 290c2ed51d1SGreg Roach 291c2ed51d1SGreg Roach /** 292c2ed51d1SGreg Roach * Display the value of this type of element. 293c2ed51d1SGreg Roach * 294c2ed51d1SGreg Roach * @param string $value 295c2ed51d1SGreg Roach * 296c2ed51d1SGreg Roach * @return string 297c2ed51d1SGreg Roach */ 298c2ed51d1SGreg Roach public function valueNumeric(string $value): string 299c2ed51d1SGreg Roach { 300c2ed51d1SGreg Roach $canonical = $this->canonical($value); 301c2ed51d1SGreg Roach 302c2ed51d1SGreg Roach if (is_numeric($canonical)) { 303c2ed51d1SGreg Roach return I18N::number((int) $canonical); 304c2ed51d1SGreg Roach } 305c2ed51d1SGreg Roach 306c2ed51d1SGreg Roach return e($value); 307c2ed51d1SGreg Roach } 308c2ed51d1SGreg Roach} 309