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