FunctionsEdit   F
last analyzed

Complexity

Total Complexity 377

Size/Duplication

Total Lines 1793
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 911
dl 0
loc 1793
rs 1.689
c 0
b 0
f 0
wmc 377

37 Methods

Rating   Name   Duplication   Size   Complexity  
A editLanguageCheckboxes() 0 14 3
A addSimpleTags() 0 26 6
A editFieldInteger() 0 8 2
D handleUpdates() 0 68 21
C printAddLayer() 0 96 11
A editFieldPedigree() 0 3 1
A updateRest() 0 29 2
A printAddNewRepositoryLink() 0 3 1
A printAddNewSourceLink() 0 3 1
A editFieldAdoption() 0 3 1
C addNewFact() 0 46 17
A editFieldNameType() 0 3 1
A printCalendarPopup() 0 5 1
A printAddNewMediaLink() 0 3 1
A twoStateCheckbox() 0 7 3
A updateSource() 0 29 2
A printEditNoteLink() 0 3 1
A checkbox() 0 3 2
A addNewSex() 0 9 3
A radioButtons() 0 15 3
A editFieldUsername() 0 12 4
A editFieldYesNo() 0 4 1
A editFieldRelationship() 0 9 2
A printAddNewNoteLink() 0 3 1
B addNewName() 0 26 7
A editFieldRestriction() 0 11 1
A editFieldContact() 0 12 1
A editFieldLanguage() 0 8 2
B selectEditControl() 0 28 8
C censusDateSelector() 0 77 13
A removeLinks() 0 9 1
A editFieldAccessLevel() 0 10 1
B splitSource() 0 45 6
F addSimpleTag() 0 475 164
D createEditForm() 0 137 32
D insertMissingSubtags() 0 87 43
B createAddForm() 0 34 7

How to fix   Complexity   

Complex Class

Complex classes like FunctionsEdit 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 FunctionsEdit, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * webtrees: online genealogy
4
 * Copyright (C) 2019 webtrees development team
5
 * This program is free software: you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation, either version 3 of the License, or
8
 * (at your option) any later version.
9
 * This program is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
 * GNU General Public License for more details.
13
 * You should have received a copy of the GNU General Public License
14
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15
 */
16
namespace Fisharebest\Webtrees\Functions;
17
18
use Fisharebest\Webtrees\Auth;
19
use Fisharebest\Webtrees\Census\Census;
20
use Fisharebest\Webtrees\Census\CensusOfCzechRepublic;
21
use Fisharebest\Webtrees\Census\CensusOfDenmark;
22
use Fisharebest\Webtrees\Census\CensusOfDeutschland;
23
use Fisharebest\Webtrees\Census\CensusOfEngland;
24
use Fisharebest\Webtrees\Census\CensusOfFrance;
25
use Fisharebest\Webtrees\Census\CensusOfScotland;
26
use Fisharebest\Webtrees\Census\CensusOfUnitedStates;
27
use Fisharebest\Webtrees\Census\CensusOfWales;
28
use Fisharebest\Webtrees\Config;
29
use Fisharebest\Webtrees\Date;
30
use Fisharebest\Webtrees\Fact;
31
use Fisharebest\Webtrees\Family;
32
use Fisharebest\Webtrees\Filter;
33
use Fisharebest\Webtrees\GedcomCode\GedcomCodeAdop;
34
use Fisharebest\Webtrees\GedcomCode\GedcomCodeName;
35
use Fisharebest\Webtrees\GedcomCode\GedcomCodePedi;
36
use Fisharebest\Webtrees\GedcomCode\GedcomCodeQuay;
37
use Fisharebest\Webtrees\GedcomCode\GedcomCodeRela;
38
use Fisharebest\Webtrees\GedcomCode\GedcomCodeStat;
39
use Fisharebest\Webtrees\GedcomCode\GedcomCodeTemp;
40
use Fisharebest\Webtrees\GedcomRecord;
41
use Fisharebest\Webtrees\GedcomTag;
42
use Fisharebest\Webtrees\I18N;
43
use Fisharebest\Webtrees\Individual;
44
use Fisharebest\Webtrees\Media;
45
use Fisharebest\Webtrees\Module;
46
use Fisharebest\Webtrees\Note;
47
use Fisharebest\Webtrees\Repository;
48
use Fisharebest\Webtrees\Source;
49
use Fisharebest\Webtrees\User;
50
use Rhumsaa\Uuid\Uuid;
51
52
/**
53
 * Class FunctionsEdit - common functions
54
 */
