Completed
Push — develop ( 15826b...d8b18f )
by Greg
10:40 queued 04:07
created

AbstractElement::subtag()   A

Complexity

Conditions 6
Paths 5

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 11
c 0
b 0
f 0
nc 5
nop 3
dl 0
loc 18
rs 9.2222
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 array_search;
30
use function e;
31
use function is_numeric;
32
use function preg_match;
33
use function str_contains;
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
    // A label to describe this element
53
    private string $label;
54
55
    // Subtags of this element
56
    private array $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
     * Create a default value for this element.
90
     *
91
     * @param Tree $tree
92
     *
93
     * @return string
94
     */
95
    public function default(Tree $tree): string
96
    {
97
        return '';
98
    }
99
100
    /**
101
     * An edit control for this data.
102
     *
103
     * @param string $id
104
     * @param string $name
105
     * @param string $value
106
     * @param Tree   $tree
107
     *
108
     * @return string
109
     */
110
    public function edit(string $id, string $name, string $value, Tree $tree): string
111
    {
112
        $values = $this->values();
113
114
        if ($values !== []) {
115
            $value = $this->canonical($value);
116
117
            // Ensure the current data is in the list.
118
            if (!array_key_exists($value, $values)) {
119
                $values = [$value => $value] + $values;
120
            }
121
122
            // We may use markup to display values, but not when editing them.
123
            $values = array_map(fn(string $x): string => strip_tags($x), $values);
124
125
            return view('components/select', [
126
                'id'       => $id,
127
                'name'     => $name,
128
                'options'  => $values,
129
                'selected' => $value,
130
            ]);
131
        }
132
133
        $attributes = [
134
            'class'     => 'form-control',
135
            'type'      => 'text',
136
            'id'        => $id,
137
            'name'      => $name,
138
            'value'     => $value,
139
            'maxlength' => static::MAXIMUM_LENGTH,
140
            'pattern'   => static::PATTERN,
141
        ];
142
143
        return '<input ' . Html::attributes($attributes) . ' />';
144
    }
145
146
    /**
147
     * An edit control for this data.
148
     *
149
     * @param string $id
150
     * @param string $name
151
     * @param string $value
152
     *
153
     * @return string
154
     */
155
    public function editHidden(string $id, string $name, string $value): string
156
    {
157
        return '<input class="form-control" type="hidden" id="' . e($id) . '" name="' . e($name) . '" value="' . e($value) . '" />';
158
    }
159
160
    /**
161
     * An edit control for this data.
162
     *
163
     * @param string $id
164
     * @param string $name
165
     * @param string $value
166
     *
167
     * @return string
168
     */
169
    public function editTextArea(string $id, string $name, string $value): string
170
    {
171
        return '<textarea class="form-control" id="' . e($id) . '" name="' . e($name) . '" rows="5" dir="auto">' . e($value) . '</textarea>';
172
    }
173
174
    /**
175
     * Escape @ signs in a GEDCOM export.
176
     *
177
     * @param string $value
178
     *
179
     * @return string
180
     */
181
    public function escape(string $value): string
182
    {
183
        return strtr($value, ['@' => '@@']);
184
    }
185
186
    /**
187
     * Create a label for this element.
188
     *
189
     * @return string
190
     */
191
    public function label(): string
192
    {
193
        return $this->label;
194
    }
195
196
    /**
197
     * Create a label/value pair for this element.
198
     *
199
     * @param string $value
200
     * @param Tree   $tree
201
     *
202
     * @return string
203
     */
204
    public function labelValue(string $value, Tree $tree): string
205
    {
206
        $label = '<span class="label">' . $this->label() . '</span>';
207
        $value = '<span class="value">' . $this->value($value, $tree) . '</span>';
208
        $html  = I18N::translate(/* I18N: e.g. "Occupation: farmer" */ '%1$s: %2$s', $label, $value);
209
210
        return '<div>' . $html . '</div>';
211
    }
212
213
    /**
214
     * Set, remove or replace a subtag.
215
     *
216
     * @param string $subtag
217
     * @param string $repeat
218
     * @param string $after
219
     *
220
     * @return void
221
     */
222
    public function subtag(string $subtag, string $repeat = '', string $after = ''): void
223
    {
224
        if ($repeat === '') {
225
            unset($this->subtags[$subtag]);
226
        } elseif ($after === '' || ($this->subtags[$subtag] ?? null) === null) {
227
            $this->subtags[$subtag] = $repeat;
228
        } else {
229
            $tmp = [];
230
231
            foreach ($this->subtags as $key => $value) {
232
                $tmp[$key] = $value;
233
234
                if ($key === $after) {
235
                    $tmp[] = $repeat;
236
                }
237
            }
238
239
            $this->subtags = $tmp;
240
        }
241
242
243
    }
244
245
    /**
246
     * @return array<string,string>
247
     */
248
    public function subtags(): array
249
    {
250
        return $this->subtags;
251
    }
252
253
    /**
254
     * Display the value of this type of element.
255
     *
256
     * @param string $value
257
     * @param Tree   $tree
258
     *
259
     * @return string
260
     */
261
    public function value(string $value, Tree $tree): string
262
    {
263
        $values = $this->values();
264
265
        if ($values === []) {
266
            if (str_contains($value, "\n")) {
267
                return '<span dir="auto" class="d-inline-block" style="white-space: pre-wrap;">' . e($value) . '</span>';
268
            }
269
270
            return '<span dir="auto">' . e($value) . '</span>';
271
        }
272
273
        $canonical = $this->canonical($value);
274
275
        return $values[$canonical] ?? '<span dir="auto">' . e($value) . '</span>';
276
    }
277
278
    /**
279
     * A list of controlled values for this element
280
     *
281
     * @return array<int|string,string>
282
     */
283
    public function values(): array
284
    {
285
        return [];
286
    }
287
288
    /**
289
     * Display the value of this type of element - convert URLs to links
290
     *
291
     * @param string $value
292
     *
293
     * @return string
294
     */
295
    protected function valueAutoLink(string $value): string
296
    {
297
        $canonical = $this->canonical($value);
298
299
        if (preg_match(static::REGEX_URL, $canonical)) {
300
            return '<a href="' . e($canonical) . '" rel="no-follow">' . e($canonical) . '</a>';
301
        }
302
303
        return e($canonical);
304
    }
305
306
    /**
307
     * Display the value of this type of element.
308
     *
309
     * @param string $value
310
     *
311
     * @return string
312
     */
313
    public function valueNumeric(string $value): string
314
    {
315
        $canonical = $this->canonical($value);
316
317
        if (is_numeric($canonical)) {
318
            return I18N::number((int) $canonical);
319
        }
320
321
        return e($value);
322
    }
323
}
324