Passed
Push — 2.0 ( ca8a7f...3587be )
by Greg
07:50
created

AbstractElement::collapseChildren()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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