55
class FunctionsEdit
56
{
57
    /**
58
     * Create a <select> control for a form.
59
     *
60
     * @param string $name
61
     * @param string[] $values
62
     * @param string|null $empty
63
     * @param string $selected
64
     * @param string $extra
65
     *
66
     * @return string
67
     */
68
    public static function selectEditControl($name, $values, $empty, $selected, $extra = '')
69
    {
70
        if ($empty === null) {
71
            $html = '';
72
        } else {
73
            if (empty($selected)) {
74
                $html = '<option value="" selected>' . Filter::escapeHtml($empty) . '</option>';
75
            } else {
76
                $html = '<option value="">' . Filter::escapeHtml($empty) . '</option>';
77
            }
78
        }
79
        // A completely empty list would be invalid, and break various things
80
        if (empty($values) && empty($html)) {
81
            $html = '<option value=""></option>';
82
        }
83
        foreach ($values as $key => $value) {
84
            // PHP array keys are cast to integers!  Cast them back
85
            if ((string) $key === (string) $selected) {
86
                $html .= '<option value="' . Filter::escapeHtml($key) . '" selected dir="auto">' . Filter::escapeHtml($value) . '</option>';
87
            } else {
88
                $html .= '<option value="' . Filter::escapeHtml($key) . '" dir="auto">' . Filter::escapeHtml($value) . '</option>';
89
            }
90
        }
91
        if (substr($name, -2) === '[]') {
92
            // id attribute is not used for arrays
93
            return '<select name="' . $name . '" ' . $extra . '>' . $html . '</select>';
94
        } else {
95
            return '<select id="' . $name . '" name="' . $name . '" ' . $extra . '>' . $html . '</select>';
96
        }
97
    }
98
99
    /**
100
     * Create a set of radio buttons for a form
101
     *
102
     * @param string $name The ID for the form element
103
     * @param string[] $values Array of value=>display items
104
     * @param string $selected The currently selected item
105
     * @param string $extra Additional markup for the label
106
     *
107
     * @return string
108
     */
109
    public static function radioButtons($name, $values, $selected, $extra = '')
110
    {
111
        $html = '';
112
        foreach ($values as $key => $value) {
113
            $html .=
114
                '<label ' . $extra . '>' .
115
                '<input type="radio" name="' . $name . '" value="' . Filter::escapeHtml($key) . '"';
116
            // PHP array keys are cast to integers!  Cast them back
117
            if ((string) $key === (string) $selected) {
118
                $html .= ' checked';
119
            }
120
            $html .= '>' . Filter::escapeHtml($value) . '</label>';
121
        }
122
123
        return $html;
124
    }
125
126
    /**
127
     * Print an edit control for a Yes/No field
128
     *
129
     * @param string $name
130
     * @param bool $selected
131
     * @param string $extra
132
     *
133
     * @return string
134
     */
135
    public static function editFieldYesNo($name, $selected = false, $extra = '')
136
    {
137
        return self::radioButtons(
138
            $name, array(I18N::translate('no'), I18N::translate('yes')), $selected, $extra
139
        );
140
    }
141
142
    /**
143
     * Print an edit control for a checkbox.
144
     *
145
     * @param string $name
146
     * @param bool $is_checked
147
     * @param string $extra
148
     *
149
     * @return string
150
     */
151
    public static function checkbox($name, $is_checked = false, $extra = '')
152
    {
153
        return '<input type="checkbox" name="' . $name . '" value="1" ' . ($is_checked ? 'checked ' : '') . $extra . '>';
154
    }
155
156
    /**
157
     * Print an edit control for a checkbox, with a hidden field to store one of the two states.
158
     * By default, a checkbox is either set, or not sent.
159
     * This function gives us a three options, set, unset or not sent.
160
     * Useful for dynamically generated forms where we don't know what elements are present.
161
     *
162
     * @param string $name
163
     * @param int $is_checked 0 or 1
164
     * @param string $extra
165
     *
166
     * @return string
167
     */
168
    public static function twoStateCheckbox($name, $is_checked = 0, $extra = '')
169
    {
170
        return
171
            '<input type="hidden" id="' . $name . '" name="' . $name . '" value="' . ($is_checked ? 1 : 0) . '">' .
172
            '<input type="checkbox" name="' . $name . '-GUI-ONLY" value="1"' .
173
            ($is_checked ? ' checked' : '') .
174
            ' onclick="document.getElementById(\'' . $name . '\').value=(this.checked?1:0);" ' . $extra . '>';
175
    }
176
177
    /**
178
     * Function edit_language_checkboxes
179
     *
180
     * @param string $parameter_name
181
     * @param array $accepted_languages
182
     *
183
     * @return string
184
     */
185
    public static function editLanguageCheckboxes($parameter_name, $accepted_languages)
186
    {
187
        $html = '';
188
        foreach (I18N::activeLocales() as $locale) {
189
            $html .= '<div class="checkbox">';
190
            $html .= '<label title="' . $locale->languageTag() . '">';
191
            $html .= '<input type="checkbox" name="' . $parameter_name . '[]" value="' . $locale->languageTag() . '"';
192
            $html .= in_array($locale->languageTag(), $accepted_languages) ? ' checked>' : '>';
193
            $html .= $locale->endonym();
194
            $html .= '</label>';
195
            $html .= '</div>';
196
        }
197
198
        return $html;
199
    }
200
201
    /**
202
     * Print an edit control for access level.
203
     *
204
     * @param string $name
205
     * @param string $selected
206
     * @param string $extra
207
     *
208
     * @return string
209
     */
210
    public static function editFieldAccessLevel($name, $selected = '', $extra = '')
211
    {
212
        $ACCESS_LEVEL = array(
213
            Auth::PRIV_PRIVATE => I18N::translate('Show to visitors'),
214
            Auth::PRIV_USER    => I18N::translate('Show to members'),
215
            Auth::PRIV_NONE    => I18N::translate('Show to managers'),
216
            Auth::PRIV_HIDE    => I18N::translate('Hide from everyone'),
217
        );
218
219
        return self::selectEditControl($name, $ACCESS_LEVEL, null, $selected, $extra);
220
    }
221
222
    /**
223
     * Print an edit control for a RESN field.
224
     *
225
     * @param string $name
226
     * @param string $selected
227
     * @param string $extra
228
     *
229
     * @return string
230
     */
231
    public static function editFieldRestriction($name, $selected = '', $extra = '')
232
    {
233
        $RESN = array(
234
            ''             => '',
235
            'none'         => I18N::translate('Show to visitors'), // Not valid GEDCOM, but very useful
236
            'privacy'      => I18N::translate('Show to members'),
237
            'confidential' => I18N::translate('Show to managers'),
238
            'locked'       => I18N::translate('Only managers can edit'),
239
        );
240
241
        return self::selectEditControl($name, $RESN, null, $selected, $extra);
242
    }
243
244
    /**
245
     * Print an edit control for a contact method field.
246
     *
247
     * @param string $name
248
     * @param string $selected
249
     * @param string $extra
250
     *
251
     * @return string
252
     */
253
    public static function editFieldContact($name, $selected = '', $extra = '')
254
    {
255
        // Different ways to contact the users
256
        $CONTACT_METHODS = array(
257
            'messaging'  => I18N::translate('Internal messaging'),
258
            'messaging2' => I18N::translate('Internal messaging with emails'),
259
            'messaging3' => I18N::translate('webtrees sends emails with no storage'),
260
            'mailto'     => I18N::translate('Mailto link'),
261
            'none'       => I18N::translate('No contact'),
262
        );
263
264
        return self::selectEditControl($name, $CONTACT_METHODS, null, $selected, $extra);
265
    }
266
267
    /**
268
     * Print an edit control for a language field.
269
     *
270
     * @param string $name
271
     * @param string $selected
272
     * @param string $extra
273
     *
274
     * @return string
275
     */
276
    public static function editFieldLanguage($name, $selected = '', $extra = '')
277
    {
278
        $languages = array();
279
        foreach (I18N::activeLocales() as $locale) {
280
            $languages[$locale->languageTag()] = $locale->endonym();
281
        }
282
283
        return self::selectEditControl($name, $languages, null, $selected, $extra);
284
    }
285
286
    /**
287
     * Print an edit control for a range of integers.
288
     *
289
     * @param string $name
290
     * @param string $selected
291
     * @param int $min
292
     * @param int $max
293
     * @param string $extra
294
     *
295
     * @return string
296
     */
297
    public static function editFieldInteger($name, $selected = '', $min, $max, $extra = '')
298
    {
299
        $array = array();
300
        for ($i = $min; $i <= $max; ++$i) {
301
            $array[$i] = I18N::number($i);
302
        }
303
304
        return self::selectEditControl($name, $array, null, $selected, $extra);
305
    }
306
307
    /**
308
     * Print an edit control for a username.
309
     *
310
     * @param string $name
311
     * @param string $selected
312
     * @param string $extra
313
     *
314
     * @return string
315
     */
316
    public static function editFieldUsername($name, $selected = '', $extra = '')
317
    {
318
        $users = array();
319
        foreach (User::all() as $user) {
320
            $users[$user->getUserName()] = $user->getRealName() . ' - ' . $user->getUserName();
321
        }
322
        // The currently selected user may not exist
323
        if ($selected && !array_key_exists($selected, $users)) {
324
            $users[$selected] = $selected;
325
        }
326
327
        return self::selectEditControl($name, $users, '-', $selected, $extra);
328
    }
329
330
    /**
331
     * Print an edit control for a ADOP field.
332
     *
333
     * @param string          $name
334
     * @param string          $selected
335
     * @param string          $extra
336
     * @param Individual|null $individual
337
     *
338
     * @return string
339
     */
340
    public static function editFieldAdoption($name, $selected = '', $extra = '', Individual $individual = null)
341
    {
342
        return self::selectEditControl($name, GedcomCodeAdop::getValues($individual), null, $selected, $extra);
343
    }
344
345
    /**
346
     * Print an edit control for a PEDI field.
347
     *
348
     * @param string          $name
349
     * @param string          $selected
350
     * @param string          $extra
351
     * @param Individual|null $individual
352
     *
353
     * @return string
354
     */
355
    public static function editFieldPedigree($name, $selected = '', $extra = '', Individual $individual = null)
356
    {
357
        return self::selectEditControl($name, GedcomCodePedi::getValues($individual), '', $selected, $extra);
358
    }
359
360
    /**
361
     * Print an edit control for a NAME TYPE field.
362
     *
363
     * @param string          $name
364
     * @param string          $selected
365
     * @param string          $extra
366
     * @param Individual|null $individual
367
     *
368
     * @return string
369
     */
370
    public static function editFieldNameType($name, $selected = '', $extra = '', Individual $individual = null)
371
    {
372
        return self::selectEditControl($name, GedcomCodeName::getValues($individual), '', $selected, $extra);
373
    }
374
375
    /**
376
     * Print an edit control for a RELA field.
377
     *
378
     * @param string $name
379
     * @param string $selected
380
     * @param string $extra
381
     *
382
     * @return string
383
     */
384
    public static function editFieldRelationship($name, $selected = '', $extra = '')
385
    {
386
        $rela_codes = GedcomCodeRela::getValues();
387
        // The user is allowed to specify values that aren't in the list.
388
        if (!array_key_exists($selected, $rela_codes)) {
389
            $rela_codes[$selected] = I18N::translate($selected);
390
        }
391
392
        return self::selectEditControl($name, $rela_codes, '', $selected, $extra);
393
    }
394
395
    /**
396
     * Remove all links from $gedrec to $xref, and any sub-tags.
397
     *
398
     * @param string $gedrec
399
     * @param string $xref
400
     *
401
     * @return string
402
     */
403
    public static function removeLinks($gedrec, $xref)
404
    {
405
        $gedrec = preg_replace('/\n1 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[2-9].*)*/', '', $gedrec);
406
        $gedrec = preg_replace('/\n2 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[3-9].*)*/', '', $gedrec);
407
        $gedrec = preg_replace('/\n3 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[4-9].*)*/', '', $gedrec);
408
        $gedrec = preg_replace('/\n4 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[5-9].*)*/', '', $gedrec);
409
        $gedrec = preg_replace('/\n5 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[6-9].*)*/', '', $gedrec);
410
411
        return $gedrec;
412
    }
413
414
    /**
415
     * Generates javascript code for calendar popup in user’s language.
416
     *
417
     * @param string $id
418
     *
419
     * @return string
420
     */
421
    public static function printCalendarPopup($id)
422
    {
423
        return
424
            ' <a href="#" onclick="cal_toggleDate(\'caldiv' . $id . '\', \'' . $id . '\'); return false;" class="icon-button_calendar" title="' . I18N::translate('Select a date') . '"></a>' .
425
            '<div id="caldiv' . $id . '" style="position:absolute;visibility:hidden;background-color:white;z-index:1000;"></div>';
426
    }
427
428
    /**
429
     * An HTML link to create a new media object.
430
     *
431
     * @param string $element_id
432
     *
433
     * @return string
434
     */
435
    public static function printAddNewMediaLink($element_id)
436
    {
437
        return '<a href="#" onclick="pastefield=document.getElementById(\'' . $element_id . '\'); window.open(\'addmedia.php?action=showmediaform\', \'_blank\', edit_window_specs); return false;" class="icon-button_addmedia" title="' . I18N::translate('Create a media object') . '"></a>';
438
    }
439
440
    /**
441
     * An HTML link to create a new repository.
442
     *
443
     * @param string $element_id
444
     *
445
     * @return string
446
     */
447
    public static function printAddNewRepositoryLink($element_id)
448
    {
449
        return '<a href="#" onclick="addnewrepository(document.getElementById(\'' . $element_id . '\')); return false;" class="icon-button_addrepository" title="' . I18N::translate('Create a repository') . '"></a>';
450
    }
451
452
    /**
453
     * An HTML link to create a new note.
454
     *
455
     * @param string $element_id
456
     *
457
     * @return string
458
     */
459
    public static function printAddNewNoteLink($element_id)
460
    {
461
        return '<a href="#" onclick="addnewnote(document.getElementById(\'' . $element_id . '\')); return false;" class="icon-button_addnote" title="' . I18N::translate('Create a shared note') . '"></a>';
462
    }
463
464
    /**
465
     * An HTML link to edit a note.
466
     *
467
     * @param string $note_id
468
     *
469
     * @return string
470
     */
471
    public static function printEditNoteLink($note_id)
472
    {
473
        return '<a href="#" onclick="edit_note(\'' . $note_id . '\'); return false;" class="icon-button_note" title="' . I18N::translate('Edit the shared note') . '"></a>';
474
    }
475
476
    /**
477
     * An HTML link to create a new source.
478
     *
479
     * @param string $element_id
480
     *
481
     * @return string
482
     */
483
    public static function printAddNewSourceLink($element_id)
484
    {
485
        return '<a href="#" onclick="addnewsource(document.getElementById(\'' . $element_id . '\')); return false;" class="icon-button_addsource" title="' . I18N::translate('Create a source') . '"></a>';
486
    }
487
488
    /**
489
     * add a new tag input field
490
     *
491
     * called for each fact to be edited on a form.
492
     * Fact level=0 means a new empty form : data are POSTed by name
493
     * else data are POSTed using arrays :
494
     * glevels[] : tag level
495
     *  islink[] : tag is a link
496
     *     tag[] : tag name
497
     *    text[] : tag value
498
     *
499
     * @param string $tag fact record to edit (eg 2 DATE xxxxx)
500
     * @param string $upperlevel optional upper level tag (eg BIRT)
501
     * @param string $label An optional label to echo instead of the default
502
     * @param string $extra optional text to display after the input field
503
     * @param Individual $person For male/female translations
504
     *
505
     * @return string
506
     */
507
    public static function addSimpleTag($tag, $upperlevel = '', $label = '', $extra = null, Individual $person = null)
508
    {
509
        global $tags, $main_fact, $xref, $bdm, $action, $WT_TREE;
510
511
        // Keep track of SOUR fields, so we can reference them in subsequent PAGE fields.
512
        static $source_element_id;
513
514
        $subnamefacts = array('NPFX', 'GIVN', 'SPFX', 'SURN', 'NSFX', '_MARNM_SURN');
515
        preg_match('/^(?:(\d+) (' . WT_REGEX_TAG . ') ?(.*))/', $tag, $match);
516
        list(, $level, $fact, $value) = $match;
517
        $level                        = (int) $level;
518
519
        // element name : used to POST data
520
        if ($level === 0) {
521
            if ($upperlevel) {
522
                $element_name = $upperlevel . '_' . $fact;
523
            } else {
524
                $element_name = $fact;
525
            }
526
        } else {
527
            $element_name = 'text[]';
528
        }
529
        if ($level === 1) {
530
            $main_fact = $fact;
531
        }
532
533
        // element id : used by javascript functions
534
        if ($level === 0) {
535
            $element_id = $fact;
536
        } else {
537
            $element_id = $fact . Uuid::uuid4();
538
        }
539
        if ($upperlevel) {
540
            $element_id = $upperlevel . '_' . $fact . Uuid::uuid4();
541
        }
542
543
        // field value
544
        $islink = (substr($value, 0, 1) === '@' && substr($value, 0, 2) !== '@#');
545
        if ($islink) {
546
            $value = trim(substr($tag, strlen($fact) + 3), '@');
547
        } else {
548
            $value = (string) substr($tag, strlen($fact) + 3);
549
        }
550
        if ($fact === 'REPO' || $fact === 'SOUR' || $fact === 'OBJE' || $fact === 'FAMC') {
551
            $islink = true;
552
        }
553
554
        if ($fact === 'SHARED_NOTE_EDIT' || $fact === 'SHARED_NOTE') {
555
            $islink = true;
556
            $fact   = 'NOTE';
557
        }
558
559
        // label
560
        echo '<tr id="', $element_id, '_tr"';
561
        if ($fact === 'DATA' || $fact === 'MAP' || ($fact === 'LATI' || $fact === 'LONG') && $value === '') {
562
            echo ' style="display:none;"';
563
        }
564
        echo '>';
565
566
        if (in_array($fact, $subnamefacts) || $fact === 'LATI' || $fact === 'LONG') {
567
            echo '<td class="optionbox wrap width25">';
568
        } else {
569
            echo '<td class="descriptionbox wrap width25">';
570
        }
571
572
        // tag name
573
        if ($label) {
574
            echo $label;
575
        } elseif ($upperlevel) {
576
            echo GedcomTag::getLabel($upperlevel . ':' . $fact);
577
        } else {
578
            echo GedcomTag::getLabel($fact);
579
        }
580
581
        // If using GEDFact-assistant window
582
        if ($action === 'addnewnote_assisted') {
583
            // Do not print on GEDFact Assistant window
584
        } else {
585
            // Not all facts have help text.
586
            switch ($fact) {
587
                case 'NAME':
588
                    if ($upperlevel !== 'REPO' && $upperlevel !== 'UNKNOWN') {
589
                        echo FunctionsPrint::helpLink($fact);
590
                    }
591
                    break;
592
                case 'DATE':
593
                case 'PLAC':
594
                case 'RESN':
595
                case 'ROMN':
596
                case 'SURN':
597
                case '_HEB':
598
                    echo FunctionsPrint::helpLink($fact);
599
                    break;
600
            }
601
        }
602
        // tag level
603
        if ($level > 0) {
604
            echo '<input type="hidden" name="glevels[]" value="', $level, '">';
605
            echo '<input type="hidden" name="islink[]" value="', $islink, '">';
606
            echo '<input type="hidden" name="tag[]" value="', $fact, '">';
607
        }
608
        echo '</td>';
609
610
        // value
611
        echo '<td class="optionbox wrap">';
612
613
        // retrieve linked NOTE
614
        if ($fact === 'NOTE' && $islink) {
615
            $note1 = Note::getInstance($value, $WT_TREE);
616
            if ($note1) {
617
                $noterec = $note1->getGedcom();
618
                preg_match('/' . $value . '/i', $noterec, $notematch);
619
                $value = $notematch[0];
620
            }
621
        }
622
623
        // Show names for spouses in MARR/HUSB/AGE and MARR/WIFE/AGE
624
        if ($fact === 'HUSB' || $fact === 'WIFE') {
625
            $family = Family::getInstance($xref, $WT_TREE);
626
            if ($family) {
627
                $spouse_link = $family->getFirstFact($fact);
628
                if ($spouse_link) {
629
                    $spouse = $spouse_link->getTarget();
630
                    if ($spouse) {
631
                        echo $spouse->getFullName();
632
                    }
633
                }
634
            }
635
        }
636
637
        if (in_array($fact, Config::emptyFacts()) && ($value === '' || $value === 'Y' || $value === 'y')) {
638
            echo '<input type="hidden" id="', $element_id, '" name="', $element_name, '" value="', $value, '">';
639
            if ($level <= 1) {
640
                echo '<input type="checkbox" ';
641
                if ($value) {
642
                    echo 'checked';
643
                }
644
                echo ' onclick="document.getElementById(\'' . $element_id . '\').value = (this.checked) ? \'Y\' : \'\';">';
645
                echo I18N::translate('yes');
646
            }
647
648
            if ($fact === 'CENS' && $value === 'Y') {
649
                echo self::censusDateSelector(WT_LOCALE, $xref);
650
                if (Module::getModuleByName('GEDFact_assistant') && GedcomRecord::getInstance($xref, $WT_TREE) instanceof Individual) {
651
                    echo
652
                        '<div></div><a href="#" style="display: none;" id="assistant-link" onclick="return activateCensusAssistant();">' .
653
                        I18N::translate('Create a shared note using the census assistant') .
654
                        '</a></div>';
655
                }
656
            }
657
        } elseif ($fact === 'TEMP') {
658
            echo self::selectEditControl($element_name, GedcomCodeTemp::templeNames(), I18N::translate('No temple - living ordinance'), $value);
659
        } elseif ($fact === 'ADOP') {
660
            echo self::editFieldAdoption($element_name, $value, '', $person);
661
        } elseif ($fact === 'PEDI') {
662
            echo self::editFieldPedigree($element_name, $value, '', $person);
663
        } elseif ($fact === 'STAT') {
664
            echo self::selectEditControl($element_name, GedcomCodeStat::statusNames($upperlevel), '', $value);
665
        } elseif ($fact === 'RELA') {
666
            echo self::editFieldRelationship($element_name, strtolower($value));
667
        } elseif ($fact === 'QUAY') {
668
            echo self::selectEditControl($element_name, GedcomCodeQuay::getValues(), '', $value);
669
        } elseif ($fact === '_WT_USER') {
670
            echo self::editFieldUsername($element_name, $value);
671
        } elseif ($fact === 'RESN') {
672
            echo self::editFieldRestriction($element_name, $value);
673
        } elseif ($fact === '_PRIM') {
674
            echo '<select id="', $element_id, '" name="', $element_name, '" >';
675
            echo '<option value=""></option>';
676
            echo '<option value="Y" ';
677
            if ($value === 'Y') {
678
                echo ' selected';
679
            }
680
            echo '>', /* I18N: option in list box “always use this image” */
681
            I18N::translate('always'), '</option>';
682
            echo '<option value="N" ';
683
            if ($value === 'N') {
684
                echo 'selected';
685
            }
686
            echo '>', /* I18N: option in list box “never use this image” */
687
            I18N::translate('never'), '</option>';
688
            echo '</select>';
689
            echo '<p class="small text-muted">', I18N::translate('Use this image for charts and on the individual’s page.'), '</p>';
690
        } elseif ($fact === 'SEX') {
691
            echo '<select id="', $element_id, '" name="', $element_name, '"><option value="M" ';
692
            if ($value === 'M') {
693
                echo 'selected';
694
            }
695
            echo '>', I18N::translate('Male'), '</option><option value="F" ';
696
            if ($value === 'F') {
697
                echo 'selected';
698
            }
699
            echo '>', I18N::translate('Female'), '</option><option value="U" ';
700
            if ($value === 'U' || empty($value)) {
701
                echo 'selected';
702
            }
703
            echo '>', I18N::translateContext('unknown gender', 'Unknown'), '</option></select>';
704
        } elseif ($fact === 'TYPE' && $level === 3) {
705
            //-- Build the selector for the Media 'TYPE' Fact
706
            echo '<select name="text[]"><option selected value="" ></option>';
707
            $selectedValue = strtolower($value);
708
            if (!array_key_exists($selectedValue, GedcomTag::getFileFormTypes())) {
709
                echo '<option selected value="', Filter::escapeHtml($value), '" >', Filter::escapeHtml($value), '</option>';
710
            }
711
            foreach (GedcomTag::getFileFormTypes() as $typeName => $typeValue) {
712
                echo '<option value="', $typeName, '" ';
713
                if ($selectedValue === $typeName) {
714
                    echo 'selected';
715
                }
716
                echo '>', $typeValue, '</option>';
717
            }
718
            echo '</select>';
719
        } elseif (($fact === 'NAME' && $upperlevel !== 'REPO' && $upperlevel !== 'UNKNOWN') || $fact === '_MARNM') {
720
            // Populated in javascript from sub-tags
721
            echo '<input type="hidden" id="', $element_id, '" name="', $element_name, '" onchange="updateTextName(\'', $element_id, '\');" value="', Filter::escapeHtml($value), '" class="', $fact, '">';
722
            echo '<span id="', $element_id, '_display" dir="auto">', Filter::escapeHtml($value), '</span>';
723
            echo ' <a href="#edit_name" onclick="convertHidden(\'', $element_id, '\'); return false;" class="icon-edit_indi" title="' . I18N::translate('Edit the name') . '"></a>';
724
        } else {
725
            // textarea
726
            if ($fact === 'TEXT' || $fact === 'ADDR' || ($fact === 'NOTE' && !$islink)) {
727
                echo '<textarea id="', $element_id, '" name="', $element_name, '" dir="auto">', Filter::escapeHtml($value), '</textarea><br>';
728
            } else {
729
                // text
730
                // If using GEDFact-assistant window
731
                if ($action === 'addnewnote_assisted') {
732
                    echo '<input type="text" id="', $element_id, '" name="', $element_name, '" value="', Filter::escapeHtml($value), '" style="width:4.1em;" dir="ltr"';
733
                } else {
734
                    echo '<input type="text" id="', $element_id, '" name="', $element_name, '" value="', Filter::escapeHtml($value), '" dir="ltr"';
735
                }
736
                echo ' class="', $fact, '"';
737
                if (in_array($fact, $subnamefacts)) {
738
                    echo ' onblur="updatewholename();" onkeyup="updatewholename();"';
739
                }
740
741
                // Extra markup for specific fact types
742
                switch ($fact) {
743
                    case 'ALIA':
744
                    case 'ASSO':
745
                    case '_ASSO':
746
                        echo ' data-autocomplete-type="ASSO" data-autocomplete-extra="input.DATE"';
747
                        break;
748
                    case 'DATE':
749
                        echo ' onblur="valid_date(this);" onmouseout="valid_date(this);"';
750
                        break;
751
                    case 'GIVN':
752
                        echo ' autofocus data-autocomplete-type="GIVN"';
753
                        break;
754
                    case 'LATI':
755
                        echo ' onblur="valid_lati_long(this, \'N\', \'S\');" onmouseout="valid_lati_long(this, \'N\', \'S\');"';
756
                        break;
757
                    case 'LONG':
758
                        echo ' onblur="valid_lati_long(this, \'E\', \'W\');" onmouseout="valid_lati_long(this, \'E\', \'W\');"';
759
                        break;
760
                    case 'NOTE':
761
                        // Shared notes. Inline notes are handled elsewhere.
762
                        echo ' data-autocomplete-type="NOTE"';
763
                        break;
764
                    case 'OBJE':
765
                        echo ' data-autocomplete-type="OBJE"';
766
                        break;
767
                    case 'PAGE':
768
                        echo ' data-autocomplete-type="PAGE" data-autocomplete-extra="#' . $source_element_id . '"';
769
                        break;
770
                    case 'PLAC':
771
                        echo ' data-autocomplete-type="PLAC"';
772
                        break;
773
                    case 'REPO':
774
                        echo ' data-autocomplete-type="REPO"';
775
                        break;
776
                    case 'SOUR':
777
                        $source_element_id = $element_id;
778
                        echo ' data-autocomplete-type="SOUR"';
779
                        break;
780
                    case 'SURN':
781
                    case '_MARNM_SURN':
782
                        echo ' data-autocomplete-type="SURN"';
783
                        break;
784
                    case 'TIME':
785
                        echo ' pattern="([0-1][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])?" dir="ltr" placeholder="' . /* I18N: Examples of valid time formats (hours:minutes:seconds) */
786
                        I18N::translate('hh:mm or hh:mm:ss') . '"';
787
                        break;
788
                }
789
                echo '>';
790
            }
791
792
            $tmp_array = array('TYPE', 'TIME', 'NOTE', 'SOUR', 'REPO', 'OBJE', 'ASSO', '_ASSO', 'AGE');
793
794
            // split PLAC
795
            if ($fact === 'PLAC') {
796
                echo '<div id="', $element_id, '_pop" style="display: inline;">';
797
                echo FunctionsPrint::printSpecialCharacterLink($element_id), ' ', FunctionsPrint::printFindPlaceLink($element_id);
798
                echo '<span  onclick="jQuery(\'tr[id^=', $upperlevel, '_LATI],tr[id^=', $upperlevel, '_LONG],tr[id^=LATI],tr[id^=LONG]\').toggle(\'fast\'); return false;" class="icon-target" title="', GedcomTag::getLabel('LATI'), ' / ', GedcomTag::getLabel('LONG'), '"></span>';
799
                echo '</div>';
800
                if (Module::getModuleByName('places_assistant')) {
801
                    \PlacesAssistantModule::setup_place_subfields($element_id);
0 ignored issues
show
Bug introduced by
The type PlacesAssistantModule was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
802
                    \PlacesAssistantModule::print_place_subfields($element_id);
803
                }
804
            } elseif (!in_array($fact, $tmp_array)) {
805
                echo FunctionsPrint::printSpecialCharacterLink($element_id);
806
            }
807
        }
808
        // MARRiage TYPE : hide text field and show a selection list
809
        if ($fact === 'TYPE' && $level === 2 && $tags[0] === 'MARR') {
810
            echo '<script>';
811
            echo 'document.getElementById(\'', $element_id, '\').style.display=\'none\'';
812
            echo '</script>';
813
            echo '<select id="', $element_id, '_sel" onchange="document.getElementById(\'', $element_id, '\').value=this.value;" >';
814
            foreach (array('Unknown', 'Civil', 'Religious', 'Partners') as $key) {
815
                if ($key === 'Unknown') {
816
                    echo '<option value="" ';
817
                } else {
818
                    echo '<option value="', $key, '" ';
819
                }
820
                $a = strtolower($key);
821
                $b = strtolower($value);
822
                if ($b !== '' && strpos($a, $b) !== false || strpos($b, $a) !== false) {
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($b !== '' && strpos($a,...trpos($b, $a) !== false, Probably Intended Meaning: $b !== '' && (strpos($a,...rpos($b, $a) !== false)
Loading history...
823
                    echo 'selected';
824
                }
825
                echo '>', GedcomTag::getLabel('MARR_' . strtoupper($key)), '</option>';
826
            }
827
            echo '</select>';
828
        } elseif ($fact === 'TYPE' && $level === 0) {
829
            // NAME TYPE : hide text field and show a selection list
830
            $onchange = 'onchange="document.getElementById(\'' . $element_id . '\').value=this.value;"';
831
            echo self::editFieldNameType($element_name, $value, $onchange, $person);
832
            echo '<script>document.getElementById("', $element_id, '").style.display="none";</script>';
833
        }
834
835
        // popup links
836
        switch ($fact) {
837
            case 'DATE':
838
                echo self::printCalendarPopup($element_id);
839
                break;
840
            case 'FAMC':
841
            case 'FAMS':
842
                echo FunctionsPrint::printFindFamilyLink($element_id);
843
                break;
844
            case 'ALIA':
845
            case 'ASSO':
846
            case '_ASSO':
847
                echo FunctionsPrint::printFindIndividualLink($element_id, $element_id . '_description');
848
                break;
849
            case 'FILE':
850
                FunctionsPrint::printFindMediaLink($element_id, '0file');
851
                break;
852
            case 'SOUR':
853
                echo FunctionsPrint::printFindSourceLink($element_id, $element_id . '_description'), ' ', self::printAddNewSourceLink($element_id);
854
                //-- checkboxes to apply '1 SOUR' to BIRT/MARR/DEAT as '2 SOUR'
855
                if ($level === 1) {
856
                    echo '<br>';
857
                    switch ($WT_TREE->getPreference('PREFER_LEVEL2_SOURCES')) {
858
                        case '2': // records
859
                            $level1_checked = 'checked';
860
                            $level2_checked = '';
861
                            break;
862
                        case '1': // facts
863
                            $level1_checked = '';
864
                            $level2_checked = 'checked';
865
                        break;
866
                        case '0': // none
867
                        default:
868
                            $level1_checked = '';
869
                            $level2_checked = '';
870
                        break;
871
                    }
872
                    if (strpos($bdm, 'B') !== false) {
873
                        echo ' <label><input type="checkbox" name="SOUR_INDI" ', $level1_checked, ' value="1">', I18N::translate('Individual'), '</label>';
874
                        if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('QUICK_REQUIRED_FACTS'), $matches)) {
875
                            foreach ($matches[1] as $match) {
876
                                if (!in_array($match, explode('|', WT_EVENTS_DEAT))) {
877
                                    echo ' <label><input type="checkbox" name="SOUR_', $match, '" ', $level2_checked, ' value="1">', GedcomTag::getLabel($match), '</label>';
878
                                }
879
                            }
880
                        }
881
                    }
882
                    if (strpos($bdm, 'D') !== false) {
883
                        if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('QUICK_REQUIRED_FACTS'), $matches)) {
884
                            foreach ($matches[1] as $match) {
885
                                if (in_array($match, explode('|', WT_EVENTS_DEAT))) {
886
                                    echo ' <label><input type="checkbox" name="SOUR_', $match, '"', $level2_checked, ' value="1">', GedcomTag::getLabel($match), '</label>';
887
                                }
888
                            }
889
                        }
890
                    }
891
                    if (strpos($bdm, 'M') !== false) {
892
                        echo ' <label><input type="checkbox" name="SOUR_FAM" ', $level1_checked, ' value="1">', I18N::translate('Family'), '</label>';
893
                        if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('QUICK_REQUIRED_FAMFACTS'), $matches)) {
894
                            foreach ($matches[1] as $match) {
895
                                echo ' <label><input type="checkbox" name="SOUR_', $match, '"', $level2_checked, ' value="1">', GedcomTag::getLabel($match), '</label>';
896
                            }
897
                        }
898
                    }
899
                }
