Completed
Push — develop ( 0d6c46...c341e4 )
by Greg
15:25 queued 09:07
created

GedcomEditService   F

Complexity

Total Complexity 61

Size/Duplication

Total Lines 351
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 147
c 1
b 0
f 0
dl 0
loc 351
rs 3.52
wmc 61

6 Methods

Rating   Name   Duplication   Size   Complexity  
B addNewName() 0 33 7
C handleUpdates() 0 66 17
A addNewSex() 0 11 3
C editLinesToGedcom() 0 47 15
A updateSource() 0 26 2
C addNewFact() 0 50 17

How to fix   Complexity   

Complex Class

Complex classes like GedcomEditService often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use GedcomEditService, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * webtrees: online genealogy
5
 * Copyright (C) 2020 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 <http://www.gnu.org/licenses/>.
16
 */
17
18
declare(strict_types=1);
19
20
namespace Fisharebest\Webtrees\Services;
21
22
use Fisharebest\Webtrees\Gedcom;
23
use Fisharebest\Webtrees\Registry;
24
use Fisharebest\Webtrees\Tree;
25
use Psr\Http\Message\ServerRequestInterface;
26
27
use function array_merge;
28
use function array_unique;
29
use function assert;
30
use function count;
31
use function preg_match_all;
32
use function str_replace;
33
use function trim;
34
35
/**
36
 * Utilities to edit/save GEDCOM data.
37
 */
