xref: /webtrees/app/Elements/AbstractElement.php (revision 9c5908c9a28e47936caf220329816f4b8356628d)
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