Passed
Push — 2.0 ( 0f9198...c8a95e )
by Greg
06:29
created

AbstractElement::valueFormatted()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 23
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 12
c 0
b 0
f 0
nc 3
nop 2
dl 0
loc 23
rs 9.8666
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
18
declare(strict_types=1);
19
20
namespace Fisharebest\Webtrees\Elements;
21
22
use Fisharebest\Webtrees\Contracts\ElementInterface;
23
use Fisharebest\Webtrees\Html;
24
use Fisharebest\Webtrees\I18N;
25
use Fisharebest\Webtrees\Registry;
26
use Fisharebest\Webtrees\Tree;
27
28
use function array_key_exists;
29
use function array_map;
30
use function e;
31
use function is_numeric;
32
use function nl2br;
33
use function preg_replace;
34
use function str_contains;
35
use function str_starts_with;
36
use function strip_tags;
37
use function trim;
38
use function view;
39
40
/**
41
 * A GEDCOM element is a tag/primitive in a GEDCOM file.
42
 */
43
abstract class AbstractElement implements ElementInterface
44
{
45
    // HTML attributes for an <input>
46
    protected const MAXIMUM_LENGTH = false;
47
    protected const PATTERN        = false;
48
49
    // Which child elements can appear under this element.
50
    protected const SUBTAGS = [];
51
52
    /** @var string A label to describe this element */
53
    private $label;
54
55
    /** @var array<string,string> Subtags of this element */
56
    private $subtags;
57
58
    /**
59
     * AbstractGedcomElement constructor.
60
     *
61
     * @param string             $label
62
     * @param array<string>|null $subtags
63
     */
64
    public function __construct(string $label, array $subtags = null)
65
    {
66
        $this->label   = $label;
67
        $this->subtags = $subtags ?? static::SUBTAGS;
68
    }
69
70
    /**
71
     * Convert a value to a canonical form.
72
     *
73
     * @param string $value
74
     *
75
     * @return string
76
     */
77
    public function canonical(string $value): string
78
    {
79
        $value = strtr($value, ["\t" => ' ', "\r" => ' ', "\n" => ' ']);
80
81
        while (str_contains($value, '  ')) {
82
            $value = strtr($value, ['  ' => ' ']);
83
        }
84
85
        return trim($value);
86
    }
87
88
    /**
89
     * Convert a multi-line value to a canonical form.
90
     *
91
     * @param string $value
92
     *
93
     * @return string
94
     */
95
    protected function canonicalText(string $value): string
96
    {
97
        // Browsers use MS-DOS line endings in multi-line data.
98
        $value = strtr($value, ["\t" => ' ', "\r\n" => "\n", "\r" => "\n"]);
99
100
        // Remove blank lines at start/end
101
        $value = preg_replace('/^( *\n)+/', '', $value);
102
103
        return preg_replace('/(\n *)+$/', '', $value);
104
    }
105
106
    /**
107
     * Should we collapse the children of this element when editing?
108
     *
109
     * @return bool
110
     */
111
    public function collapseChildren(): bool
112
    {
113
        return false;
114
    }
115
116
    /**
117
     * Create a default value for this element.
118
     *
119
     * @param Tree $tree
120
     *
121
     * @return string
122
     */
123
    public function default(Tree $tree): string
124
    {
125
        return '';
126
    }
127
128
    /**
129
     * An edit control for this data.
130
     *
131
     * @param string $id
132
     * @param string $name
133
     * @param string $value
134
     * @param Tree   $tree
135
     *
136
     * @return string
137
     */
138
    public function edit(string $id, string $name, string $value, Tree $tree): string
139
    {
140
        $values = $this->values();
141
142
        if ($values !== []) {
143
            $value = $this->canonical($value);
144
145
            // Ensure the current data is in the list.
146
            if (!array_key_exists($value, $values)) {
147
                $values = [$value => $value] + $values;
148
            }
149
150
            // We may use markup to display values, but not when editing them.
151
            $values = array_map(static function (string $x): string {
152
                return strip_tags($x);
153
            }, $values);
154
155
            return view('components/select', [
156
                'id'       => $id,
157
                'name'     => $name,
158
                'options'  => $values,
159
                'selected' => $value,
160
            ]);
161
        }
162
163
        $attributes = [
164
            'class'     => 'form-control',
165
            'dir'       => 'auto',
166
            'type'      => 'text',
167
            'id'        => $id,
168
            'name'      => $name,
169
            'value'     => $value,
170
            'maxlength' => static::MAXIMUM_LENGTH,
171
            'pattern'   => static::PATTERN,
172
        ];
173
174
        return '<input ' . Html::attributes($attributes) . ' />';
175
    }
176
177
    /**
178
     * An edit control for this data.
179
     *
180
     * @param string $id
181
     * @param string $name
182
     * @param string $value
183
     *
184
     * @return string
185
     */
186
    public function editHidden(string $id, string $name, string $value): string
187
    {
188
        return '<input class="form-control" type="hidden" id="' . e($id) . '" name="' . e($name) . '" value="' . e($value) . '" />';
189
    }
190
191
    /**
192
     * An edit control for this data.
193
     *
194
     * @param string $id
195
     * @param string $name
196
     * @param string $value
197
     *
198
     * @return string
199
     */
200
    public function editTextArea(string $id, string $name, string $value): string
201
    {
202
        return '<textarea class="form-control" id="' . e($id) . '" name="' . e($name) . '" rows="3" dir="auto">' . e($value) . '</textarea>';
203
    }
204
205
    /**
206
     * Escape @ signs in a GEDCOM export.
207
     *
208
     * @param string $value
209
     *
210
     * @return string
211
     */
212
    public function escape(string $value): string
213
    {
214
        return strtr($value, ['@' => '@@']);
215
    }
216
217
    /**
218
     * Create a label for this element.
219
     *
220
     * @return string
221
     */
222
    public function label(): string
223
    {
224
        return $this->label;
225
    }
226
227
    /**
228
     * Create a label/value pair for this element.
229
     *
230
     * @param string $value
231
     * @param Tree   $tree
232
     *
233
     * @return string
234
     */
235
    public function labelValue(string $value, Tree $tree): string
236
    {
237
        $label = '<span class="label">' . $this->label() . '</span>';
238
        $value = '<span class="value align-top">' . $this->value($value, $tree) . '</span>';
239
        $html  = I18N::translate(/* I18N: e.g. "Occupation: farmer" */ '%1$s: %2$s', $label, $value);
240
241
        return '<div>' . $html . '</div>';
242
    }
243
244
    /**
245
     * Set, remove or replace a subtag.
246
     *
247
     * @param string $subtag
248
     * @param string $repeat
249
     * @param string $before
250
     *
251
     * @return void
252
     */
253
    public function subtag(string $subtag, string $repeat, string $before = ''): void
254
    {
255
        if ($repeat === '') {
256
            unset($this->subtags[$subtag]);
257
        } elseif ($before === '' || ($this->subtags[$before] ?? null) === null) {
258
            $this->subtags[$subtag] = $repeat;
259
        } else {
260
            $tmp = [];
261
262
            foreach ($this->subtags as $key => $value) {
263
                if ($key === $before) {
264
                    $tmp[$subtag] = $repeat;
265
                }
266
                $tmp[$key] = $value;
267
            }
268
269
            $this->subtags = $tmp;
270
        }
271
    }
272
273
    /**
274
     * @return array<string,string>
275
     */
276
    public function subtags(): array
277
    {
278
        return $this->subtags;
279
    }
280
281
    /**
282
     * Display the value of this type of element.
283
     *
284
     * @param string $value
285
     * @param Tree   $tree
286
     *
287
     * @return string
288
     */
289
    public function value(string $value, Tree $tree): string
290
    {
291
        $values = $this->values();
292
293
        if ($values === []) {
294
            if (str_contains($value, "\n")) {
295
                return '<bdi class="d-inline-block">' . nl2br(e($value)) . '</bdi>';
296
            }
297
298
            return '<bdi>' . e($value) . '</bdi>';
299
        }
300
301
        $canonical = $this->canonical($value);
302
303
        return $values[$canonical] ?? '<bdi>' . e($value) . '</bdi>';
304
    }
305
306
    /**
307
     * A list of controlled values for this element
308
     *
309
     * @return array<int|string,string>
310
     */
311
    public function values(): array
312
    {
313
        return [];
314
    }
315
316
    /**
317
     * Display the value of this type of element - convert URLs to links.
318
     *
319
     * @param string $value
320
     *
321
     * @return string
322
     */
323
    protected function valueAutoLink(string $value): string
324
    {
325
        $canonical = $this->canonical($value);
326
327
        if (str_contains($canonical, 'http://') || str_contains($canonical, 'https://')) {
328
            $html = Registry::markdownFactory()->autolink()->convertToHtml($canonical);
329
330
            return strip_tags($html, '<a>');
331
        }
332
333
        return e($canonical);
334
    }
335
336
    /**
337
     * Display the value of this type of element - multi-line text with/without markdown.
338
     *
339
     * @param string $value
340
     * @param Tree   $tree
341
     *
342
     * @return string
343
     */
344
    protected function valueFormatted(string $value, Tree $tree): string
345
    {
346
        $canonical = $this->canonical($value);
347
348
        $format = $tree->getPreference('FORMAT_TEXT');
349
350
        if ($format === 'markdown') {
351
            $html = Registry::markdownFactory()->markdown($tree)->convertToHtml($canonical);
352
353
            return '<div class="markdown" dir="auto">' . $html . '</div>';
354
        }
355
356
        $html = Registry::markdownFactory()->autolink($tree)->convertToHtml($canonical);
357
        $html = strip_tags($html, '<a>');
358
        $html = trim($html);
359
360
        if (str_contains($html, "\n")) {
361
            $html = nl2br($html);
362
363
            return '<div dir="auto">' . $html . '</div>';
364
        }
365
366
        return '<span dir="auto">' . $html . '</span>';
367
    }
368
369
    /**
370
     * Display the value of this type of element - convert to URL.
371
     *
372
     * @param string $value
373
     *
374
     * @return string
375
     */
376
    protected function valueLink(string $value): string
377
    {
378
        $canonical = $this->canonical($value);
379
380
        if (str_starts_with($canonical, 'https://') || str_starts_with($canonical, 'http://')) {
381
            return '<a dir="auto" href="' . e($canonical) . '">' . e($value) . '</a>';
382
        }
383
384
        return e($value);
385
    }
386
387
    /**
388
     * Display the value of this type of element.
389
     *
390
     * @param string $value
391
     *
392
     * @return string
393
     */
394
    public function valueNumeric(string $value): string
395
    {
396
        $canonical = $this->canonical($value);
397
398
        if (is_numeric($canonical)) {
399
            return I18N::number((int) $canonical);
400
        }
401
402
        return e($value);
403
    }
404
}
405