xref: /webtrees/app/Services/GedcomEditService.php (revision e22e42f733f4ab630c81dc26a61e9a5470ca33de)
17c7d1e03SGreg Roach<?php
27c7d1e03SGreg Roach
37c7d1e03SGreg Roach/**
47c7d1e03SGreg Roach * webtrees: online genealogy
589f7189bSGreg Roach * Copyright (C) 2021 webtrees development team
67c7d1e03SGreg Roach * This program is free software: you can redistribute it and/or modify
77c7d1e03SGreg Roach * it under the terms of the GNU General Public License as published by
87c7d1e03SGreg Roach * the Free Software Foundation, either version 3 of the License, or
97c7d1e03SGreg Roach * (at your option) any later version.
107c7d1e03SGreg Roach * This program is distributed in the hope that it will be useful,
117c7d1e03SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of
127c7d1e03SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
137c7d1e03SGreg Roach * GNU General Public License for more details.
147c7d1e03SGreg Roach * You should have received a copy of the GNU General Public License
1589f7189bSGreg Roach * along with this program. If not, see <https://www.gnu.org/licenses/>.
167c7d1e03SGreg Roach */
177c7d1e03SGreg Roach
187c7d1e03SGreg Roachdeclare(strict_types=1);
197c7d1e03SGreg Roach
207c7d1e03SGreg Roachnamespace Fisharebest\Webtrees\Services;
217c7d1e03SGreg Roach
22*e22e42f7SGreg Roachuse Fisharebest\Webtrees\Fact;
237c7d1e03SGreg Roachuse Fisharebest\Webtrees\Gedcom;
24c2ed51d1SGreg Roachuse Fisharebest\Webtrees\Registry;
257c7d1e03SGreg Roachuse Fisharebest\Webtrees\Tree;
267c7d1e03SGreg Roachuse Psr\Http\Message\ServerRequestInterface;
277c7d1e03SGreg Roach
287c7d1e03SGreg Roachuse function array_merge;
297c7d1e03SGreg Roachuse function array_unique;
307c7d1e03SGreg Roachuse function assert;
317c7d1e03SGreg Roachuse function count;
327c7d1e03SGreg Roachuse function preg_match_all;
337c7d1e03SGreg Roachuse function str_replace;
347c7d1e03SGreg Roachuse function trim;
357c7d1e03SGreg Roach
367c7d1e03SGreg Roach/**
377c7d1e03SGreg Roach * Utilities to edit/save GEDCOM data.
387c7d1e03SGreg Roach */
397c7d1e03SGreg Roachclass GedcomEditService
407c7d1e03SGreg Roach{
417c7d1e03SGreg Roach    /** @var string[] */
427c7d1e03SGreg Roach    public $glevels = [];
437c7d1e03SGreg Roach
447c7d1e03SGreg Roach    /** @var string[] */
457c7d1e03SGreg Roach    public $tag = [];
467c7d1e03SGreg Roach
477c7d1e03SGreg Roach    /** @var string[] */
487c7d1e03SGreg Roach    public $islink = [];
497c7d1e03SGreg Roach
507c7d1e03SGreg Roach    /** @var string[] */
517c7d1e03SGreg Roach    public $text = [];
527c7d1e03SGreg Roach
537c7d1e03SGreg Roach    /** @var string[] */
547c7d1e03SGreg Roach    protected $glevelsSOUR = [];
557c7d1e03SGreg Roach
567c7d1e03SGreg Roach    /** @var string[] */
577c7d1e03SGreg Roach    protected $tagSOUR = [];
587c7d1e03SGreg Roach
597c7d1e03SGreg Roach    /** @var string[] */
607c7d1e03SGreg Roach    protected $islinkSOUR = [];
617c7d1e03SGreg Roach
627c7d1e03SGreg Roach    /** @var string[] */
637c7d1e03SGreg Roach    protected $textSOUR = [];
647c7d1e03SGreg Roach
657c7d1e03SGreg Roach    /** @var string[] */
667c7d1e03SGreg Roach    protected $glevelsRest = [];
677c7d1e03SGreg Roach
687c7d1e03SGreg Roach    /** @var string[] */
697c7d1e03SGreg Roach    protected $tagRest = [];
707c7d1e03SGreg Roach
717c7d1e03SGreg Roach    /** @var string[] */
727c7d1e03SGreg Roach    protected $islinkRest = [];
737c7d1e03SGreg Roach
747c7d1e03SGreg Roach    /** @var string[] */
757c7d1e03SGreg Roach    protected $textRest = [];
767c7d1e03SGreg Roach
777c7d1e03SGreg Roach    /**
787c7d1e03SGreg Roach     * This function splits the $glevels, $tag, $islink, and $text arrays so that the
797c7d1e03SGreg Roach     * entries associated with a SOUR record are separate from everything else.
807c7d1e03SGreg Roach     *
817c7d1e03SGreg Roach     * Input arrays:
827c7d1e03SGreg Roach     * - $glevels[] - an array of the gedcom level for each line that was edited
837c7d1e03SGreg Roach     * - $tag[] - an array of the tags for each gedcom line that was edited
847c7d1e03SGreg Roach     * - $islink[] - an array of 1 or 0 values to indicate when the text is a link element
857c7d1e03SGreg Roach     * - $text[] - an array of the text data for each line
867c7d1e03SGreg Roach     *
877c7d1e03SGreg Roach     * Output arrays:
887c7d1e03SGreg Roach     * ** For the SOUR record:
897c7d1e03SGreg Roach     * - $glevelsSOUR[] - an array of the gedcom level for each line that was edited
907c7d1e03SGreg Roach     * - $tagSOUR[] - an array of the tags for each gedcom line that was edited
917c7d1e03SGreg Roach     * - $islinkSOUR[] - an array of 1 or 0 values to indicate when the text is a link element
927c7d1e03SGreg Roach     * - $textSOUR[] - an array of the text data for each line
937c7d1e03SGreg Roach     * ** For the remaining records:
947c7d1e03SGreg Roach     * - $glevelsRest[] - an array of the gedcom level for each line that was edited
957c7d1e03SGreg Roach     * - $tagRest[] - an array of the tags for each gedcom line that was edited
967c7d1e03SGreg Roach     * - $islinkRest[] - an array of 1 or 0 values to indicate when the text is a link element
977c7d1e03SGreg Roach     * - $textRest[] - an array of the text data for each line
987c7d1e03SGreg Roach     *
997c7d1e03SGreg Roach     * @return void
1007c7d1e03SGreg Roach     */
1017c7d1e03SGreg Roach    public function splitSource(): void
1027c7d1e03SGreg Roach    {
1037c7d1e03SGreg Roach        $this->glevelsSOUR = [];
1047c7d1e03SGreg Roach        $this->tagSOUR     = [];
1057c7d1e03SGreg Roach        $this->islinkSOUR  = [];
1067c7d1e03SGreg Roach        $this->textSOUR    = [];
1077c7d1e03SGreg Roach
1087c7d1e03SGreg Roach        $this->glevelsRest = [];
1097c7d1e03SGreg Roach        $this->tagRest     = [];
1107c7d1e03SGreg Roach        $this->islinkRest  = [];
1117c7d1e03SGreg Roach        $this->textRest    = [];
1127c7d1e03SGreg Roach
1137c7d1e03SGreg Roach        $inSOUR    = false;
1147c7d1e03SGreg Roach        $levelSOUR = 0;
1157c7d1e03SGreg Roach
1167c7d1e03SGreg Roach        // Assume all arrays are the same size.
1177c7d1e03SGreg Roach        $count = count($this->glevels);
1187c7d1e03SGreg Roach
1197c7d1e03SGreg Roach        for ($i = 0; $i < $count; $i++) {
1207c7d1e03SGreg Roach            if ($inSOUR) {
1217c7d1e03SGreg Roach                if ($levelSOUR < $this->glevels[$i]) {
1227c7d1e03SGreg Roach                    $dest = 'S';
1237c7d1e03SGreg Roach                } else {
1247c7d1e03SGreg Roach                    $inSOUR = false;
1257c7d1e03SGreg Roach                    $dest   = 'R';
1267c7d1e03SGreg Roach                }
1277c7d1e03SGreg Roach            } elseif ($this->tag[$i] === 'SOUR') {
1287c7d1e03SGreg Roach                $inSOUR    = true;
1297c7d1e03SGreg Roach                $levelSOUR = $this->glevels[$i];
1307c7d1e03SGreg Roach                $dest      = 'S';
1317c7d1e03SGreg Roach            } else {
1327c7d1e03SGreg Roach                $dest = 'R';
1337c7d1e03SGreg Roach            }
1347c7d1e03SGreg Roach
1357c7d1e03SGreg Roach            if ($dest === 'S') {
1367c7d1e03SGreg Roach                $this->glevelsSOUR[] = $this->glevels[$i];
1377c7d1e03SGreg Roach                $this->tagSOUR[]     = $this->tag[$i];
1387c7d1e03SGreg Roach                $this->islinkSOUR[]  = $this->islink[$i];
1397c7d1e03SGreg Roach                $this->textSOUR[]    = $this->text[$i];
1407c7d1e03SGreg Roach            } else {
1417c7d1e03SGreg Roach                $this->glevelsRest[] = $this->glevels[$i];
1427c7d1e03SGreg Roach                $this->tagRest[]     = $this->tag[$i];
1437c7d1e03SGreg Roach                $this->islinkRest[]  = $this->islink[$i];
1447c7d1e03SGreg Roach                $this->textRest[]    = $this->text[$i];
1457c7d1e03SGreg Roach            }
1467c7d1e03SGreg Roach        }
1477c7d1e03SGreg Roach    }
1487c7d1e03SGreg Roach
1497c7d1e03SGreg Roach    /**
1507c7d1e03SGreg Roach     * Add new GEDCOM lines from the $xxxRest interface update arrays, which
1517c7d1e03SGreg Roach     * were produced by the splitSOUR() function.
1527c7d1e03SGreg Roach     * See the FunctionsEdit::handle_updatesges() function for details.
1537c7d1e03SGreg Roach     *
1547c7d1e03SGreg Roach     * @param string $inputRec
1557c7d1e03SGreg Roach     *
1567c7d1e03SGreg Roach     * @return string
1577c7d1e03SGreg Roach     */
1587c7d1e03SGreg Roach    public function updateRest(string $inputRec): string
1597c7d1e03SGreg Roach    {
1607c7d1e03SGreg Roach        if (count($this->tagRest) === 0) {
1617c7d1e03SGreg Roach            return $inputRec; // No update required
1627c7d1e03SGreg Roach        }
1637c7d1e03SGreg Roach
1647c7d1e03SGreg Roach        // Save original interface update arrays before replacing them with the xxxRest ones
1657c7d1e03SGreg Roach        $glevelsSave = $this->glevels;
1667c7d1e03SGreg Roach        $tagSave     = $this->tag;
1677c7d1e03SGreg Roach        $islinkSave  = $this->islink;
1687c7d1e03SGreg Roach        $textSave    = $this->text;
1697c7d1e03SGreg Roach
1707c7d1e03SGreg Roach        $this->glevels = $this->glevelsRest;
1717c7d1e03SGreg Roach        $this->tag     = $this->tagRest;
1727c7d1e03SGreg Roach        $this->islink  = $this->islinkRest;
1737c7d1e03SGreg Roach        $this->text    = $this->textRest;
1747c7d1e03SGreg Roach
1757c7d1e03SGreg Roach        $myRecord = $this->handleUpdates($inputRec, 'no'); // Now do the update
1767c7d1e03SGreg Roach
1777c7d1e03SGreg Roach        // Restore the original interface update arrays (just in case ...)
1787c7d1e03SGreg Roach        $this->glevels = $glevelsSave;
1797c7d1e03SGreg Roach        $this->tag     = $tagSave;
1807c7d1e03SGreg Roach        $this->islink  = $islinkSave;
1817c7d1e03SGreg Roach        $this->text    = $textSave;
1827c7d1e03SGreg Roach
1837c7d1e03SGreg Roach        return $myRecord;
1847c7d1e03SGreg Roach    }
1857c7d1e03SGreg Roach
1867c7d1e03SGreg Roach    /**
1877c7d1e03SGreg Roach     * Add new gedcom lines from interface update arrays
1887c7d1e03SGreg Roach     * The edit_interface and FunctionsEdit::add_simple_tag function produce the following
1897c7d1e03SGreg Roach     * arrays incoming from the $_POST form
1907c7d1e03SGreg Roach     * - $glevels[] - an array of the gedcom level for each line that was edited
1917c7d1e03SGreg Roach     * - $tag[] - an array of the tags for each gedcom line that was edited
1927c7d1e03SGreg Roach     * - $islink[] - an array of 1 or 0 values to tell whether the text is a link element and should be surrounded by @@
1937c7d1e03SGreg Roach     * - $text[] - an array of the text data for each line
1947c7d1e03SGreg Roach     * With these arrays you can recreate the gedcom lines like this
1957c7d1e03SGreg Roach     * <code>$glevel[0].' '.$tag[0].' '.$text[0]</code>
1967c7d1e03SGreg Roach     * There will be an index in each of these arrays for each line of the gedcom
1977c7d1e03SGreg Roach     * fact that is being edited.
1987c7d1e03SGreg Roach     * If the $text[] array is empty for the given line, then it means that the
1997c7d1e03SGreg Roach     * user removed that line during editing or that the line is supposed to be
2007c7d1e03SGreg Roach     * empty (1 DEAT, 1 BIRT) for example. To know if the line should be removed
2017c7d1e03SGreg Roach     * there is a section of code that looks ahead to the next lines to see if there
2027c7d1e03SGreg Roach     * are sub lines. For example we don't want to remove the 1 DEAT line if it has
2037c7d1e03SGreg Roach     * a 2 PLAC or 2 DATE line following it. If there are no sub lines, then the line
2047c7d1e03SGreg Roach     * can be safely removed.
2057c7d1e03SGreg Roach     *
2067c7d1e03SGreg Roach     * @param string $newged        the new gedcom record to add the lines to
2077c7d1e03SGreg Roach     * @param string $levelOverride Override GEDCOM level specified in $glevels[0]
2087c7d1e03SGreg Roach     *
2097c7d1e03SGreg Roach     * @return string The updated gedcom record
2107c7d1e03SGreg Roach     */
211c8183f29SGreg Roach    public function handleUpdates(string $newged, string $levelOverride = 'no'): string
2127c7d1e03SGreg Roach    {
2137c7d1e03SGreg Roach        if ($levelOverride === 'no') {
2147c7d1e03SGreg Roach            $levelAdjust = 0;
2157c7d1e03SGreg Roach        } else {
2167c7d1e03SGreg Roach            $levelAdjust = 1;
2177c7d1e03SGreg Roach        }
2187c7d1e03SGreg Roach
2197c7d1e03SGreg Roach        // Assert all arrays are the same size.
2207c7d1e03SGreg Roach        assert(count($this->glevels) === count($this->tag));
2217c7d1e03SGreg Roach        assert(count($this->glevels) === count($this->text));
2227c7d1e03SGreg Roach        assert(count($this->glevels) === count($this->islink));
2237c7d1e03SGreg Roach
2247c7d1e03SGreg Roach        $count = count($this->glevels);
2257c7d1e03SGreg Roach
2267c7d1e03SGreg Roach        for ($j = 0; $j < $count; $j++) {
2277c7d1e03SGreg Roach            // Look for empty SOUR reference with non-empty sub-records.
2287c7d1e03SGreg Roach            // This can happen when the SOUR entry is deleted but its sub-records
2297c7d1e03SGreg Roach            // were incorrectly left intact.
2307c7d1e03SGreg Roach            // The sub-records should be deleted.
2317c7d1e03SGreg Roach            if ($this->tag[$j] === 'SOUR' && ($this->text[$j] === '@@' || $this->text[$j] === '')) {
2327c7d1e03SGreg Roach                $this->text[$j] = '';
2337c7d1e03SGreg Roach                $k              = $j + 1;
2347c7d1e03SGreg Roach                while ($k < $count && $this->glevels[$k] > $this->glevels[$j]) {
2357c7d1e03SGreg Roach                    $this->text[$k] = '';
2367c7d1e03SGreg Roach                    $k++;
2377c7d1e03SGreg Roach                }
2387c7d1e03SGreg Roach            }
2397c7d1e03SGreg Roach
2407c7d1e03SGreg Roach            if (trim($this->text[$j]) !== '') {
2417c7d1e03SGreg Roach                $pass = true;
2427c7d1e03SGreg Roach            } else {
2437c7d1e03SGreg Roach                //-- for facts with empty values they must have sub records
2447c7d1e03SGreg Roach                //-- this section checks if they have subrecords
2457c7d1e03SGreg Roach                $k    = $j + 1;
2467c7d1e03SGreg Roach                $pass = false;
2477c7d1e03SGreg Roach                while ($k < $count && $this->glevels[$k] > $this->glevels[$j]) {
2487c7d1e03SGreg Roach                    if ($this->text[$k] !== '') {
249c2ed51d1SGreg Roach                        if ($this->tag[$j] !== 'OBJE' || $this->tag[$k] === 'FILE') {
2507c7d1e03SGreg Roach                            $pass = true;
2517c7d1e03SGreg Roach                            break;
2527c7d1e03SGreg Roach                        }
2537c7d1e03SGreg Roach                    }
2547c7d1e03SGreg Roach                    $k++;
2557c7d1e03SGreg Roach                }
2567c7d1e03SGreg Roach            }
2577c7d1e03SGreg Roach
2587c7d1e03SGreg Roach            //-- if the value is not empty or it has sub lines
2597c7d1e03SGreg Roach            //--- then write the line to the gedcom record
2607c7d1e03SGreg Roach            //-- we have to let some emtpy text lines pass through... (DEAT, BIRT, etc)
2617c7d1e03SGreg Roach            if ($pass) {
2627c7d1e03SGreg Roach                $newline = (int) $this->glevels[$j] + $levelAdjust . ' ' . $this->tag[$j];
2637c7d1e03SGreg Roach                if ($this->text[$j] !== '') {
2647c7d1e03SGreg Roach                    if ($this->islink[$j]) {
26565de9aa7SGreg Roach                        $newline .= ' @' . trim($this->text[$j], '@') . '@';
2667c7d1e03SGreg Roach                    } else {
2677c7d1e03SGreg Roach                        $newline .= ' ' . $this->text[$j];
2687c7d1e03SGreg Roach                    }
2697c7d1e03SGreg Roach                }
2707c7d1e03SGreg Roach                $next_level = 1 + (int) $this->glevels[$j] + $levelAdjust;
2717c7d1e03SGreg Roach
2727c7d1e03SGreg Roach                $newged .= "\n" . str_replace("\n", "\n" . $next_level . ' CONT ', $newline);
2737c7d1e03SGreg Roach            }
2747c7d1e03SGreg Roach        }
2757c7d1e03SGreg Roach
2767c7d1e03SGreg Roach        return $newged;
2777c7d1e03SGreg Roach    }
2787c7d1e03SGreg Roach
2797c7d1e03SGreg Roach    /**
2807c7d1e03SGreg Roach     * Create a form to add a new fact.
2817c7d1e03SGreg Roach     *
2827c7d1e03SGreg Roach     * @param ServerRequestInterface $request
2837c7d1e03SGreg Roach     * @param Tree                   $tree
2847c7d1e03SGreg Roach     * @param string                 $fact
2857c7d1e03SGreg Roach     *
2867c7d1e03SGreg Roach     * @return string
2877c7d1e03SGreg Roach     */
28824f2a3afSGreg Roach    public function addNewFact(ServerRequestInterface $request, Tree $tree, string $fact): string
2897c7d1e03SGreg Roach    {
2907c7d1e03SGreg Roach        $params = (array) $request->getParsedBody();
2917c7d1e03SGreg Roach
2927c7d1e03SGreg Roach        $FACT = $params[$fact];
2937c7d1e03SGreg Roach        $DATE = $params[$fact . '_DATE'] ?? '';
2947c7d1e03SGreg Roach        $PLAC = $params[$fact . '_PLAC'] ?? '';
2957c7d1e03SGreg Roach
2967c7d1e03SGreg Roach        if ($DATE !== '' || $PLAC !== '' || $FACT !== '' && $FACT !== 'Y') {
2977c7d1e03SGreg Roach            if ($FACT !== '' && $FACT !== 'Y') {
2987c7d1e03SGreg Roach                $gedrec = "\n1 " . $fact . ' ' . $FACT;
2997c7d1e03SGreg Roach            } else {
3007c7d1e03SGreg Roach                $gedrec = "\n1 " . $fact;
3017c7d1e03SGreg Roach            }
3027c7d1e03SGreg Roach            if ($DATE !== '') {
3037c7d1e03SGreg Roach                $gedrec .= "\n2 DATE " . $DATE;
3047c7d1e03SGreg Roach            }
3057c7d1e03SGreg Roach            if ($PLAC !== '') {
3067c7d1e03SGreg Roach                $gedrec .= "\n2 PLAC " . $PLAC;
3077c7d1e03SGreg Roach
3087c7d1e03SGreg Roach                if (preg_match_all('/(' . Gedcom::REGEX_TAG . ')/', $tree->getPreference('ADVANCED_PLAC_FACTS'), $match)) {
3097c7d1e03SGreg Roach                    foreach ($match[1] as $tag) {
3107c7d1e03SGreg Roach                        $TAG = $params[$fact . '_' . $tag];
3117c7d1e03SGreg Roach                        if ($TAG !== '') {
3127c7d1e03SGreg Roach                            $gedrec .= "\n3 " . $tag . ' ' . $TAG;
3137c7d1e03SGreg Roach                        }
3147c7d1e03SGreg Roach                    }
3157c7d1e03SGreg Roach                }
31650f35038SGreg Roach                $LATI = $params[$fact . '_LATI'] ?? '';
31750f35038SGreg Roach                $LONG = $params[$fact . '_LONG'] ?? '';
3187c7d1e03SGreg Roach                if ($LATI !== '' || $LONG !== '') {
3197c7d1e03SGreg Roach                    $gedrec .= "\n3 MAP\n4 LATI " . $LATI . "\n4 LONG " . $LONG;
3207c7d1e03SGreg Roach                }
3217c7d1e03SGreg Roach            }
3227c7d1e03SGreg Roach            if ((bool) ($params['SOUR_' . $fact] ?? false)) {
3237c7d1e03SGreg Roach                return $this->updateSource($gedrec, 'yes');
3247c7d1e03SGreg Roach            }
3257c7d1e03SGreg Roach
3267c7d1e03SGreg Roach            return $gedrec;
3277c7d1e03SGreg Roach        }
3287c7d1e03SGreg Roach
3297c7d1e03SGreg Roach        if ($FACT === 'Y') {
3307c7d1e03SGreg Roach            if ((bool) ($params['SOUR_' . $fact] ?? false)) {
3317c7d1e03SGreg Roach                return $this->updateSource("\n1 " . $fact . ' Y', 'yes');
3327c7d1e03SGreg Roach            }
3337c7d1e03SGreg Roach
3347c7d1e03SGreg Roach            return "\n1 " . $fact . ' Y';
3357c7d1e03SGreg Roach        }
3367c7d1e03SGreg Roach
3377c7d1e03SGreg Roach        return '';
3387c7d1e03SGreg Roach    }
3397c7d1e03SGreg Roach
3407c7d1e03SGreg Roach    /**
3417c7d1e03SGreg Roach     * Add new GEDCOM lines from the $xxxSOUR interface update arrays, which
3427c7d1e03SGreg Roach     * were produced by the splitSOUR() function.
3437c7d1e03SGreg Roach     * See the FunctionsEdit::handle_updatesges() function for details.
3447c7d1e03SGreg Roach     *
3457c7d1e03SGreg Roach     * @param string $inputRec
3467c7d1e03SGreg Roach     * @param string $levelOverride
3477c7d1e03SGreg Roach     *
3487c7d1e03SGreg Roach     * @return string
3497c7d1e03SGreg Roach     */
3507c7d1e03SGreg Roach    public function updateSource(string $inputRec, string $levelOverride = 'no'): string
3517c7d1e03SGreg Roach    {
3527c7d1e03SGreg Roach        if (count($this->tagSOUR) === 0) {
3537c7d1e03SGreg Roach            return $inputRec; // No update required
3547c7d1e03SGreg Roach        }
3557c7d1e03SGreg Roach
3567c7d1e03SGreg Roach        // Save original interface update arrays before replacing them with the xxxSOUR ones
3577c7d1e03SGreg Roach        $glevelsSave = $this->glevels;
3587c7d1e03SGreg Roach        $tagSave     = $this->tag;
3597c7d1e03SGreg Roach        $islinkSave  = $this->islink;
3607c7d1e03SGreg Roach        $textSave    = $this->text;
3617c7d1e03SGreg Roach
3627c7d1e03SGreg Roach        $this->glevels = $this->glevelsSOUR;
3637c7d1e03SGreg Roach        $this->tag     = $this->tagSOUR;
3647c7d1e03SGreg Roach        $this->islink  = $this->islinkSOUR;
3657c7d1e03SGreg Roach        $this->text    = $this->textSOUR;
3667c7d1e03SGreg Roach
3677c7d1e03SGreg Roach        $myRecord = $this->handleUpdates($inputRec, $levelOverride); // Now do the update
3687c7d1e03SGreg Roach
3697c7d1e03SGreg Roach        // Restore the original interface update arrays (just in case ...)
3707c7d1e03SGreg Roach        $this->glevels = $glevelsSave;
3717c7d1e03SGreg Roach        $this->tag     = $tagSave;
3727c7d1e03SGreg Roach        $this->islink  = $islinkSave;
3737c7d1e03SGreg Roach        $this->text    = $textSave;
3747c7d1e03SGreg Roach
3757c7d1e03SGreg Roach        return $myRecord;
3767c7d1e03SGreg Roach    }
3777c7d1e03SGreg Roach
3787c7d1e03SGreg Roach    /**
3797c7d1e03SGreg Roach     * Create a form to add a sex record.
3807c7d1e03SGreg Roach     *
3817c7d1e03SGreg Roach     * @param ServerRequestInterface $request
3827c7d1e03SGreg Roach     *
3837c7d1e03SGreg Roach     * @return string
3847c7d1e03SGreg Roach     */
3857c7d1e03SGreg Roach    public function addNewSex(ServerRequestInterface $request): string
3867c7d1e03SGreg Roach    {
3877c7d1e03SGreg Roach        $params = (array) $request->getParsedBody();
3887c7d1e03SGreg Roach
3897c7d1e03SGreg Roach        switch ($params['SEX']) {
3907c7d1e03SGreg Roach            case 'M':
3917c7d1e03SGreg Roach                return "\n1 SEX M";
3927c7d1e03SGreg Roach            case 'F':
3937c7d1e03SGreg Roach                return "\n1 SEX F";
3947c7d1e03SGreg Roach            default:
3957c7d1e03SGreg Roach                return "\n1 SEX U";
3967c7d1e03SGreg Roach        }
3977c7d1e03SGreg Roach    }
3987c7d1e03SGreg Roach
3997c7d1e03SGreg Roach    /**
4007c7d1e03SGreg Roach     * Assemble the pieces of a newly created record into gedcom
4017c7d1e03SGreg Roach     *
4027c7d1e03SGreg Roach     * @param ServerRequestInterface $request
4037c7d1e03SGreg Roach     * @param Tree                   $tree
4047c7d1e03SGreg Roach     *
4057c7d1e03SGreg Roach     * @return string
4067c7d1e03SGreg Roach     */
4077c7d1e03SGreg Roach    public function addNewName(ServerRequestInterface $request, Tree $tree): string
4087c7d1e03SGreg Roach    {
4097c7d1e03SGreg Roach        $params = (array) $request->getParsedBody();
4107c7d1e03SGreg Roach        $gedrec = "\n1 NAME " . $params['NAME'];
4117c7d1e03SGreg Roach
4127c7d1e03SGreg Roach        $tags = [
4137c7d1e03SGreg Roach            'NPFX',
4147c7d1e03SGreg Roach            'GIVN',
4157c7d1e03SGreg Roach            'SPFX',
4167c7d1e03SGreg Roach            'SURN',
4177c7d1e03SGreg Roach            'NSFX',
4187c7d1e03SGreg Roach            'NICK',
4197c7d1e03SGreg Roach        ];
4207c7d1e03SGreg Roach
4217c7d1e03SGreg Roach        if (preg_match_all('/(' . Gedcom::REGEX_TAG . ')/', $tree->getPreference('ADVANCED_NAME_FACTS'), $match)) {
4227c7d1e03SGreg Roach            $tags = array_merge($tags, $match[1]);
4237c7d1e03SGreg Roach        }
4247c7d1e03SGreg Roach
4257c7d1e03SGreg Roach        // Paternal and Polish and Lithuanian surname traditions can also create a _MARNM
4267c7d1e03SGreg Roach        $SURNAME_TRADITION = $tree->getPreference('SURNAME_TRADITION');
4277c7d1e03SGreg Roach        if ($SURNAME_TRADITION === 'paternal' || $SURNAME_TRADITION === 'polish' || $SURNAME_TRADITION === 'lithuanian') {
4287c7d1e03SGreg Roach            $tags[] = '_MARNM';
4297c7d1e03SGreg Roach        }
4307c7d1e03SGreg Roach
4317c7d1e03SGreg Roach        foreach (array_unique($tags) as $tag) {
4327c7d1e03SGreg Roach            $TAG = $params[$tag];
4337c7d1e03SGreg Roach
4347c7d1e03SGreg Roach            if ($TAG !== '') {
4357c7d1e03SGreg Roach                $gedrec .= "\n2 " . $tag . ' ' . $TAG;
4367c7d1e03SGreg Roach            }
4377c7d1e03SGreg Roach        }
4387c7d1e03SGreg Roach
4397c7d1e03SGreg Roach        return $gedrec;
4407c7d1e03SGreg Roach    }
441c2ed51d1SGreg Roach
442c2ed51d1SGreg Roach    /**
443c2ed51d1SGreg Roach     * Reassemble edited GEDCOM fields into a GEDCOM fact/event string.
444c2ed51d1SGreg Roach     *
445c2ed51d1SGreg Roach     * @param string        $record_type
446c2ed51d1SGreg Roach     * @param array<string> $levels
447c2ed51d1SGreg Roach     * @param array<string> $tags
448c2ed51d1SGreg Roach     * @param array<string> $values
449c2ed51d1SGreg Roach     *
450c2ed51d1SGreg Roach     * @return string
451c2ed51d1SGreg Roach     */
452c2ed51d1SGreg Roach    public function editLinesToGedcom(string $record_type, array $levels, array $tags, array $values): string
453c2ed51d1SGreg Roach    {
454c2ed51d1SGreg Roach        // Assert all arrays are the same size.
455c2ed51d1SGreg Roach        $count = count($levels);
456c2ed51d1SGreg Roach        assert($count > 0);
457c2ed51d1SGreg Roach        assert(count($tags) === $count);
458c2ed51d1SGreg Roach        assert(count($values) === $count);
459c2ed51d1SGreg Roach
460c2ed51d1SGreg Roach        $gedcom_lines = [];
461c2ed51d1SGreg Roach        $hierarchy    = [$record_type];
462c2ed51d1SGreg Roach
463c2ed51d1SGreg Roach        for ($i = 0; $i < $count; $i++) {
464c2ed51d1SGreg Roach            $hierarchy[$levels[$i]] = $tags[$i];
465c2ed51d1SGreg Roach
466c2ed51d1SGreg Roach            $full_tag   = implode(':', array_slice($hierarchy, 0, 1 + (int) $levels[$i]));
467c2ed51d1SGreg Roach            $element    = Registry::elementFactory()->make($full_tag);
468c2ed51d1SGreg Roach            $values[$i] = $element->canonical($values[$i]);
469c2ed51d1SGreg Roach
470c2ed51d1SGreg Roach            // If "1 FACT Y" has a DATE or PLAC, then delete the value of Y
471c2ed51d1SGreg Roach            if ($levels[$i] === '1' && $values[$i] === 'Y') {
472c2ed51d1SGreg Roach                for ($j = $i + 1; $j < $count && $levels[$j] > $levels[$i]; ++$j) {
473c2ed51d1SGreg Roach                    if ($levels[$j] === '2' && ($tags[$j] === 'DATE' || $tags[$j] === 'PLAC') && $values[$j] !== '') {
474c2ed51d1SGreg Roach                        $values[$i] = '';
475c2ed51d1SGreg Roach                        break;
476c2ed51d1SGreg Roach                    }
477c2ed51d1SGreg Roach                }
478c2ed51d1SGreg Roach            }
479c2ed51d1SGreg Roach
480c2ed51d1SGreg Roach            // Include this line if there is a value - or if there is a child record with a value.
481c2ed51d1SGreg Roach            $include = $values[$i] !== '';
482c2ed51d1SGreg Roach
483c2ed51d1SGreg Roach            for ($j = $i + 1; !$include && $j < $count && $levels[$j] > $levels[$i]; $j++) {
484c2ed51d1SGreg Roach                $include = $values[$j] !== '';
485c2ed51d1SGreg Roach            }
486c2ed51d1SGreg Roach
487c2ed51d1SGreg Roach            if ($include) {
488c2ed51d1SGreg Roach                if ($values[$i] === '') {
489c2ed51d1SGreg Roach                    $gedcom_lines[] = $levels[$i] . ' ' . $tags[$i];
490c2ed51d1SGreg Roach                } else {
491f7c88e25SGreg Roach                    if ($tags[$i] === 'CONC') {
492f7c88e25SGreg Roach                        $next_level = (int) $levels[$i];
493f7c88e25SGreg Roach                    } else {
494c2ed51d1SGreg Roach                        $next_level = 1 + (int) $levels[$i];
495f7c88e25SGreg Roach                    }
496c2ed51d1SGreg Roach
497c2ed51d1SGreg Roach                    $gedcom_lines[] = $levels[$i] . ' ' . $tags[$i] . ' ' . str_replace("\n", "\n" . $next_level . ' CONT ', $values[$i]);
498c2ed51d1SGreg Roach                }
499c2ed51d1SGreg Roach            }
500c2ed51d1SGreg Roach        }
501c2ed51d1SGreg Roach
502c2ed51d1SGreg Roach        return implode("\n", $gedcom_lines);
503c2ed51d1SGreg Roach    }
504*e22e42f7SGreg Roach
505*e22e42f7SGreg Roach    /**
506*e22e42f7SGreg Roach     * Add blank lines, to allow a user to add/edit new values.
507*e22e42f7SGreg Roach     *
508*e22e42f7SGreg Roach     * @param Fact $fact
509*e22e42f7SGreg Roach     * @param bool $include_hidden
510*e22e42f7SGreg Roach     *
511*e22e42f7SGreg Roach     * @return string
512*e22e42f7SGreg Roach     */
513*e22e42f7SGreg Roach    public function insertMissingSubtags(Fact $fact, bool $include_hidden): string
514*e22e42f7SGreg Roach    {
515*e22e42f7SGreg Roach        return $fact->record()->insertMissingLevels($fact->tag(), $fact->gedcom(), $include_hidden);
516*e22e42f7SGreg Roach    }
5177c7d1e03SGreg Roach}
518