900
                break;
901
            case 'REPO':
902
                echo FunctionsPrint::printFindRepositoryLink($element_id), ' ', self::printAddNewRepositoryLink($element_id);
903
                break;
904
            case 'NOTE':
905
                // Shared Notes Icons ========================================
906
                if ($islink) {
907
                    // Print regular Shared Note icons ---------------------------
908
                    echo ' ', FunctionsPrint::printFindNoteLink($element_id, $element_id . '_description'), ' ', self::printAddNewNoteLink($element_id);
909
                    if ($value) {
910
                        echo ' ', self::printEditNoteLink($value);
911
                    }
912
                }
913
                break;
914
            case 'OBJE':
915
                echo FunctionsPrint::printFindMediaLink($element_id, '1media');
916
                if (!$value) {
917
                    echo ' ', self::printAddNewMediaLink($element_id);
918
                    $value = 'new';
919
                }
920
                break;
921
        }
922
923
        echo '<div id="' . $element_id . '_description">';
924
925
        // current value
926
        if ($fact === 'DATE') {
927
            $date = new Date($value);
928
            echo $date->display();
929
        }
930
        if (($fact === 'ASSO' || $fact === '_ASSO') && $value === '') {
931
            if ($level === 1) {
932
                echo '<p class="small text-muted">' . I18N::translate('An associate is another individual who was involved with this individual, such as a friend or an employer.') . '</p>';
933
            } else {
934
                echo '<p class="small text-muted">' . I18N::translate('An associate is another individual who was involved with this fact or event, such as a witness or a priest.') . '</p>';
935
            }
936
        }