38
class GedcomEditService
39
{
40
    /** @var string[] */
41
    public $glevels = [];
42
43
    /** @var string[] */
44
    public $tag = [];
45
46
    /** @var string[] */
47
    public $islink = [];
48
49
    /** @var string[] */
50
    public $text = [];
51
52
    /** @var string[] */
53
    protected $glevelsSOUR = [];
54
55
    /** @var string[] */
56
    protected $tagSOUR = [];
57
58
    /** @var string[] */
59
    protected $islinkSOUR = [];
60
61
    /** @var string[] */
62
    protected $textSOUR = [];
63
64
    /** @var string[] */
65
    protected $glevelsRest = [];
66
67
    /** @var string[] */
68
    protected $tagRest = [];
69
70
    /** @var string[] */
71
    protected $islinkRest = [];
72
73
    /** @var string[] */
74
    protected $textRest = [];
75
76
    /**
77
     * Add new gedcom lines from interface update arrays
78
     * The edit_interface and FunctionsEdit::add_simple_tag function produce the following
79
     * arrays incoming from the $_POST form
80
     * - $glevels[] - an array of the gedcom level for each line that was edited
81
     * - $tag[] - an array of the tags for each gedcom line that was edited
82
     * - $islink[] - an array of 1 or 0 values to tell whether the text is a link element and should be surrounded by @@
83
     * - $text[] - an array of the text data for each line
84
     * With these arrays you can recreate the gedcom lines like this
85
     * <code>$glevel[0].' '.$tag[0].' '.$text[0]</code>
86
     * There will be an index in each of these arrays for each line of the gedcom
87
     * fact that is being edited.
88
     * If the $text[] array is empty for the given line, then it means that the
89
     * user removed that line during editing or that the line is supposed to be
90
     * empty (1 DEAT, 1 BIRT) for example. To know if the line should be removed
91
     * there is a section of code that looks ahead to the next lines to see if there
92
     * are sub lines. For example we don't want to remove the 1 DEAT line if it has
93
     * a 2 PLAC or 2 DATE line following it. If there are no sub lines, then the line
94
     * can be safely removed.
95
     *
96
     * @param string $newged        the new gedcom record to add the lines to
97
     * @param string $levelOverride Override GEDCOM level specified in $glevels[0]
98
     *
99
     * @return string The updated gedcom record
100
     */
101
    public function handleUpdates(string $newged, $levelOverride = 'no'): string
102
    {
103
        if ($levelOverride === 'no') {
104
            $levelAdjust = 0;
105
        } else {
106
            $levelAdjust = 1;
107
        }
108
109
        // Assert all arrays are the same size.
110
        assert(count($this->glevels) === count($this->tag));
111
        assert(count($this->glevels) === count($this->text));
112
        assert(count($this->glevels) === count($this->islink));
113
114
        $count = count($this->glevels);
115
116
        for ($j = 0; $j < $count; $j++) {
117
            // Look for empty SOUR reference with non-empty sub-records.
118
            // This can happen when the SOUR entry is deleted but its sub-records
119
            // were incorrectly left intact.
120
            // The sub-records should be deleted.
121
            if ($this->tag[$j] === 'SOUR' && ($this->text[$j] === '@@' || $this->text[$j] === '')) {
122
                $this->text[$j] = '';
123
                $k              = $j + 1;
124
                while ($k < $count && $this->glevels[$k] > $this->glevels[$j]) {
125
                    $this->text[$k] = '';
126
                    $k++;
127
                }
128
            }
129
130
            if (trim($this->text[$j]) !== '') {
131
                $pass = true;
132
            } else {
133
                //-- for facts with empty values they must have sub records
134
                //-- this section checks if they have subrecords
135
                $k    = $j + 1;
136
                $pass = false;
137
                while ($k < $count && $this->glevels[$k] > $this->glevels[$j]) {
138
                    if ($this->text[$k] !== '') {
139
                        if ($this->tag[$j] !== 'OBJE' || $this->tag[$k] === 'FILE') {
140
                            $pass = true;
141
                            break;
142
                        }
143
                    }
144
                    $k++;
145
                }
146
            }
147
148
            //-- if the value is not empty or it has sub lines
149
            //--- then write the line to the gedcom record
150
            //-- we have to let some emtpy text lines pass through... (DEAT, BIRT, etc)
151
            if ($pass) {
152
                $newline = (int) $this->glevels[$j] + $levelAdjust . ' ' . $this->tag[$j];
153
                if ($this->text[$j] !== '') {
154
                    if ($this->islink[$j]) {
155
                        $newline .= ' @' . $this->text[$j] . '@';
156
                    } else {
157
                        $newline .= ' ' . $this->text[$j];
158
                    }
159
                }
160
                $next_level = 1 + (int) $this->glevels[$j] + $levelAdjust;
161
162
                $newged .= "\n" . str_replace("\n", "\n" . $next_level . ' CONT ', $newline);
163
            }
164
        }
165
166
        return $newged;
167
    }
168
169
    /**
170
     * Create a form to add a new fact.
171
     *
172
     * @param ServerRequestInterface $request
173
     * @param Tree                   $tree
174
     * @param string                 $fact
175
     *
176
     * @return string
177
     */
178
    public function addNewFact(ServerRequestInterface $request, Tree $tree, $fact): string
179
    {
180
        $params = (array) $request->getParsedBody();
181
182
        $FACT = $params[$fact];
183
        $DATE = $params[$fact . '_DATE'] ?? '';
184
        $PLAC = $params[$fact . '_PLAC'] ?? '';
185
186
        if ($DATE !== '' || $PLAC !== '' || $FACT !== '' && $FACT !== 'Y') {
187
            if ($FACT !== '' && $FACT !== 'Y') {
188
                $gedrec = "\n1 " . $fact . ' ' . $FACT;
189
            } else {
190
                $gedrec = "\n1 " . $fact;
191
            }
192
            if ($DATE !== '') {
193
                $gedrec .= "\n2 DATE " . $DATE;
194
            }
195
            if ($PLAC !== '') {
196
                $gedrec .= "\n2 PLAC " . $PLAC;
197
198
                if (preg_match_all('/(' . Gedcom::REGEX_TAG . ')/', $tree->getPreference('ADVANCED_PLAC_FACTS'), $match)) {
199
                    foreach ($match[1] as $tag) {
200
                        $TAG = $params[$fact . '_' . $tag];
201
                        if ($TAG !== '') {
202
                            $gedrec .= "\n3 " . $tag . ' ' . $TAG;
203
                        }
204
                    }
205
                }
206
                $LATI = $params[$fact . '_LATI'] ?? '';
207
                $LONG = $params[$fact . '_LONG'] ?? '';
208
                if ($LATI !== '' || $LONG !== '') {
209
                    $gedrec .= "\n3 MAP\n4 LATI " . $LATI . "\n4 LONG " . $LONG;
210
                }
211
            }
212
            if ((bool) ($params['SOUR_' . $fact] ?? false)) {
213
                return $this->updateSource($gedrec, 'yes');
214
            }
215
216
            return $gedrec;
217
        }
218
219
        if ($FACT === 'Y') {
220
            if ((bool) ($params['SOUR_' . $fact] ?? false)) {
221
                return $this->updateSource("\n1 " . $fact . ' Y', 'yes');
222
            }
223
224
            return "\n1 " . $fact . ' Y';
225
        }
226
227
        return '';
228
    }
229
230
    /**
231
     * Add new GEDCOM lines from the $xxxSOUR interface update arrays, which
232
     * were produced by the splitSOUR() function.
233
     * See the FunctionsEdit::handle_updatesges() function for details.
234
     *
235
     * @param string $inputRec
236
     * @param string $levelOverride
237
     *
238
     * @return string
239
     */
240
    public function updateSource(string $inputRec, string $levelOverride = 'no'): string
241
    {
242
        if (count($this->tagSOUR) === 0) {
243
            return $inputRec; // No update required
244
        }
245
246
        // Save original interface update arrays before replacing them with the xxxSOUR ones
247
        $glevelsSave = $this->glevels;
248
        $tagSave     = $this->tag;
249
        $islinkSave  = $this->islink;
250
        $textSave    = $this->text;
251
252
        $this->glevels = $this->glevelsSOUR;
253
        $this->tag     = $this->tagSOUR;
254
        $this->islink  = $this->islinkSOUR;
255
        $this->text    = $this->textSOUR;
256
257
        $myRecord = $this->handleUpdates($inputRec, $levelOverride); // Now do the update
258
259
        // Restore the original interface update arrays (just in case ...)
260
        $this->glevels = $glevelsSave;
261
        $this->tag     = $tagSave;
262
        $this->islink  = $islinkSave;
263
        $this->text    = $textSave;
264
265
        return $myRecord;
266
    }
267
268
    /**
269
     * Create a form to add a sex record.
270
     *
271
     * @param ServerRequestInterface $request
272
     *
273
     * @return string
274
     */
275
    public function addNewSex(ServerRequestInterface $request): string
276
    {
277
        $params = (array) $request->getParsedBody();
278
279
        switch ($params['SEX']) {
280
            case 'M':
281
                return "\n1 SEX M";
282
            case 'F':
283
                return "\n1 SEX F";
284
            default:
285
                return "\n1 SEX U";
286
        }
287
    }
288
289
    /**
290
     * Assemble the pieces of a newly created record into gedcom
291
     *
292
     * @param ServerRequestInterface $request
293
     * @param Tree                   $tree
294
     *
295
     * @return string
296
     */
297
    public function addNewName(ServerRequestInterface $request, Tree $tree): string
298
    {
299
        $params = (array) $request->getParsedBody();
300
        $gedrec = "\n1 NAME " . $params['NAME'];
301
302
        $tags = [
303
            'NPFX',
304
            'GIVN',
305
            'SPFX',
306
            'SURN',
307
            'NSFX',
308
            'NICK',
309
        ];
310
311
        if (preg_match_all('/(' . Gedcom::REGEX_TAG . ')/', $tree->getPreference('ADVANCED_NAME_FACTS'), $match)) {
312
            $tags = array_merge($tags, $match[1]);
313
        }
314
315
        // Paternal and Polish and Lithuanian surname traditions can also create a _MARNM
316
        $SURNAME_TRADITION = $tree->getPreference('SURNAME_TRADITION');
317
        if ($SURNAME_TRADITION === 'paternal' || $SURNAME_TRADITION === 'polish' || $SURNAME_TRADITION === 'lithuanian') {
318
            $tags[] = '_MARNM';
319
        }
320
321
        foreach (array_unique($tags) as $tag) {
322
            $TAG = $params[$tag];
323
324
            if ($TAG !== '') {
325
                $gedrec .= "\n2 " . $tag . ' ' . $TAG;
326
            }
327
        }
328
329
        return $gedrec;
330
    }
331
332
    /**
333
     * Reassemble edited GEDCOM fields into a GEDCOM fact/event string.
334
     *
335
     * @param string        $record_type
336
     * @param array<string> $levels
337
     * @param array<string> $tags
338
     * @param array<string> $values
339
     *
340
     * @return string
341
     */
342
    public function editLinesToGedcom(string $record_type, array $levels, array $tags, array $values): string
343
    {
344
        // Assert all arrays are the same size.
345
        $count = count($levels);
346
        assert($count > 0);
347
        assert(count($tags) === $count);
348
        assert(count($values) === $count);
349
350
        $gedcom_lines = [];
351
        $hierarchy    = [$record_type];
352
353
        for ($i = 0; $i < $count; $i++) {
354
            $hierarchy[$levels[$i]] = $tags[$i];
355
356
            $full_tag   = implode(':', array_slice($hierarchy, 0, 1 + (int) $levels[$i]));
357
            $element    = Registry::elementFactory()->make($full_tag);
358
            $values[$i] = $element->canonical($values[$i]);
359
360
            // If "1 FACT Y" has a DATE or PLAC, then delete the value of Y
361
            if ($levels[$i] === '1' && $values[$i] === 'Y') {
362
                for ($j = $i + 1; $j < $count && $levels[$j] > $levels[$i]; ++$j) {
363
                    if ($levels[$j] === '2' && ($tags[$j] === 'DATE' || $tags[$j] === 'PLAC') && $values[$j] !== '') {
364
                        $values[$i] = '';
365
                        break;
366
                    }
367
                }
368
            }
369
370
            // Include this line if there is a value - or if there is a child record with a value.
371
            $include = $values[$i] !== '';
372
373
            for ($j = $i + 1; !$include && $j < $count && $levels[$j] > $levels[$i]; $j++) {
374
                $include = $values[$j] !== '';
375
            }
376
377
            if ($include) {
378
                if ($values[$i] === '') {
379
                    $gedcom_lines[] = $levels[$i] . ' ' . $tags[$i];
380
                } else {
381
                    $next_level = 1 + (int) $levels[$i];
382
383
                    $gedcom_lines[] = $levels[$i] . ' ' . $tags[$i] . ' ' . str_replace("\n", "\n" . $next_level . ' CONT ', $values[$i]);
384
                }
385
            }
386
        }
387
388
        return implode("\n", $gedcom_lines);
389
    }
390
}
391