Passed
Push — master ( cbebe6...0973b4 )
by Greg
06:55
created

FunctionsPrint::formatFactPlace()   D

Complexity

Conditions 15
Paths 239

Size

Total Lines 71
Code Lines 48

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 15
eloc 48
nc 239
nop 4
dl 0
loc 71
rs 4.5958
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * webtrees: online genealogy
5
 * Copyright (C) 2019 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\Functions;
21
22
use Fisharebest\Webtrees\Date;
23
use Fisharebest\Webtrees\Fact;
24
use Fisharebest\Webtrees\Family;
25
use Fisharebest\Webtrees\Filter;
26
use Fisharebest\Webtrees\Gedcom;
27
use Fisharebest\Webtrees\GedcomCode\GedcomCodeStat;
28
use Fisharebest\Webtrees\GedcomCode\GedcomCodeTemp;
29
use Fisharebest\Webtrees\GedcomRecord;
30
use Fisharebest\Webtrees\GedcomTag;
31
use Fisharebest\Webtrees\Http\RequestHandlers\HelpText;
32
use Fisharebest\Webtrees\I18N;
33
use Fisharebest\Webtrees\Individual;
34
use Fisharebest\Webtrees\Note;
35
use Fisharebest\Webtrees\Place;
36
use Fisharebest\Webtrees\Tree;
37
use Illuminate\Support\Collection;
38
use Illuminate\Support\Str;
39
use Ramsey\Uuid\Uuid;
40
41
use function view;
42
43
/**
44
 * Class FunctionsPrint - common functions
45
 */