937
938
        if ($value && $value !== 'new' && $islink) {
939
            switch ($fact) {
940
                case 'ALIA':
941
                case 'ASSO':
942
                case '_ASSO':
943
                    $tmp = Individual::getInstance($value, $WT_TREE);
944
                    if ($tmp) {
945
                        echo ' ', $tmp->getFullName();
946
                    }
947
                    break;
948
                case 'SOUR':
949
                    $tmp = Source::getInstance($value, $WT_TREE);
950
                    if ($tmp) {
951
                        echo ' ', $tmp->getFullName();
952
                    }
953
                    break;
954
                case 'NOTE':
955
                    $tmp = Note::getInstance($value, $WT_TREE);
956
                    if ($tmp) {
957
                        echo ' ', $tmp->getFullName();
958
                    }
959
                    break;
960
                case 'OBJE':
961
                    $tmp = Media::getInstance($value, $WT_TREE);
962
                    if ($tmp) {
963
                        echo ' ', $tmp->getFullName();
964
                    }
965
                    break;
966
                case 'REPO':
967
                    $tmp = Repository::getInstance($value, $WT_TREE);
968
                    if ($tmp) {
969
                        echo ' ', $tmp->getFullName();
970
                    }
971
                    break;
972
            }
973
        }
974
975
        // pastable values
