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