Passed
Push — 2.0 ( ae8d87...bbf139 )
by Greg
08:42
created

AbstractElement::canonicalText()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 9
rs 10
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 preg_match;
32
use function str_contains;
33
use function str_starts_with;
34
use function strip_tags;
35
use function trim;
36
use function view;
37
38
/**
39
 * A GEDCOM element is a tag/primitive in a GEDCOM file.
40
 */
41
abstract class AbstractElement implements ElementInterface
42
{
43
    protected const REGEX_URL = '~((https?|ftp]):)(//([^\s/?#<>]*))?([^\s?#<>]*)(\?([^\s#<>]*))?(#[^\s?#<>]+)?~';
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
     * Create a default value for this element.
108
     *
109
     * @param Tree $tree
110
     *
111
     * @return string
112
     */
113
    public function default(Tree $tree): string
114
    {
115
        return '';
116
    }
117
118
    /**
119
     * An edit control for this data.
120
     *
121
     * @param string $id
122
     * @param string $name
123
     * @param string $value
124
     * @param Tree   $tree
125
     *
126
     * @return string
127
     */
128
    public function edit(string $id, string $name, string $value, Tree $tree): string
129
    {
130
        $values = $this->values();
131
132
        if ($values !== []) {
133
            $value = $this->canonical($value);
134
135
            // Ensure the current data is in the list.
136
            if (!array_key_exists($value, $values)) {
137
                $values = [$value => $value] + $values;
138
            }
139
140
            // We may use markup to display values, but not when editing them.
141
            $values = array_map(function (string $x): string {
142
                return strip_tags($x);
143
            }, $values);
144
145
            return view('components/select', [
146
                'id'       => $id,
147
                'name'     => $name,
148
                'options'  => $values,
149
                'selected' => $value,
150
            ]);
151
        }
152
153
        $attributes = [
154
            'class'     => 'form-control',
155
            'dir'       => 'auto',
156
            'type'      => 'text',
157
            'id'        => $id,
158
            'name'      => $name,
159
            'value'     => $value,
160
            'maxlength' => static::MAXIMUM_LENGTH,
161
            'pattern'   => static::PATTERN,
162
        ];
163
164
        return '<input ' . Html::attributes($attributes) . ' />';
165
    }
166
167
    /**
168
     * An edit control for this data.
169
     *
170
     * @param string $id
171
     * @param string $name
172
     * @param string $value
173
     *
174
     * @return string
175
     */
176
    public function editHidden(string $id, string $name, string $value): string
177
    {
178
        return '<input class="form-control" type="hidden" id="' . e($id) . '" name="' . e($name) . '" value="' . e($value) . '" />';
179
    }
180
181
    /**
182
     * An edit control for this data.
183
     *
184
     * @param string $id
185
     * @param string $name
186
     * @param string $value
187
     *
188
     * @return string
189
     */
190
    public function editTextArea(string $id, string $name, string $value): string
191
    {
192
        return '<textarea class="form-control" id="' . e($id) . '" name="' . e($name) . '" rows="5" dir="auto">' . e($value) . '</textarea>';
193
    }
194
195
    /**
196
     * Escape @ signs in a GEDCOM export.
197
     *
198
     * @param string $value
199
     *
200
     * @return string
201
     */
202
    public function escape(string $value): string
203
    {
204
        return strtr($value, ['@' => '@@']);
205
    }
206
207
    /**
208
     * Create a label for this element.
209
     *
210
     * @return string
211
     */
212
    public function label(): string
213
    {
214
        return $this->label;
215
    }
216
217
    /**
218
     * Create a label/value pair for this element.
219
     *
220
     * @param string $value
221
     * @param Tree   $tree
222
     *
223
     * @return string
224
     */
225
    public function labelValue(string $value, Tree $tree): string
226
    {
227
        $label = '<span class="label">' . $this->label() . '</span>';
228
        $value = '<span class="value align-top">' . $this->value($value, $tree) . '</span>';
229
        $html  = I18N::translate(/* I18N: e.g. "Occupation: farmer" */ '%1$s: %2$s', $label, $value);
230
231
        return '<div>' . $html . '</div>';
232
    }
233
234
    /**
235
     * Set, remove or replace a subtag.
236
     *
237
     * @param string $subtag
238
     * @param string $repeat
239
     * @param string $before
240
     *
241
     * @return void
242
     */
243
    public function subtag(string $subtag, string $repeat = '0:1', string $before = ''): void
244
    {
245
        if ($repeat === '') {
246
            unset($this->subtags[$subtag]);
247
        } elseif ($before === '' || ($this->subtags[$before] ?? null) === null) {
248
            $this->subtags[$subtag] = $repeat;
249
        } else {
250
            $tmp = [];
251
252
            foreach ($this->subtags as $key => $value) {
253
                if ($key === $before) {
254
                    $tmp[$subtag] = $repeat;
255
                }
256
                $tmp[$key] = $value;
257
            }
258
259
            $this->subtags = $tmp;
260
        }
261
    }
262
263
    /**
264
     * @return array<string,string>
265
     */
266
    public function subtags(): array
267
    {
268
        return $this->subtags;
269
    }
270
271
    /**
272
     * Display the value of this type of element.
273
     *
274
     * @param string $value
275
     * @param Tree   $tree
276
     *
277
     * @return string
278
     */
279
    public function value(string $value, Tree $tree): string
280
    {
281
        $values = $this->values();
282
283
        if ($values === []) {
284
            if (str_contains($value, "\n")) {
285
                return '<bdi class="d-inline-block" style="white-space: pre-wrap;">' . e($value) . '</bdi>';
286
            }
287
288
            return '<bdi>' . e($value) . '</bdi>';
289
        }
290
291
        $canonical = $this->canonical($value);
292
293
        return $values[$canonical] ?? '<bdi>' . e($value) . '</bdi>';
294
    }
295
296
    /**
297
     * A list of controlled values for this element
298
     *
299
     * @return array<int|string,string>
300
     */
301
    public function values(): array
302
    {
303
        return [];
304
    }
305
306
    /**
307
     * Display the value of this type of element - convert URLs to links.
308
     *
309
     * @param string $value
310
     *
311
     * @return string
312
     */
313
    protected function valueAutoLink(string $value): string
314
    {
315
        $canonical = $this->canonical($value);
316
317
        if (preg_match(static::REGEX_URL, $canonical)) {
318
            return '<a href="' . e($canonical) . '" rel="no-follow">' . e($canonical) . '</a>';
319
        }
320
321
        return e($canonical);
322
    }
323
324
    /**
325
     * Display the value of this type of element - convert to URL.
326
     *
327
     * @param string $value
328
     *
329
     * @return string
330
     */
331
    protected function valueLink(string $value): string
332
    {
333
        $canonical = $this->canonical($value);
334
335
        if (str_starts_with($canonical, 'https://') || str_starts_with($canonical, 'http://')) {
336
            return '<a dir="auto" href="' . e($canonical) . '">' . e($value) . '</a>';
337
        }
338
339
        return e($value);
340
    }
341
342
    /**
343
     * Display the value of this type of element.
344
     *
345
     * @param string $value
346
     *
347
     * @return string
348
     */
349
    public function valueNumeric(string $value): string
350
    {
351
        $canonical = $this->canonical($value);
352
353
        if (is_numeric($canonical)) {
354
            return I18N::number((int) $canonical);
355
        }
356
357
        return e($value);
358
    }
359
}
360