976
        if ($fact === 'FORM' && $upperlevel === 'OBJE') {
977
            FunctionsPrint::printAutoPasteLink($element_id, Config::fileFormats());
978
        }
979
        echo '</div>', $extra, '</td></tr>';
980
981
        return $element_id;
982
    }
983
984
    /**
985
     * Genearate a <select> element, with the dates/places of all known censuses
986
     *
987
     * @param string $locale - Sort the censuses for this locale
988
     * @param string $xref   - The individual for whom we are adding a census
989
     *
990
     * @return string
991
     */
992
    public static function censusDateSelector($locale, $xref)
0 ignored issues
show
Unused Code introduced by
The parameter $locale is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

992
    public static function censusDateSelector(/** @scrutinizer ignore-unused */ $locale, $xref)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $xref is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

992
    public static function censusDateSelector($locale, /** @scrutinizer ignore-unused */ $xref)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
993
    {
994
        global $controller;
995
996
        // Show more likely census details at the top of the list.
997
        switch (WT_LOCALE) {
998
            case 'cs':
999
                $census_places = array(new CensusOfCzechRepublic);
1000
                break;
1001
            case 'en-AU':
1002
            case 'en-GB':
1003
                $census_places = array(new CensusOfEngland, new CensusOfWales, new CensusOfScotland);
1004
                break;
1005
            case 'en-US':
1006
                $census_places = array(new CensusOfUnitedStates);
1007
                break;
1008
            case 'fr':
1009
            case 'fr-CA':
1010
                $census_places = array(new CensusOfFrance);
1011
                break;
1012
            case 'da':
1013
                $census_places = array(new CensusOfDenmark);
1014
                break;
1015
            case 'de':
1016
                $census_places = array(new CensusOfDeutschland);
1017
                break;
1018
            default:
1019
                $census_places = array();
1020
                break;
1021
        }
1022
        foreach (Census::allCensusPlaces() as $census_place) {
1023
            if (!in_array($census_place, $census_places)) {
1024
                $census_places[] = $census_place;
1025
            }
1026
        }
1027
1028
        $controller->addInlineJavascript('
1029
				function selectCensus(el) {
1030
					var option = jQuery(":selected", el);
1031
					jQuery("input.DATE", jQuery(el).closest("table")).val(option.val());
1032
					jQuery("input.PLAC", jQuery(el).closest("table")).val(option.data("place"));
1033
					jQuery("input.census-class", jQuery(el).closest("table")).val(option.data("census"));
1034
					if (option.data("place")) {
1035
						jQuery("#assistant-link").show();
1036
					} else {
1037
						jQuery("#assistant-link").hide();
1038
					}
1039
				}
1040
				function set_pid_array(pa) {
1041
					jQuery("#pid_array").val(pa);
1042
				}
1043
				function activateCensusAssistant() {
1044
					if (jQuery("#newshared_note_img").hasClass("icon-plus")) {
1045
						expand_layer("newshared_note");
1046
					}
1047
					var field  = jQuery("#newshared_note input.NOTE")[0];
1048
					var xref   = jQuery("input[name=xref]").val();
1049
					var census = jQuery(".census-assistant-selector :selected").data("census");
1050
					return addnewnote_assisted(field, xref, census);
1051
				}
1052
			');
1053
1054
        $options = '<option value="">' . I18N::translate('Census date') . '</option>';
1055
1056
        foreach ($census_places as $census_place) {
1057
            $options .= '<option value=""></option>';
1058
            foreach ($census_place->allCensusDates() as $census) {
1059
                $date            = new Date($census->censusDate());
1060
                $year            = $date->minimumDate()->format('%Y');
1061
                $place_hierarchy = explode(', ', $census->censusPlace());
1062
                $options .= '<option value="' . $census->censusDate() . '" data-place="' . $census->censusPlace() . '" data-census="' . get_class($census) . '">' . $place_hierarchy[0] . ' ' . $year . '</option>';
1063
            }
1064
        }
1065
1066
        return
1067
            '<input type="hidden" id="pid_array" name="pid_array" value="">' .
1068
            '<select class="census-assistant-selector" onchange="selectCensus(this);">' . $options . '</select>';
1069
    }
1070
1071
    /**
1072
     * Prints collapsable fields to add ASSO/RELA, SOUR, OBJE, etc.
1073
     *
1074
     * @param string $tag
1075
     * @param int $level
1076
     * @param string $parent_tag
1077
     */
1078
    public static function printAddLayer($tag, $level = 2, $parent_tag = '')
1079
    {
1080
        global $WT_TREE;
1081
1082
        switch ($tag) {
1083
            case 'SOUR':
1084
                echo '<a href="#" onclick="return expand_layer(\'newsource\');"><i id="newsource_img" class="icon-plus"></i> ', I18N::translate('Add a source citation'), '</a>';
1085
                echo '<br>';
1086
                echo '<div id="newsource" style="display: none;">';
1087
                echo '<table class="facts_table">';
1088
                // 2 SOUR
1089
                self::addSimpleTag($level . ' SOUR @');
1090
                // 3 PAGE
1091
                self::addSimpleTag(($level + 1) . ' PAGE');
1092
                // 3 DATA
1093
                self::addSimpleTag(($level + 1) . ' DATA');
1094
                // 4 TEXT
1095
                self::addSimpleTag(($level + 2) . ' TEXT');
1096
                if ($WT_TREE->getPreference('FULL_SOURCES')) {
1097
                    // 4 DATE
1098
                    self::addSimpleTag(($level + 2) . ' DATE', '', GedcomTag::getLabel('DATA:DATE'));
1099
                    // 3 QUAY
1100
                    self::addSimpleTag(($level + 1) . ' QUAY');
1101
                }
1102
                // 3 OBJE
1103
                self::addSimpleTag(($level + 1) . ' OBJE');
1104
                // 3 SHARED_NOTE
1105
                self::addSimpleTag(($level + 1) . ' SHARED_NOTE');
1106
                echo '</table></div>';
1107
                break;
1108
1109
            case 'ASSO':
1110
            case 'ASSO2':
1111
                //-- Add a new ASSOciate
1112
                if ($tag === 'ASSO') {
1113
                    echo "<a href=\"#\" onclick=\"return expand_layer('newasso');\"><i id=\"newasso_img\" class=\"icon-plus\"></i> ", I18N::translate('Add an associate'), '</a>';
1114
                    echo '<br>';
1115
                    echo '<div id="newasso" style="display: none;">';
1116
                } else {
1117
                    echo "<a href=\"#\" onclick=\"return expand_layer('newasso2');\"><i id=\"newasso2_img\" class=\"icon-plus\"></i> ", I18N::translate('Add an associate'), '</a>';
1118
                    echo '<br>';
1119
                    echo '<div id="newasso2" style="display: none;">';
1120
                }
1121
                echo '<table class="facts_table">';
1122
                // 2 ASSO
1123
                self::addSimpleTag($level . ' _ASSO @');
1124
                // 3 RELA
1125
                self::addSimpleTag(($level + 1) . ' RELA');
1126
                // 3 NOTE
1127
                self::addSimpleTag(($level + 1) . ' NOTE');
1128
                // 3 SHARED_NOTE
1129
                self::addSimpleTag(($level + 1) . ' SHARED_NOTE');
1130
                echo '</table></div>';
1131
                break;
1132
1133
            case 'NOTE':
1134
                //-- Retrieve existing note or add new note to fact
1135
                echo "<a href=\"#\" onclick=\"return expand_layer('newnote');\"><i id=\"newnote_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a note'), '</a>';
1136
                echo '<br>';
1137
                echo '<div id="newnote" style="display: none;">';
1138
                echo '<table class="facts_table">';
1139
                // 2 NOTE
1140
                self::addSimpleTag($level . ' NOTE');
1141
                echo '</table></div>';
1142
                break;
1143
1144
            case 'SHARED_NOTE':
1145
                echo "<a href=\"#\" onclick=\"return expand_layer('newshared_note');\"><i id=\"newshared_note_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a shared note'), '</a>';
1146
                echo '<br>';
1147
                echo '<div id="newshared_note" style="display: none;">';
1148
                echo '<table class="facts_table">';
1149
                // 2 SHARED NOTE
1150
                self::addSimpleTag($level . ' SHARED_NOTE', $parent_tag);
1151
                echo '</table></div>';
1152
                break;
1153
1154
            case 'OBJE':
1155
                if ($WT_TREE->getPreference('MEDIA_UPLOAD') >= Auth::accessLevel($WT_TREE)) {
1156
                    echo "<a href=\"#\" onclick=\"return expand_layer('newobje');\"><i id=\"newobje_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a media object'), '</a>';
1157
                    echo '<br>';
1158
                    echo '<div id="newobje" style="display: none;">';
1159
                    echo '<table class="facts_table">';
1160
                    self::addSimpleTag($level . ' OBJE');
1161
                    echo '</table></div>';
1162
                }
1163
                break;
1164
1165
            case 'RESN':
1166
                echo "<a href=\"#\" onclick=\"return expand_layer('newresn');\"><i id=\"newresn_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a restriction'), '</a>';
1167
                echo '<br>';
1168
                echo '<div id="newresn" style="display: none;">';
1169
                echo '<table class="facts_table">';
1170
                // 2 RESN
1171
                self::addSimpleTag($level . ' RESN');
1172
                echo '</table></div>';
1173
                break;
1174
        }
1175
    }
1176
1177
    /**
1178
     * Add some empty tags to create a new fact.
1179
     *
1180
     * @param string $fact
1181
     */
1182
    public static function addSimpleTags($fact)
1183
    {
1184
        global $WT_TREE;
1185
1186
        // For new individuals, these facts default to "Y"
1187
        if ($fact === 'MARR') {
1188
            self::addSimpleTag('0 ' . $fact . ' Y');
1189
        } else {
1190
            self::addSimpleTag('0 ' . $fact);
1191
        }
1192
1193
        if (!in_array($fact, Config::nonDateFacts())) {
1194
            self::addSimpleTag('0 DATE', $fact, GedcomTag::getLabel($fact . ':DATE'));
1195
        }
1196
1197
        if (!in_array($fact, Config::nonPlaceFacts())) {
1198
            self::addSimpleTag('0 PLAC', $fact, GedcomTag::getLabel($fact . ':PLAC'));
1199
1200
            if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_PLAC_FACTS'), $match)) {
1201
                foreach ($match[1] as $tag) {
1202
                    self::addSimpleTag('0 ' . $tag, $fact, GedcomTag::getLabel($fact . ':PLAC:' . $tag));
1203
                }
1204
            }
1205
            self::addSimpleTag('0 MAP', $fact);
1206
            self::addSimpleTag('0 LATI', $fact);
1207
            self::addSimpleTag('0 LONG', $fact);
1208
        }
1209
    }
