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; 26 27use function array_key_exists; 28use function array_map; 29use function e; 30use function is_numeric; 31use function preg_match; 32use function strpos; 33use function trim; 34use function view; 35 36/** 37 * A GEDCOM element is a tag/primitive in a GEDCOM file. 38 */ 39abstract class AbstractElement implements ElementInterface 40{ 41 protected const REGEX_URL = '~((https?|ftp]):)(//([^\s/?#<>]*))?([^\s?#<>]*)(\?([^\s#<>]*))?(#[^\s?#<>]+)?~'; 42 43 // HTML attributes for an <input> 44 protected const MAXIMUM_LENGTH = false; 45 protected const PATTERN = false; 46 47 // Which child elements can appear under this element. 48 protected const SUBTAGS = []; 49 50 /** @var string A label to describe this element */ 51 private $label; 52 53 /** @var array<string,string> */ 54 private $subtags; 55 56 /** 57 * AbstractGedcomElement constructor. 58 * 59 * @param string $label 60 * @param array<string>|null $subtags 61 */ 62 public function __construct(string $label, array $subtags = null) 63 { 64 $this->label = $label; 65 $this->subtags = $subtags ?? static::SUBTAGS; 66 } 67 68 /** 69 * Convert a value to a canonical form. 70 * 71 * @param string $value 72 * 73 * @return string 74 */ 75 public function canonical(string $value): string 76 { 77 $value = strtr($value, ["\t" => ' ', "\r" => ' ', "\n" => ' ']); 78 79 while (strpos($value, ' ') !== false) { 80 $value = strtr($value, [' ' => ' ']); 81 } 82 83 return trim($value); 84 } 85 86 /** 87 * Create a default value for this element. 88 * 89 * @param Tree $tree 90 * 91 * @return string 92 */ 93 public function default(Tree $tree): string 94 { 95 return ''; 96 } 97 98 /** 99 * An edit control for this data. 100 * 101 * @param string $id 102 * @param string $name 103 * @param string $value 104 * @param Tree $tree 105 * 106 * @return string 107 */ 108 public function edit(string $id, string $name, string $value, Tree $tree): string 109 { 110 $values = $this->values(); 111 112 if ($values !== []) { 113 $value = $this->canonical($value); 114 115 // Ensure the current data is in the list. 116 if (!array_key_exists($value, $values)) { 117 $values = [$value => $value] + $values; 118 } 119 120 // We may use markup to display values, but not when editing them. 121 $values = array_map('strip_tags', $values); 122 123 return view('components/select', [ 124 'id' => $id, 125 'name' => $name, 126 'options' => $values, 127 'selected' => $value, 128 ]); 129 } 130 131 $attributes = [ 132 'class' => 'form-control', 133 'type' => 'text', 134 'id' => $id, 135 'name' => $name, 136 'value' => $value, 137 'maxvalue' => static::MAXIMUM_LENGTH, 138 'pattern' => static::PATTERN, 139 ]; 140 141 return '<input ' . Html::attributes($attributes) . ' />'; 142 } 143 144 /** 145 * An edit control for this data. 146 * 147 * @param string $id 148 * @param string $name 149 * @param string $value 150 * 151 * @return string 152 */ 153 public function editHidden(string $id, string $name, string $value): string 154 { 155 return '<input class="form-control" type="hidden" id="' . e($id) . '" name="' . e($name) . '" value="' . e($value) . '" />'; 156 } 157 158 /** 159 * An edit control for this data. 160 * 161 * @param string $id 162 * @param string $name 163 * @param string $value 164 * 165 * @return string 166 */ 167 public function editTextArea(string $id, string $name, string $value): string 168 { 169 return '<textarea class="form-control" id="' . e($id) . '" name="' . e($name) . '" rows="5" dir="auto">' . e($value) . '</textarea>'; 170 } 171 172 /** 173 * Escape @ signs in a GEDCOM export. 174 * 175 * @param string $value 176 * 177 * @return string 178 */ 179 public function escape(string $value): string 180 { 181 return strtr($value, ['@' => '@@']); 182 } 183 184 /** 185 * Create a label for this element. 186 * 187 * @return string 188 */ 189 public function label(): string 190 { 191 return $this->label; 192 } 193 194 /** 195 * Create a label/value pair for this element. 196 * 197 * @param string $value 198 * @param Tree $tree 199 * 200 * @return string 201 */ 202 public function labelValue(string $value, Tree $tree): string 203 { 204 $label = '<span class="label">' . $this->label() . '</span>'; 205 $value = '<span class="value">' . $this->value($value, $tree) . '</span>'; 206 $html = I18N::translate(/* I18N: e.g. "Occupation: farmer" */ '%1$s: %2$s', $label, $value); 207 208 return '<div>' . $html . '</div>'; 209 } 210 211 /** 212 * @return array<string,string> 213 */ 214 public function subtags(): array 215 { 216 return $this->subtags; 217 } 218 219 /** 220 * Display the value of this type of element. 221 * 222 * @param string $value 223 * @param Tree $tree 224 * 225 * @return string 226 */ 227 public function value(string $value, Tree $tree): string 228 { 229 $values = $this->values(); 230 231 if ($values === []) { 232 if (str_contains($value, "\n")) { 233 return '<span dir="auto" class="d-inline-block" style="white-space: pre-wrap;">' . e($value) . '</span>'; 234 } 235 236 return '<span dir="auto">' . e($value) . '</span>'; 237 } 238 239 $canonical = $this->canonical($value); 240 241 return $values[$canonical] ?? '<span dir="auto">' . e($value) . '</span>'; 242 } 243 244 /** 245 * A list of controlled values for this element 246 * 247 * @return array<int|string,string> 248 */ 249 public function values(): array 250 { 251 return []; 252 } 253 254 /** 255 * Display the value of this type of element - convert URLs to links 256 * 257 * @param string $value 258 * 259 * @return string 260 */ 261 protected function valueAutoLink(string $value): string 262 { 263 $canonical = $this->canonical($value); 264 265 if (preg_match(static::REGEX_URL, $canonical)) { 266 return '<a href="' . e($canonical) . '" rel="no-follow">' . e($canonical) . '</a>'; 267 } 268 269 return e($canonical); 270 } 271 272 /** 273 * Display the value of this type of element. 274 * 275 * @param string $value 276 * 277 * @return string 278 */ 279 public function valueNumeric(string $value): string 280 { 281 $canonical = $this->canonical($value); 282 283 if (is_numeric($canonical)) { 284 return I18N::number((int) $canonical); 285 } 286 287 return e($value); 288 } 289} 290