46
class FunctionsPrint
47
{
48
    /**
49
     * print a note record
50
     *
51
     * @param Tree   $tree
52
     * @param string $text
53
     * @param int    $nlevel the level of the note record
54
     * @param string $nrec   the note record to print
55
     *
56
     * @return string
57
     */
58
    private static function printNoteRecord(Tree $tree, $text, $nlevel, $nrec): string
59
    {
60
        $text .= Functions::getCont($nlevel, $nrec);
61
62
        // Check if shared note (we have already checked that it exists)
63
        if (preg_match('/^0 @(' . Gedcom::REGEX_XREF . ')@ NOTE/', $nrec, $match)) {
64
            $note  = Note::getInstance($match[1], $tree);
65
            $label = 'SHARED_NOTE';
66
            $html  = Filter::formatText($note->getNote(), $tree);
67
        } else {
68
            $note  = null;
69
            $label = 'NOTE';
70
            $html  = Filter::formatText($text, $tree);
71
        }
72
73
        if (strpos($text, "\n") === false) {
74
            // A one-line note? strip the block-level tags, so it displays inline
75
            return GedcomTag::getLabelValue($label, strip_tags($html, '<a><strong><em>'));
76
        }
77
78
        if ($tree->getPreference('EXPAND_NOTES')) {
79
            // A multi-line note, and we're expanding notes by default
80
            return GedcomTag::getLabelValue($label, $html);
81
        }
82
83
        // A multi-line note, with an expand/collapse option
84
        $element_id = Uuid::uuid4()->toString();
85
        // NOTE: class "note-details" is (currently) used only by some third-party themes
86
        if ($note) {
87
            $first_line = '<a href="' . e($note->url()) . '">' . $note->fullName() . '</a>';
88
        } else {
89
            [$text] = explode("\n", strip_tags($html));
90
            $first_line = Str::limit($text, 100, I18N::translate('…'));
91
        }
92
93
        return
94
            '<div class="fact_NOTE"><span class="label">' .
95
            '<a href="#" onclick="expand_layer(\'' . $element_id . '\'); return false;"><i id="' . $element_id . '_img" class="icon-plus"></i></a> ' . GedcomTag::getLabel($label) . ':</span> ' . '<span id="' . $element_id . '-alt">' . $first_line . '</span>' .
96
            '</div>' .
97
            '<div class="note-details" id="' . $element_id . '" style="display:none">' . $html . '</div>';
98
    }
99
100
    /**
101
     * Print all of the notes in this fact record
102
     *
103
     * @param Tree   $tree
104
     * @param string $factrec The factrecord to print the notes from
105
     * @param int    $level   The level of the factrecord
106
     *
107
     * @return string HTML
108
     */
109
    public static function printFactNotes(Tree $tree, $factrec, $level): string
110
    {
111
        $data          = '';
112
        $previous_spos = 0;
113
        $nlevel        = $level + 1;
114
        $ct            = preg_match_all("/$level NOTE (.*)/", $factrec, $match, PREG_SET_ORDER);
115
        for ($j = 0; $j < $ct; $j++) {
116
            $spos1 = strpos($factrec, $match[$j][0], $previous_spos);
117
            $spos2 = strpos($factrec . "\n$level", "\n$level", $spos1 + 1);
118
            if (!$spos2) {
119
                $spos2 = strlen($factrec);
120
            }
121
            $previous_spos = $spos2;
122
            $nrec          = substr($factrec, $spos1, $spos2 - $spos1);
123
            if (!isset($match[$j][1])) {
124
                $match[$j][1] = '';
125
            }
126
            if (!preg_match('/^@(' . Gedcom::REGEX_XREF . ')@$/', $match[$j][1], $nmatch)) {
127
                $data .= self::printNoteRecord($tree, $match[$j][1], $nlevel, $nrec);
128
            } else {
129
                $note = Note::getInstance($nmatch[1], $tree);
130
                if ($note) {
131
                    if ($note->canShow()) {
132
                        $noterec = $note->gedcom();
133
                        $nt      = preg_match("/0 @$nmatch[1]@ NOTE (.*)/", $noterec, $n1match);
134
                        $data    .= self::printNoteRecord($tree, ($nt > 0) ? $n1match[1] : '', 1, $noterec);
135
                    }
136
                } else {
137
                    $data = '<div class="fact_NOTE"><span class="label">' . I18N::translate('Note') . '</span>: <span class="field error">' . $nmatch[1] . '</span></div>';
138
                }
139
            }
140
        }
141
142
        return $data;
143
    }
144
145
    /**
146
     * Format age of parents in HTML
147
     *
148
     * @param Individual $person child
149
     * @param Date       $birth_date
150
     *
151
     * @return string HTML
152
     */
153
    public static function formatParentsAges(Individual $person, Date $birth_date): string
154
    {
155
        $html     = '';
156
        $families = $person->childFamilies();
157
        // Multiple sets of parents (e.g. adoption) cause complications, so ignore.
158
        if ($birth_date->isOK() && $families->count() === 1) {
159
            $family = $families->first();
160
            foreach ($family->spouses() as $parent) {
161
                if ($parent->getBirthDate()->isOK()) {
162
                    $sex      = '<small>' . view('icons/sex', ['sex' => $parent->sex()]) . '</small>';
163
                    $age      = Date::getAge($parent->getBirthDate(), $birth_date);
164
                    $deatdate = $parent->getDeathDate();
165
                    switch ($parent->sex()) {
166
                        case 'F':
167
                            // Highlight mothers who die in childbirth or shortly afterwards
168
                            if ($deatdate->isOK() && $deatdate->maximumJulianDay() < $birth_date->minimumJulianDay() + 90) {
169
                                $html .= ' <span title="' . GedcomTag::getLabel('_DEAT_PARE', $parent) . '" class="parentdeath">' . $sex . $age . '</span>';
170
                            } else {
171
                                $html .= ' <span title="' . I18N::translate('Mother’s age') . '">' . $sex . $age . '</span>';
172
                            }
173
                            break;
174
                        case 'M':
175
                            // Highlight fathers who die before the birth
176
                            if ($deatdate->isOK() && $deatdate->maximumJulianDay() < $birth_date->minimumJulianDay()) {
177
                                $html .= ' <span title="' . GedcomTag::getLabel('_DEAT_PARE', $parent) . '" class="parentdeath">' . $sex . $age . '</span>';
178
                            } else {
179
                                $html .= ' <span title="' . I18N::translate('Father’s age') . '">' . $sex . $age . '</span>';
180
                            }
181
                            break;
182
                        default:
183
                            $html .= ' <span title="' . I18N::translate('Parent’s age') . '">' . $sex . $age . '</span>';
184
                            break;
185
                    }
186
                }
187
            }
188
            if ($html) {
189
                $html = '<span class="age">' . $html . '</span>';
190
            }
191
        }
192
193
        return $html;
194
    }
195
196
    /**
197
     * Print fact DATE/TIME
198
     *
199
     * @param Fact         $event  event containing the date/age
200
     * @param GedcomRecord $record the person (or couple) whose ages should be printed
201
     * @param bool         $anchor option to print a link to calendar
202
     * @param bool         $time   option to print TIME value
203
     *
204
     * @return string
205
     */
206
    public static function formatFactDate(Fact $event, GedcomRecord $record, $anchor, $time): string
207
    {
208
        $factrec = $event->gedcom();
209
        $html    = '';
210
        // Recorded age
211
        if (preg_match('/\n2 AGE (.+)/', $factrec, $match)) {
212
            $fact_age = $match[1];
213
        } else {
214
            $fact_age = '';
215
        }
216
        if (preg_match('/\n2 HUSB\n3 AGE (.+)/', $factrec, $match)) {
217
            $husb_age = $match[1];
218
        } else {
219
            $husb_age = '';
220
        }
221
        if (preg_match('/\n2 WIFE\n3 AGE (.+)/', $factrec, $match)) {
222
            $wife_age = $match[1];
223
        } else {
224
            $wife_age = '';
225
        }
226
227
        // Calculated age
228
        $fact = $event->getTag();
229
        if (preg_match('/\n2 DATE (.+)/', $factrec, $match)) {
230
            $date = new Date($match[1]);
231
            $html .= ' ' . $date->display($anchor);
232
            // time
233
            if ($time && preg_match('/\n3 TIME (.+)/', $factrec, $match)) {
234
                $html .= ' – <span class="date">' . $match[1] . '</span>';
235
            }
236
            if ($record instanceof Individual) {
237
                if (in_array($fact, Gedcom::BIRTH_EVENTS, true) && $record->tree()->getPreference('SHOW_PARENTS_AGE')) {
238
                    // age of parents at child birth
239
                    $html .= self::formatParentsAges($record, $date);
240
                }
241
                if ($fact !== 'BIRT' && $fact !== 'CHAN' && $fact !== '_TODO') {
242
                    // age at event
243
                    $birth_date = $record->getBirthDate();
244
                    // Can't use getDeathDate(), as this also gives BURI/CREM events, which
245
                    // wouldn't give the correct "days after death" result for people with
246
                    // no DEAT.
247
                    $death_event = $record->facts(['DEAT'])->first();
248
                    if ($death_event instanceof Fact) {
249
                        $death_date = $death_event->date();
250
                    } else {
251
                        $death_date = new Date('');
252
                    }
253
                    $ageText = '';
254
                    if ($fact === 'DEAT' || (Date::compare($date, $death_date) <= 0 || !$record->isDead())) {
255
                        // Before death, print age
256
                        $age = Date::getAgeGedcom($birth_date, $date);
257
                        // Only show calculated age if it differs from recorded age
258
                        if ($age !== '' && $age !== '0d') {
259
                            if ($fact_age !== '' && $fact_age !== $age) {
260
                                $ageText = '(' . I18N::translate('Age') . ' ' . FunctionsDate::getAgeAtEvent($age) . ')';
261
                            } elseif ($fact_age === '' && $husb_age === '' && $wife_age === '') {
262
                                $ageText = '(' . I18N::translate('Age') . ' ' . FunctionsDate::getAgeAtEvent($age) . ')';
263
                            } elseif ($husb_age !== '' && $husb_age !== $age && $record->sex() === 'M') {
264
                                $ageText = '(' . I18N::translate('Age') . ' ' . FunctionsDate::getAgeAtEvent($age) . ')';
265
                            } elseif ($wife_age !== '' && $wife_age !== $age && $record->sex() === 'F') {
266
                                $ageText = '(' . I18N::translate('Age') . ' ' . FunctionsDate::getAgeAtEvent($age) . ')';
267
                            }
268
                        }
269
                    }
270
                    if ($fact !== 'DEAT' && Date::compare($date, $death_date) >= 0) {
271
                        // After death, print time since death
272
                        $age = FunctionsDate::getAgeAtEvent(Date::getAgeGedcom($death_date, $date));
273
                        if ($age !== '') {
274
                            if (Date::getAgeGedcom($death_date, $date) === '0d') {
275
                                $ageText = '(' . I18N::translate('on the date of death') . ')';
276
                            } else {
277
                                $ageText = '(' . $age . ' ' . I18N::translate('after death') . ')';
278
                                // Family events which occur after death are probably errors
279
                                if ($event->record() instanceof Family) {
280
                                    $ageText .= view('icons/warning');
281
                                }
282
                            }
283
                        }
284
                    }
285
                    if ($ageText) {
286
                        $html .= ' <span class="age">' . $ageText . '</span>';
287
                    }
288
                }
289
            }
290
        }
291
        // print gedcom ages
292
        $age_labels = [
293
            I18N::translate('Age')     => $fact_age,
294
            I18N::translate('Husband') => $husb_age,
295
            I18N::translate('Wife')    => $wife_age,
296
        ];
297
298
        foreach ($age_labels as $label => $age) {
299
            if ($age !== '') {
300
                $html .= ' <span class="label">' . $label . ':</span> <span class="age">' . FunctionsDate::getAgeAtEvent($age) . '</span>';
301
            }
302
        }
303
304
        return $html;
305
    }
306
307
    /**
308
     * print fact PLACe TEMPle STATus
309
     *
310
     * @param Fact $event       gedcom fact record
311
     * @param bool $anchor      to print a link to placelist
312
     * @param bool $sub_records to print place subrecords
313
     * @param bool $lds         to print LDS TEMPle and STATus
314
     *
315
     * @return string HTML
316
     */
317
    public static function formatFactPlace(Fact $event, $anchor = false, $sub_records = false, $lds = false): string
318
    {
319
        $tree = $event->record()->tree();
320
321
        if ($anchor) {
322
            // Show the full place name, for facts/events tab
323
            $html = $event->place()->fullName(true);
324
        } else {
325
            // Abbreviate the place name, for chart boxes
326
            return $event->place()->shortName();
327
        }
328
329
        if ($sub_records) {
330
            $placerec = Functions::getSubRecord(2, '2 PLAC', $event->gedcom());
331
            if ($placerec !== '') {
332
                if (preg_match_all('/\n3 (?:_HEB|ROMN) (.+)/', $placerec, $matches)) {
333
                    foreach ($matches[1] as $match) {
334
                        $wt_place = new Place($match, $tree);
335
                        $html     .= ' - ' . $wt_place->fullName();
336
                    }
337
                }
338
                $map_lati = '';
339
                $cts      = preg_match('/\d LATI (.*)/', $placerec, $match);
340
                if ($cts > 0) {
341
                    $map_lati = $match[1];
342
                    $html     .= '<br><span class="label">' . I18N::translate('Latitude') . ': </span>' . $map_lati;
343
                }
344
                $map_long = '';
345
                $cts      = preg_match('/\d LONG (.*)/', $placerec, $match);
346
                if ($cts > 0) {
347
                    $map_long = $match[1];
348
                    $html     .= ' <span class="label">' . I18N::translate('Longitude') . ': </span>' . $map_long;
349
                }
350
                if ($map_lati && $map_long) {
351
                    $map_lati = trim(strtr($map_lati, 'NSEW,�', ' - -. ')); // S5,6789 ==> -5.6789
352
                    $map_long = trim(strtr($map_long, 'NSEW,�', ' - -. ')); // E3.456� ==> 3.456
353
354
                    $html .= '<a href="https://maps.google.com/maps?q=' . e($map_lati) . ',' . e($map_long) . '" rel="nofollow" title="' . I18N::translate('Google Maps™') . '">' .
355
                        view('icons/google-maps') .
356
                        '<span class="sr-only">' . I18N::translate('Google Maps™') . '</span>' .
357
                        '</a>';
358
359
                    $html .= '<a href="https://www.bing.com/maps/?lvl=15&cp=' . e($map_lati) . '~' . e($map_long) . '" rel="nofollow" title="' . I18N::translate('Bing Maps™') . '">' .
360
                        view('icons/bing-maps') .
361
                        '<span class="sr-only">' . I18N::translate('Bing Maps™') . '</span>' .
362
                        '</a>';
363
364
                    $html .= '<a href="https://www.openstreetmap.org/#map=15/' . e($map_lati) . '/' . e($map_long) . '" rel="nofollow" title="' . I18N::translate('OpenStreetMap™') . '">' .
365
                        view('icons/openstreetmap') .
366
                        '<span class="sr-only">' . I18N::translate('OpenStreetMap™') . '</span>' .
367
                        '</a>';
368
                }
369
                if (preg_match('/\d NOTE (.*)/', $placerec, $match)) {
370
                    $html .= '<br>' . self::printFactNotes($tree, $placerec, 3);
371
                }
372
            }
373
        }
374
        if ($lds) {
375
            if (preg_match('/2 TEMP (.*)/', $event->gedcom(), $match)) {
376
                $html .= '<br>' . I18N::translate('LDS temple') . ': ' . GedcomCodeTemp::templeName($match[1]);
377
            }
378
            if (preg_match('/2 STAT (.*)/', $event->gedcom(), $match)) {
379
                $html .= '<br>' . I18N::translate('Status') . ': ' . GedcomCodeStat::statusName($match[1]);
380
                if (preg_match('/3 DATE (.*)/', $event->gedcom(), $match)) {
381
                    $date = new Date($match[1]);
382
                    $html .= ', ' . GedcomTag::getLabel('STAT:DATE') . ': ' . $date->display();
383
                }
384
            }
385
        }
386
387
        return $html;
388
    }
389
390
    /**
391
     * Check for facts that may exist only once for a certain record type.
392
     * If the fact already exists in the second array, delete it from the first one.
393
     *
394
     * @param string[]   $uniquefacts
395
     * @param Collection $recfacts
396
     *
397
     * @return string[]
398
     */
399
    public static function checkFactUnique(array $uniquefacts, Collection $recfacts): array
400
    {
401
        foreach ($recfacts as $factarray) {
402
            $fact = $factarray->getTag();
403
404
            $key = array_search($fact, $uniquefacts, true);
405
            if ($key !== false) {
406
                unset($uniquefacts[$key]);
407
            }
408
        }
409
410
        return $uniquefacts;
411
    }
412
413
    /**
414
     * Print a new fact box on details pages
415
     *
416
     * @param GedcomRecord $record    the person, family, source etc the fact will be added to
417
     * @param Collection   $usedfacts an array of facts already used in this record
418
     * @param string       $type      the type of record INDI, FAM, SOUR etc
419
     *
420
     * @return void
421
     */
422
    public static function printAddNewFact(GedcomRecord $record, Collection $usedfacts, $type): void
423
    {
424
        $tree = $record->tree();
425
426
        // -- Add from pick list
427
        switch ($type) {
428
            case 'INDI':
429
                $addfacts    = preg_split('/[, ;:]+/', $tree->getPreference('INDI_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
430
                $uniquefacts = preg_split('/[, ;:]+/', $tree->getPreference('INDI_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
431
                $quickfacts  = preg_split('/[, ;:]+/', $tree->getPreference('INDI_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
432
                break;
433
            case 'FAM':
434
                $addfacts    = preg_split('/[, ;:]+/', $tree->getPreference('FAM_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
435
                $uniquefacts = preg_split('/[, ;:]+/', $tree->getPreference('FAM_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
436
                $quickfacts  = preg_split('/[, ;:]+/', $tree->getPreference('FAM_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
437
                break;
438
            case 'SOUR':
439
                $addfacts    = preg_split('/[, ;:]+/', $tree->getPreference('SOUR_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
440
                $uniquefacts = preg_split('/[, ;:]+/', $tree->getPreference('SOUR_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
441
                $quickfacts  = preg_split('/[, ;:]+/', $tree->getPreference('SOUR_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
442
                break;
443
            case 'NOTE':
444
                $addfacts    = preg_split('/[, ;:]+/', $tree->getPreference('NOTE_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
445
                $uniquefacts = preg_split('/[, ;:]+/', $tree->getPreference('NOTE_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
446
                $quickfacts  = preg_split('/[, ;:]+/', $tree->getPreference('NOTE_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
447
                break;
448
            case 'REPO':
449
                $addfacts    = preg_split('/[, ;:]+/', $tree->getPreference('REPO_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
450
                $uniquefacts = preg_split('/[, ;:]+/', $tree->getPreference('REPO_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
451
                $quickfacts  = preg_split('/[, ;:]+/', $tree->getPreference('REPO_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
452
                break;
453
            case 'OBJE':
454
                $addfacts    = ['NOTE'];
455
                $uniquefacts = ['_PRIM'];
456
                $quickfacts  = [];
457
                break;
458
            default:
459
                return;
460
        }
461
        $addfacts            = array_merge(self::checkFactUnique($uniquefacts, $usedfacts), $addfacts);
462
        $quickfacts          = array_intersect($quickfacts, $addfacts);
463
        $translated_addfacts = [];
464
        foreach ($addfacts as $addfact) {
465
            $translated_addfacts[$addfact] = GedcomTag::getLabel($addfact);
466
        }
467
        uasort($translated_addfacts, static function (string $x, string $y): int {
468
            return I18N::strcasecmp(I18N::translate($x), I18N::translate($y));
469
        });
470
471
        echo view('edit/add-fact-row', [
472
            'add_facts'   => $translated_addfacts,
473
            'quick_facts' => $quickfacts,
474
            'record'      => $record,
475
            'tree'        => $tree,
476
        ]);
477
    }
478
}
479