1210
1211
    /**
1212
     * Assemble the pieces of a newly created record into gedcom
1213
     *
1214
     * @return string
1215
     */
1216
    public static function addNewName()
1217
    {
1218
        global $WT_TREE;
1219
1220
        $gedrec = "\n1 NAME " . Filter::post('NAME');
1221
1222
        $tags = array('NPFX', 'GIVN', 'SPFX', 'SURN', 'NSFX');
1223
1224
        if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_NAME_FACTS'), $match)) {
1225
            $tags = array_merge($tags, $match[1]);
1226
        }
1227
1228
        // Paternal and Polish and Lithuanian surname traditions can also create a _MARNM
1229
        $SURNAME_TRADITION = $WT_TREE->getPreference('SURNAME_TRADITION');
1230
        if ($SURNAME_TRADITION === 'paternal' || $SURNAME_TRADITION === 'polish' || $SURNAME_TRADITION === 'lithuanian') {
1231
            $tags[] = '_MARNM';
1232
        }
1233
1234
        foreach (array_unique($tags) as $tag) {
1235
            $TAG = Filter::post($tag);
1236
            if ($TAG) {
1237
                $gedrec .= "\n2 {$tag} {$TAG}";
1238
            }
1239
        }
1240
1241
        return $gedrec;
1242
    }
1243
1244
    /**
1245
     * Create a form to add a sex record.
1246
     *
1247
     * @return string
1248
     */
1249
    public static function addNewSex()
1250
    {
1251
        switch (Filter::post('SEX', '[MF]', 'U')) {
1252
            case 'M':
1253
                return "\n1 SEX M";
1254
            case 'F':
1255
                return "\n1 SEX F";
1256
            default:
1257
                return "\n1 SEX U";
1258
        }
1259
    }
1260
1261
    /**
1262
     * Create a form to add a new fact.
1263
     *
1264
     * @param string $fact
1265
     *
1266
     * @return string
1267
     */
1268
    public static function addNewFact($fact)
1269
    {
1270
        global $WT_TREE;
1271
1272
        $FACT = Filter::post($fact);
1273
        $DATE = Filter::post($fact . '_DATE');
1274
        $PLAC = Filter::post($fact . '_PLAC');
1275
        if ($DATE || $PLAC || $FACT && $FACT !== 'Y') {
1276
            if ($FACT && $FACT !== 'Y') {
1277
                $gedrec = "\n1 " . $fact . ' ' . $FACT;
1278
            } else {
1279
                $gedrec = "\n1 " . $fact;
1280
            }
1281
            if ($DATE) {
1282
                $gedrec .= "\n2 DATE " . $DATE;
1283
            }
1284
            if ($PLAC) {
1285
                $gedrec .= "\n2 PLAC " . $PLAC;
1286
1287
                if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_PLAC_FACTS'), $match)) {
1288
                    foreach ($match[1] as $tag) {
1289
                        $TAG = Filter::post($fact . '_' . $tag);
1290
                        if ($TAG) {
1291
                            $gedrec .= "\n3 " . $tag . ' ' . $TAG;
1292
                        }
1293
                    }
1294
                }
1295
                $LATI = Filter::post($fact . '_LATI');
1296
                $LONG = Filter::post($fact . '_LONG');
1297
                if ($LATI || $LONG) {
1298
                    $gedrec .= "\n3 MAP\n4 LATI " . $LATI . "\n4 LONG " . $LONG;
1299
                }
1300
            }
1301
            if (Filter::postBool('SOUR_' . $fact)) {
1302
                return self::updateSource($gedrec, 2);
1303
            } else {
1304
                return $gedrec;
1305
            }
1306
        } elseif ($FACT === 'Y') {
1307
            if (Filter::postBool('SOUR_' . $fact)) {
1308
                return self::updateSource("\n1 " . $fact . ' Y', 2);
1309
            } else {
1310
                return "\n1 " . $fact . ' Y';
1311
            }
1312
        } else {
1313
            return '';
1314
        }
1315
    }
1316
1317
    /**
1318
     * This function splits the $glevels, $tag, $islink, and $text arrays so that the
1319
     * entries associated with a SOUR record are separate from everything else.
1320
     *
1321
     * Input arrays:
1322
     * - $glevels[] - an array of the gedcom level for each line that was edited
1323
     * - $tag[] - an array of the tags for each gedcom line that was edited
1324
     * - $islink[] - an array of 1 or 0 values to indicate when the text is a link element
1325
     * - $text[] - an array of the text data for each line
1326
     *
1327
     * Output arrays:
1328
     * ** For the SOUR record:
1329
     * - $glevelsSOUR[] - an array of the gedcom level for each line that was edited
1330
     * - $tagSOUR[] - an array of the tags for each gedcom line that was edited
1331
     * - $islinkSOUR[] - an array of 1 or 0 values to indicate when the text is a link element
1332
     * - $textSOUR[] - an array of the text data for each line
1333
     * ** For the remaining records:
1334
     * - $glevelsRest[] - an array of the gedcom level for each line that was edited
1335
     * - $tagRest[] - an array of the tags for each gedcom line that was edited
1336
     * - $islinkRest[] - an array of 1 or 0 values to indicate when the text is a link element
1337
     * - $textRest[] - an array of the text data for each line
1338
     */
1339
    public static function splitSource()
1340
    {
1341
        global $glevels, $tag, $islink, $text;
1342
        global $glevelsSOUR, $tagSOUR, $islinkSOUR, $textSOUR;
1343
        global $glevelsRest, $tagRest, $islinkRest, $textRest;
1344
1345
        $glevelsSOUR = array();
1346
        $tagSOUR     = array();
1347
        $islinkSOUR  = array();
1348
        $textSOUR    = array();
1349
1350
        $glevelsRest = array();
1351
        $tagRest     = array();
1352
        $islinkRest  = array();
1353
        $textRest    = array();
1354
1355
        $inSOUR = false;
1356
1357
        for ($i = 0; $i < count($glevels); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
1358
            if ($inSOUR) {
1359
                if ($levelSOUR < $glevels[$i]) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $levelSOUR does not seem to be defined for all execution paths leading up to this point.
Loading history...
1360
                    $dest = 'S';
1361
                } else {
1362
                    $inSOUR = false;
1363
                    $dest   = 'R';
1364
                }
1365
            } else {
1366
                if ($tag[$i] === 'SOUR') {
1367
                    $inSOUR    = true;
1368
                    $levelSOUR = $glevels[$i];
1369
                    $dest      = 'S';
1370
                } else {
1371
                    $dest = 'R';
1372
                }
1373
            }
1374
            if ($dest === 'S') {
1375
                $glevelsSOUR[] = $glevels[$i];
1376
                $tagSOUR[]     = $tag[$i];
1377
                $islinkSOUR[]  = $islink[$i];
1378
                $textSOUR[]    = $text[$i];
1379
            } else {
1380
                $glevelsRest[] = $glevels[$i];
1381
                $tagRest[]     = $tag[$i];
1382
                $islinkRest[]  = $islink[$i];
1383
                $textRest[]    = $text[$i];
1384
            }
1385
        }
1386
    }
1387
1388
    /**
1389
     * Add new GEDCOM lines from the $xxxSOUR interface update arrays, which
1390
     * were produced by the splitSOUR() function.
1391
     * See the FunctionsEdit::handle_updatesges() function for details.
1392
     *
1393
     * @param string $inputRec
1394
     * @param string $levelOverride
1395
     *
1396
     * @return string
1397
     */
1398
    public static function updateSource($inputRec, $levelOverride = 'no')
1399
    {
1400
        global $glevels, $tag, $islink, $text;
1401
        global $glevelsSOUR, $tagSOUR, $islinkSOUR, $textSOUR;
1402
1403
        if (count($tagSOUR) === 0) {
1404
            return $inputRec; // No update required
1405
        }
1406
1407
        // Save original interface update arrays before replacing them with the xxxSOUR ones
1408
        $glevelsSave = $glevels;
1409
        $tagSave     = $tag;
1410
        $islinkSave  = $islink;
1411
        $textSave    = $text;
1412
1413
        $glevels = $glevelsSOUR;
1414
        $tag     = $tagSOUR;
1415
        $islink  = $islinkSOUR;
1416
        $text    = $textSOUR;
1417
1418
        $myRecord = self::handleUpdates($inputRec, $levelOverride); // Now do the update
1419
1420
        // Restore the original interface update arrays (just in case ...)
1421
        $glevels = $glevelsSave;
1422
        $tag     = $tagSave;
1423
        $islink  = $islinkSave;
1424
        $text    = $textSave;
1425
1426
        return $myRecord;
1427
    }
1428
1429
    /**
1430
     * Add new GEDCOM lines from the $xxxRest interface update arrays, which
1431
     * were produced by the splitSOUR() function.
1432
     * See the FunctionsEdit::handle_updatesges() function for details.
1433
     *
1434
     * @param string $inputRec
1435
     * @param string $levelOverride
1436
     *
1437
     * @return string
1438
     */
1439
    public static function updateRest($inputRec, $levelOverride = 'no')
1440
    {
1441
        global $glevels, $tag, $islink, $text;
1442
        global $glevelsRest, $tagRest, $islinkRest, $textRest;
1443
1444
        if (count($tagRest) === 0) {
1445
            return $inputRec; // No update required
1446
        }
1447
1448
        // Save original interface update arrays before replacing them with the xxxRest ones
1449
        $glevelsSave = $glevels;
1450
        $tagSave     = $tag;
1451
        $islinkSave  = $islink;
1452
        $textSave    = $text;
1453
1454
        $glevels = $glevelsRest;
1455
        $tag     = $tagRest;
1456
        $islink  = $islinkRest;
1457
        $text    = $textRest;
1458
1459
        $myRecord = self::handleUpdates($inputRec, $levelOverride); // Now do the update
1460
1461
        // Restore the original interface update arrays (just in case ...)
1462
        $glevels = $glevelsSave;
1463
        $tag     = $tagSave;
1464
        $islink  = $islinkSave;
1465
        $text    = $textSave;
1466
1467
        return $myRecord;
1468
    }
1469
1470
    /**
1471
     * Add new gedcom lines from interface update arrays
1472
     * The edit_interface and FunctionsEdit::add_simple_tag function produce the following
1473
     * arrays incoming from the $_POST form
1474
     * - $glevels[] - an array of the gedcom level for each line that was edited
1475
     * - $tag[] - an array of the tags for each gedcom line that was edited
1476
     * - $islink[] - an array of 1 or 0 values to tell whether the text is a link element and should be surrounded by @@
1477
     * - $text[] - an array of the text data for each line
1478
     * With these arrays you can recreate the gedcom lines like this
1479
     * <code>$glevel[0].' '.$tag[0].' '.$text[0]</code>
1480
     * There will be an index in each of these arrays for each line of the gedcom
1481
     * fact that is being edited.
1482
     * If the $text[] array is empty for the given line, then it means that the
1483
     * user removed that line during editing or that the line is supposed to be
1484
     * empty (1 DEAT, 1 BIRT) for example. To know if the line should be removed
1485
     * there is a section of code that looks ahead to the next lines to see if there
1486
     * are sub lines. For example we don't want to remove the 1 DEAT line if it has
1487
     * a 2 PLAC or 2 DATE line following it. If there are no sub lines, then the line
1488
     * can be safely removed.
1489
     *
1490
     * @param string $newged the new gedcom record to add the lines to
1491
     * @param string $levelOverride Override GEDCOM level specified in $glevels[0]
1492
     *
1493
     * @return string The updated gedcom record
1494
     */
1495
    public static function handleUpdates($newged, $levelOverride = 'no')
1496
    {
1497
        global $glevels, $islink, $tag, $uploaded_files, $text;
1498
1499
        if ($levelOverride === 'no' || count($glevels) === 0) {
1500
            $levelAdjust = 0;
1501
        } else {
1502
            $levelAdjust = $levelOverride - $glevels[0];
1503
        }
1504
1505
        for ($j = 0; $j < count($glevels); $j++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
1506
1507
            // Look for empty SOUR reference with non-empty sub-records.
1508
            // This can happen when the SOUR entry is deleted but its sub-records
1509
            // were incorrectly left intact.
1510
            // The sub-records should be deleted.
1511
            if ($tag[$j] === 'SOUR' && ($text[$j] === '@@' || $text[$j] === '')) {
1512
                $text[$j] = '';
1513
                $k        = $j + 1;
1514
                while (($k < count($glevels)) && ($glevels[$k] > $glevels[$j])) {
1515
                    $text[$k] = '';
1516
                    $k++;
1517
                }
1518
            }
1519
1520
            if (trim($text[$j]) !== '') {
1521
                $pass = true;
1522
            } else {
1523
                //-- for facts with empty values they must have sub records
1524
                //-- this section checks if they have subrecords
1525
                $k    = $j + 1;
1526
                $pass = false;
1527
                while (($k < count($glevels)) && ($glevels[$k] > $glevels[$j])) {
1528
                    if ($text[$k] !== '') {
1529
                        if (($tag[$j] !== 'OBJE') || ($tag[$k] === 'FILE')) {
1530
                            $pass = true;
1531
                            break;
1532
                        }
1533
                    }
1534
                    if (($tag[$k] === 'FILE') && (count($uploaded_files) > 0)) {
1535
                        $filename = array_shift($uploaded_files);
1536
                        if (!empty($filename)) {
1537
                            $text[$k] = $filename;
1538
                            $pass     = true;
1539
                            break;
1540
                        }
1541
                    }
1542
                    $k++;
1543
                }
1544
            }
1545
1546
            //-- if the value is not empty or it has sub lines
1547
            //--- then write the line to the gedcom record
1548
            //-- we have to let some emtpy text lines pass through... (DEAT, BIRT, etc)
1549
            if ($pass) {
1550
                $newline = $glevels[$j] + $levelAdjust . ' ' . $tag[$j];
1551
                if ($text[$j] !== '') {
1552
                    if ($islink[$j]) {
1553
                        $newline .= ' @' . $text[$j] . '@';
1554
                    } else {
1555
                        $newline .= ' ' . $text[$j];
1556
                    }
1557
                }
1558
                $newged .= "\n" . str_replace("\n", "\n" . (1 + substr($newline, 0, 1)) . ' CONT ', $newline);
1559
            }
1560
        }
1561
1562
        return $newged;
1563
    }
1564
1565
    /**
1566
     * builds the form for adding new facts
1567
     *
1568
     * @param string $fact the new fact we are adding
1569
     */
1570
    public static function createAddForm($fact)
1571
    {
1572
        global $tags, $WT_TREE;
1573
1574
        $tags = array();
1575
1576
        // handle  MARRiage TYPE
1577
        if (substr($fact, 0, 5) === 'MARR_') {
1578
            $tags[0] = 'MARR';
1579
            self::addSimpleTag('1 MARR');
1580
            self::insertMissingSubtags($fact);
1581
        } else {
1582
            $tags[0] = $fact;
1583
            if ($fact === '_UID') {
1584
                $fact .= ' ' . GedcomTag::createUid();
1585
            }
1586
            // These new level 1 tags need to be turned into links
1587
            if (in_array($fact, array('ALIA', 'ASSO'))) {
1588
                $fact .= ' @';
1589
            }
1590
            if (in_array($fact, Config::emptyFacts())) {
1591
                self::addSimpleTag('1 ' . $fact . ' Y');
1592
            } else {
1593
                self::addSimpleTag('1 ' . $fact);
1594
            }
1595
            self::insertMissingSubtags($tags[0]);
1596
            //-- handle the special SOURce case for level 1 sources [ 1759246 ]
1597
            if ($fact === 'SOUR') {
1598
                self::addSimpleTag('2 PAGE');
1599
                self::addSimpleTag('2 DATA');
1600
                self::addSimpleTag('3 TEXT');
1601
                if ($WT_TREE->getPreference('FULL_SOURCES')) {
1602
                    self::addSimpleTag('3 DATE', '', GedcomTag::getLabel('DATA:DATE'));
1603
                    self::addSimpleTag('2 QUAY');
1604
                }
1605
            }
1606
        }
1607
    }
1608
1609
    /**
1610
     * Create a form to edit a Fact object.
1611
     *
1612
     * @param Fact $fact
1613
     *
1614
     * @return string
1615
     */
1616
    public static function createEditForm(Fact $fact)
1617
    {
1618
        global $tags;
1619
1620
        $record = $fact->getParent();
1621
1622
        $tags     = array();
1623
        $gedlines = explode("\n", $fact->getGedcom());
1624
1625
        $linenum = 0;
1626
        $fields  = explode(' ', $gedlines[$linenum]);
1627
        $glevel  = $fields[0];
1628
        $level   = $glevel;
1629
1630
        $type       = $fact->getTag();
1631
        $level0type = $record::RECORD_TYPE;
1632
        $level1type = $type;
1633
1634
        $i           = $linenum;
1635
        $inSource    = false;
1636
        $levelSource = 0;
1637
        $add_date    = true;
1638
1639
        // List of tags we would expect at the next level
1640
        // NB add_missing_subtags() already takes care of the simple cases
1641
        // where a level 1 tag is missing a level 2 tag. Here we only need to
1642
        // handle the more complicated cases.
1643
        $expected_subtags = array(
1644
            'SOUR' => array('PAGE', 'DATA'),
1645
            'DATA' => array('TEXT'),
1646
            'PLAC' => array('MAP'),
1647
            'MAP'  => array('LATI', 'LONG'),
1648
        );
1649
        if ($record->getTree()->getPreference('FULL_SOURCES')) {
1650
            $expected_subtags['SOUR'][] = 'QUAY';
1651
            $expected_subtags['DATA'][] = 'DATE';
1652
        }
1653
        if (GedcomCodeTemp::isTagLDS($level1type)) {
1654
            $expected_subtags['STAT'] = array('DATE');
1655
        }
1656
        if (in_array($level1type, Config::dateAndTime())) {
1657
            $expected_subtags['DATE'] = array('TIME'); // TIME is NOT a valid 5.5.1 tag
1658
        }
1659
        if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $record->getTree()->getPreference('ADVANCED_PLAC_FACTS'), $match)) {
1660
            $expected_subtags['PLAC'] = array_merge($match[1], $expected_subtags['PLAC']);
1661
        }
1662
1663
        $stack = array();
1664
        // Loop on existing tags :
1665
        while (true) {
1666
            // Keep track of our hierarchy, e.g. 1=>BIRT, 2=>PLAC, 3=>FONE
1667
            $stack[$level] = $type;
1668
            // Merge them together, e.g. BIRT:PLAC:FONE
1669
            $label = implode(':', array_slice($stack, 0, $level));
0 ignored issues
show
Bug introduced by
$level of type string is incompatible with the type integer|null expected by parameter $length of array_slice(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1669
            $label = implode(':', array_slice($stack, 0, /** @scrutinizer ignore-type */ $level));
Loading history...
1670
1671
            $text = '';
1672
            for ($j = 2; $j < count($fields); $j++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
1673
                if ($j > 2) {
1674
                    $text .= ' ';
1675
                }
1676
                $text .= $fields[$j];
1677
            }
1678
            $text = rtrim($text);
1679
            while (($i + 1 < count($gedlines)) && (preg_match("/" . ($level + 1) . ' CONT ?(.*)/', $gedlines[$i + 1], $cmatch) > 0)) {
1680
                $text .= "\n" . $cmatch[1];
1681
                $i++;
1682
            }
1683
1684
            if ($type === 'SOUR') {
1685
                $inSource    = true;
1686
                $levelSource = $level;
1687
            } elseif ($levelSource >= $level) {
1688
                $inSource = false;
1689
            }
1690
1691
            if ($type !== 'CONT') {
1692
                $tags[]    = $type;
1693
                $subrecord = $level . ' ' . $type . ' ' . $text;
1694
                if ($inSource && $type === 'DATE') {
1695
                    self::addSimpleTag($subrecord, '', GedcomTag::getLabel($label, $record));
1696
                } elseif (!$inSource && $type === 'DATE') {
1697
                    self::addSimpleTag($subrecord, $level1type, GedcomTag::getLabel($label, $record));
1698
                    if ($level === '2') {
1699
                        // We already have a date - no need to add one.
1700
                        $add_date = false;
1701
                    }
1702
                } elseif ($type === 'STAT') {
1703
                    self::addSimpleTag($subrecord, $level1type, GedcomTag::getLabel($label, $record));
1704
                } else {
1705
                    self::addSimpleTag($subrecord, $level0type, GedcomTag::getLabel($label, $record));
1706
                }
1707
            }
1708
1709
            // Get a list of tags present at the next level
1710
            $subtags = array();
1711
            for ($ii = $i + 1; isset($gedlines[$ii]) && preg_match('/^(\d+) (\S+)/', $gedlines[$ii], $mm) && $mm[1] > $level; ++$ii) {
1712
                if ($mm[1] == $level + 1) {
1713
                    $subtags[] = $mm[2];
1714
                }
1715
            }
1716
1717
            // Insert missing tags
1718
            if (!empty($expected_subtags[$type])) {
1719
                foreach ($expected_subtags[$type] as $subtag) {
1720
                    if (!in_array($subtag, $subtags)) {
1721
                        self::addSimpleTag(($level + 1) . ' ' . $subtag, '', GedcomTag::getLabel($label . ':' . $subtag));
1722
                        if (!empty($expected_subtags[$subtag])) {
1723
                            foreach ($expected_subtags[$subtag] as $subsubtag) {
1724
                                self::addSimpleTag(($level + 2) . ' ' . $subsubtag, '', GedcomTag::getLabel($label . ':' . $subtag . ':' . $subsubtag));
1725
                            }
1726
                        }
1727
                    }
1728
                }
1729
            }
1730
1731
            $i++;
1732
            if (isset($gedlines[$i])) {
1733
                $fields = explode(' ', $gedlines[$i]);
1734
                $level  = $fields[0];
1735
                if (isset($fields[1])) {
1736
                    $type = trim($fields[1]);
1737
                } else {
1738
                    $level = 0;
1739
                }
1740
            } else {
1741
                $level = 0;
1742
            }
1743
            if ($level <= $glevel) {
1744
                break;
1745
            }
1746
        }
1747
1748
        if ($level1type !== '_PRIM') {
1749
            self::insertMissingSubtags($level1type, $add_date);
1750
        }
1751
1752
        return $level1type;
1753
    }
1754
1755
    /**
1756
     * Populates the global $tags array with any missing sub-tags.
1757
     *
1758
     * @param string $level1tag the type of the level 1 gedcom record
1759
     * @param bool $add_date
1760
     */
1761
    public static function insertMissingSubtags($level1tag, $add_date = false)
1762
    {
1763
        global $tags, $WT_TREE;
1764
1765
        // handle  MARRiage TYPE
1766
        $type_val = '';
1767
        if (substr($level1tag, 0, 5) === 'MARR_') {
1768
            $type_val  = substr($level1tag, 5);
1769
            $level1tag = 'MARR';
1770
        }
1771
1772
        foreach (Config::levelTwoTags() as $key => $value) {
1773
            if ($key === 'DATE' && in_array($level1tag, Config::nonDateFacts()) || $key === 'PLAC' && in_array($level1tag, Config::nonPlaceFacts())) {
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($key === 'DATE' && in_a...onfig::nonPlaceFacts()), Probably Intended Meaning: $key === 'DATE' && (in_a...nfig::nonPlaceFacts()))
Loading history...
1774
                continue;
1775
            }
1776
            if (in_array($level1tag, $value) && !in_array($key, $tags)) {
1777
                if ($key === 'TYPE') {
1778
                    self::addSimpleTag('2 TYPE ' . $type_val, $level1tag);
1779
                } elseif ($level1tag === '_TODO' && $key === 'DATE') {
1780
                    self::addSimpleTag('2 ' . $key . ' ' . strtoupper(date('d M Y')), $level1tag);
1781
                } elseif ($level1tag === '_TODO' && $key === '_WT_USER') {
1782
                    self::addSimpleTag('2 ' . $key . ' ' . Auth::user()->getUserName(), $level1tag);
1783
                } elseif ($level1tag === 'TITL' && strstr($WT_TREE->getPreference('ADVANCED_NAME_FACTS'), $key) !== false) {
1784
                    self::addSimpleTag('2 ' . $key, $level1tag);
1785
                } elseif ($level1tag === 'NAME' && strstr($WT_TREE->getPreference('ADVANCED_NAME_FACTS'), $key) !== false) {
1786
                    self::addSimpleTag('2 ' . $key, $level1tag);
1787
                } elseif ($level1tag !== 'TITL' && $level1tag !== 'NAME') {
1788
                    self::addSimpleTag('2 ' . $key, $level1tag);
1789
                }
1790
                // Add level 3/4 tags as appropriate
1791
                switch ($key) {
1792
                    case 'PLAC':
1793
                        if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_PLAC_FACTS'), $match)) {
1794
                            foreach ($match[1] as $tag) {
1795
                                self::addSimpleTag('3 ' . $tag, '', GedcomTag::getLabel($level1tag . ':PLAC:' . $tag));
1796
                            }
1797
                        }
1798
                        self::addSimpleTag('3 MAP');
1799
                        self::addSimpleTag('4 LATI');
1800
                        self::addSimpleTag('4 LONG');
1801
                        break;
1802
                    case 'FILE':
1803
                        self::addSimpleTag('3 FORM');
1804
                        break;
1805
                    case 'EVEN':
1806
                        self::addSimpleTag('3 DATE');
1807
                        self::addSimpleTag('3 PLAC');
1808
                        break;
1809
                    case 'STAT':
1810
                        if (GedcomCodeTemp::isTagLDS($level1tag)) {
1811
                            self::addSimpleTag('3 DATE', '', GedcomTag::getLabel('STAT:DATE'));
1812
                        }
1813
                        break;
1814
                    case 'DATE':
1815
                        // TIME is NOT a valid 5.5.1 tag
1816
                        if (in_array($level1tag, Config::dateAndTime())) {
1817
                            self::addSimpleTag('3 TIME');
1818
                        }
1819
                        break;
1820
                    case 'HUSB':
1821
                    case 'WIFE':
1822
                        self::addSimpleTag('3 AGE');
1823
                        break;
1824
                    case 'FAMC':
1825
                        if ($level1tag === 'ADOP') {
1826
                            self::addSimpleTag('3 ADOP BOTH');
1827
                        }
1828
                        break;
1829
                }
1830
            } elseif ($key === 'DATE' && $add_date) {
1831
                self::addSimpleTag('2 DATE', $level1tag, GedcomTag::getLabel($level1tag . ':DATE'));
1832
            }
1833
        }
1834
        // Do something (anything!) with unrecognized custom tags
1835
        if (substr($level1tag, 0, 1) === '_' && $level1tag !== '_UID' && $level1tag !== '_TODO') {
1836
            foreach (array('DATE', 'PLAC', 'ADDR', 'AGNC', 'TYPE', 'AGE') as $tag) {
1837
                if (!in_array($tag, $tags)) {
1838
                    self::addSimpleTag('2 ' . $tag);
1839
                    if ($tag === 'PLAC') {
1840
                        if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_PLAC_FACTS'), $match)) {
1841
                            foreach ($match[1] as $ptag) {
1842
                                self::addSimpleTag('3 ' . $ptag, '', GedcomTag::getLabel($level1tag . ':PLAC:' . $ptag));
1843
                            }
1844
                        }
1845
                        self::addSimpleTag('3 MAP');
1846
                        self::addSimpleTag('4 LATI');
1847
                        self::addSimpleTag('4 LONG');
1848
                    }
1849
                }
1850
            }
1851
        }
1852
    }
1853
}
1854