Print all of the notes in this fact record

@param Tree   $tree
@param string $factrec The factrecord to print the notes from
@param int    $level   The level of the factrecord

@return string HTML
513305
public static function printFactNotes(Tree $tree, $factrec, $level): string
    {
        $data          = '';
        $previous_spos = 0;
        $nlevel        = $level + 1;
        $ct            = preg_match_all("/$level NOTE (.*)/", $factrec, $match, PREG_SET_ORDER);
        for ($j = 0; $j < $ct; $j++) {
            $spos1 = strpos($factrec, $match[$j][0], $previous_spos);
            $spos2 = strpos($factrec . "\n$level", "\n$level", $spos1 + 1);
            if (!$spos2) {
                $spos2 = strlen($factrec);
            }
            $previous_spos = $spos2;
            $nrec          = substr($factrec, $spos1, $spos2 - $spos1);
            if (!isset($match[$j][1])) {
                $match[$j][1] = '';
            }
            if (!preg_match('/^@(' . Gedcom::REGEX_XREF . ')@$/', $match[$j][1], $nmatch)) {
                $data .= self::printNoteRecord($tree, $match[$j][1], $nlevel, $nrec);
            } else {
                $note = Note::getInstance($nmatch[1], $tree);
                if ($note) {
                    if ($note->canShow()) {
                        $noterec = $note->gedcom();
                        $nt      = preg_match("/0 @$nmatch[1]@ NOTE (.*)/", $noterec, $n1match);
                        $data    .= self::printNoteRecord($tree, ($nt > 0) ? $n1match[1] : '', 1, $noterec);
                    }
                } else {
                    $data = '<div class="fact_NOTE"><span class="label">' . I18N::translate('Note') . '</span>: <span class="field error">' . $nmatch[1] . '</span></div>';
                }
            }
        }

        return $data;
    }
964508
private function countCommonXrefs(Tree $tree1, Tree $tree2): int
    {
        $subquery1 = DB::table('individuals')
            ->where('i_file', '=', $tree1->id())
            ->select(['i_id AS xref'])
            ->union(DB::table('families')
                ->where('f_file', '=', $tree1->id())
                ->select(['f_id AS xref']))
            ->union(DB::table('sources')
                ->where('s_file', '=', $tree1->id())
                ->select(['s_id AS xref']))
            ->union(DB::table('media')
                ->where('m_file', '=', $tree1->id())
                ->select(['m_id AS xref']))
            ->union(DB::table('other')
                ->where('o_file', '=', $tree1->id())
                ->whereNotIn('o_type', ['HEAD', 'TRLR'])
                ->select(['o_id AS xref']));

        $subquery2 = DB::table('change')
            ->where('gedcom_id', '=', $tree2->id())
            ->select(['xref AS other_xref'])
            ->union(DB::table('individuals')
                ->where('i_file', '=', $tree2->id())
                ->select(['i_id AS xref']))
            ->union(DB::table('families')
                ->where('f_file', '=', $tree2->id())
                ->select(['f_id AS xref']))
            ->union(DB::table('sources')
                ->where('s_file', '=', $tree2->id())
                ->select(['s_id AS xref']))
            ->union(DB::table('media')
                ->where('m_file', '=', $tree2->id())
                ->select(['m_id AS xref']))
            ->union(DB::table('other')
                ->where('o_file', '=', $tree2->id())
                ->whereNotIn('o_type', ['HEAD', 'TRLR'])
                ->select(['o_id AS xref']));

        return DB::table(DB::raw('(' . $subquery1->toSql() . ') AS sub1'))
            ->mergeBindings($subquery1)
            ->joinSub($subquery2, 'sub2', 'other_xref', '=', 'xref')
            ->count();
    }
552203
private static function printNoteRecord(Tree $tree, $text, $nlevel, $nrec): string
    {
        $text .= Functions::getCont($nlevel, $nrec);

        // Check if shared note (we have already checked that it exists)
        if (preg_match('/^0 @(' . Gedcom::REGEX_XREF . ')@ NOTE/', $nrec, $match)) {
            $note  = Note::getInstance($match[1], $tree);
            $label = 'SHARED_NOTE';
            $html  = Filter::formatText($note->getNote(), $tree);
        } else {
            $note  = null;
            $label = 'NOTE';
            $html  = Filter::formatText($text, $tree);
        }

        if (strpos($text, "\n") === false) {
            // A one-line note? strip the block-level tags, so it displays inline
            return GedcomTag::getLabelValue($label, strip_tags($html, '<a><strong><em>'));
        }

        if ($tree->getPreference('EXPAND_NOTES')) {
            // A multi-line note, and we're expanding notes by default
            return GedcomTag::getLabelValue($label, $html);
        }

        // A multi-line note, with an expand/collapse option
        $element_id = Uuid::uuid4()->toString();
        // NOTE: class "note-details" is (currently) used only by some third-party themes
        if ($note) {
            $first_line = '<a href="' . e($note->url()) . '">' . $note->fullName() . '</a>';
        } else {
            [$text] = explode("\n", strip_tags($html));
            $first_line = Str::limit($text, 100, I18N::translate('…'));
        }

        return
            '<div class="fact_NOTE"><span class="label">' .
            '<a href="#" onclick="expand_layer(\'' . $element_id . '\'); return false;"><i id="' . $element_id . '_img" class="icon-plus"></i></a> ' . GedcomTag::getLabel($label) . ':</span> ' . '<span id="' . $element_id . '-alt">' . $first_line . '</span>' .
            '</div>' .
            '<div class="note-details" id="' . $element_id . '" style="display:none">' . $html . '</div>';
    }
1334418
private function allNotes(Tree $tree): Collection
    {
        return DB::table('other')
            ->where('o_file', '=', $tree->id())
            ->where('o_type', '=', 'NOTE')
            ->get()
            ->map(Note::rowMapper())
            ->filter(GedcomRecord::accessFilter());
    }
867361
public function log($level, $message, array $context = [])
    {
        if (!isset($this->logLevels[$level])) {
            $level = LogLevel::DEBUG;
        }

        if ($this->logLevels[$level] < $this->level) {
            // do not print.
            return;
        }

        if ($this->level === 3 && $this->logLevels[$level] === 3) {
            $style = 'dim';
        } else {
            $style = $this->levelStyles[$level];
        }

        if ($this->indent) {
            $this->write(\str_repeat($this->indentCharacter, $this->indent));
        }

        if (\is_object($message) || \is_array($message)) {
            $this->writeln(\print_r($message, true), $style);
        } else {
            $this->writeln($message, $style);
        }
    }
610931
public static function createMediaObject($level, $gedrec, Tree $tree): string
    {
        if (preg_match('/\n\d FILE (.+)/', $gedrec, $file_match)) {
            $file = $file_match[1];
        } else {
            $file = '';
        }

        if (preg_match('/\n\d TITL (.+)/', $gedrec, $file_match)) {
            $titl = $file_match[1];
        } else {
            $titl = '';
        }

        // Have we already created a media object with the same title/filename?
        $xref = DB::table('media_file')
            ->where('m_file', '=', $tree->id())
            ->where('descriptive_title', '=', $titl)
            ->where('multimedia_file_refn', '=', mb_substr($file, 0, 248))
            ->value('m_id');

        if (!$xref) {
            $xref = $tree->getNewXref();
            // renumber the lines
            $gedrec = preg_replace_callback('/\n(\d+)/', static function (array $m) use ($level): string {
                return "\n" . ($m[1] - $level);
            }, $gedrec);
            // convert to an object
            $gedrec = str_replace("\n0 OBJE\n", '0 @' . $xref . "@ OBJE\n", $gedrec);

            // Fix Legacy GEDCOMS
            $gedrec = preg_replace('/\n1 FORM (.+)\n1 FILE (.+)\n1 TITL (.+)/', "\n1 FILE $2\n2 FORM $1\n2 TITL $3", $gedrec);

            // Fix FTB GEDCOMS
            $gedrec = preg_replace('/\n1 FORM (.+)\n1 TITL (.+)\n1 FILE (.+)/', "\n1 FILE $3\n2 FORM $1\n2 TITL $2", $gedrec);

            // Fix RM7 GEDCOMS
            $gedrec = preg_replace('/\n1 FILE (.+)\n1 FORM (.+)\n1 TITL (.+)/', "\n1 FILE $1\n2 FORM $2\n2 TITL $3", $gedrec);

            // Create new record
            $record = new Media($xref, $gedrec, null, $tree);

            DB::table('media')->insert([
                'm_id'     => $xref,
                'm_file'   => $tree->id(),
                'm_gedcom' => $gedrec,
            ]);

            foreach ($record->mediaFiles() as $media_file) {
                DB::table('media_file')->insert([
                    'm_id'                 => $xref,
                    'm_file'               => $tree->id(),
                    'multimedia_file_refn' => mb_substr($media_file->filename(), 0, 248),
                    'multimedia_format'    => mb_substr($media_file->format(), 0, 4),
                    'source_media_type'    => mb_substr($media_file->type(), 0, 15),
                    'descriptive_title'    => mb_substr($media_file->title(), 0, 248),
                ]);
            }
        }

        return "\n" . $level . ' OBJE @' . $xref . '@';
    }
547592
private function formatNameRecord(Tree $tree, $n, Fact $fact): string
    {
        $individual = $fact->record();

        // Create a dummy record, so we can extract the formatted NAME value from it.
        $dummy = new Individual(
            'xref',
            "0 @xref@ INDI\n1 DEAT Y\n" . $fact->gedcom(),
            null,
            $individual->tree()
        );
        $dummy->setPrimaryName(0); // Make sure we use the name from "1 NAME"

        $container_class = 'card';
        $content_class   = 'collapse';
        $aria            = 'false';

        if ($n === 0) {
            $content_class = 'collapse show';
            $aria          = 'true';
        }
        if ($fact->isPendingDeletion()) {
            $container_class .= ' old';
        } elseif ($fact->isPendingAddition()) {
            $container_class .= ' new';
        }

        ob_start();
        echo '<dl><dt class="label">', I18N::translate('Name'), '</dt>';
        echo '<dd class="field">', $dummy->fullName(), '</dd>';
        $ct = preg_match_all('/\n2 (\w+) (.*)/', $fact->gedcom(), $nmatch, PREG_SET_ORDER);
        for ($i = 0; $i < $ct; $i++) {
            $tag = $nmatch[$i][1];
            if ($tag !== 'SOUR' && $tag !== 'NOTE' && $tag !== 'SPFX') {
                echo '<dt class="label">', GedcomTag::getLabel($tag, $individual), '</dt>';
                echo '<dd class="field">'; // Before using dir="auto" on this field, note that Gecko treats this as an inline element but WebKit treats it as a block element
                if (isset($nmatch[$i][2])) {
                    $name = e($nmatch[$i][2]);
                    $name = str_replace('/', '', $name);
                    $name = preg_replace('/(\S*)\*/', '<span class="starredname">\\1</span>', $name);
                    switch ($tag) {
                        case 'TYPE':
                            echo GedcomCodeName::getValue($name, $individual);
                            break;
                        case 'SURN':
                            // The SURN field is not necessarily the surname.
                            // Where it is not a substring of the real surname, show it after the real surname.
                            $surname = e($dummy->getAllNames()[0]['surname']);
                            $surns   = preg_replace('/, */', ' ', $nmatch[$i][2]);
                            if (strpos($dummy->getAllNames()[0]['surname'], $surns) !== false) {
                                echo '<span dir="auto">' . $surname . '</span>';
                            } else {
                                echo I18N::translate('%1$s (%2$s)', '<span dir="auto">' . $surname . '</span>', '<span dir="auto">' . $name . '</span>');
                            }
                            break;
                        default:
                            echo '<span dir="auto">' . $name . '</span>';
                            break;
                    }
                }
                echo '</dd>';
                echo '</dl>';
            }
        }
        if (strpos($fact->gedcom(), "\n2 SOUR") !== false) {
            echo '<div id="indi_sour" class="clearfix">', FunctionsPrintFacts::printFactSources($tree, $fact->gedcom(), 2), '</div>';
        }
        if (strpos($fact->gedcom(), "\n2 NOTE") !== false) {
            echo '<div id="indi_note" class="clearfix">', FunctionsPrint::printFactNotes($tree, $fact->gedcom(), 2), '</div>';
        }
        $content = ob_get_clean();

        if ($fact->canEdit()) {
            $edit_links =
                '<a class="btn btn-link" href="#" data-confirm="' . I18N::translate('Are you sure you want to delete this fact?') . '" onclick="return delete_fact(this.dataset.confirm, \'' . e($individual->tree()->name()) . '\', \'' . e($individual->xref()) . '\', \'' . $fact->id() . '\');" title="' . I18N::translate('Delete this name') . '">' . view('icons/delete') . '<span class="sr-only">' . I18N::translate('Delete this name') . '</span></a>' .
                '<a class="btn btn-link" href="' . e(route('edit-name', ['xref' => $individual->xref(), 'fact_id' => $fact->id(), 'ged' => $individual->tree()->name()])) . '" title="' . I18N::translate('Edit the name') . '">' . view('icons/edit') . '<span class="sr-only">' . I18N::translate('Edit the name') . '</span></a>';
        } else {
            $edit_links = '';
        }

        return '
			<div class="' . $container_class . '">
        <div class="card-header" role="tab" id="name-header-' . $n . '">
		        <a data-toggle="collapse" data-parent="#individual-names" href="#name-content-' . $n . '" aria-expanded="' . $aria . '" aria-controls="name-content-' . $n . '">' . $dummy->fullName() . '</a>
		      ' . $edit_links . '
        </div>
		    <div id="name-content-' . $n . '" class="' . $content_class . '" role="tabpanel" aria-labelledby="name-header-' . $n . '">
		      <div class="card-body">' . $content . '</div>
        </div>
      </div>';
    }
911549
public function editFact(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $xref    = $request->get('xref', '');
        $fact_id = $request->get('fact_id', '');

        $record = GedcomRecord::getInstance($xref, $tree);
        Auth::checkRecordAccess($record, true);

        // Find the fact to edit
        $edit_fact = null;
        foreach ($record->facts() as $fact) {
            if ($fact->id() === $fact_id && $fact->canEdit()) {
                $edit_fact = $fact;
                break;
            }
        }
        if ($edit_fact === null) {
            throw new NotFoundHttpException();
        }

        $can_edit_raw = Auth::isAdmin() || $tree->getPreference('SHOW_GEDCOM_RECORD');

        $title = $record->fullName() . ' - ' . GedcomTag::getLabel($edit_fact->getTag());

        return $this->viewResponse('edit/edit-fact', [
            'can_edit_raw' => $can_edit_raw,
            'edit_fact'    => $edit_fact,
            'record'       => $record,
            'title'        => $title,
            'tree'         => $tree,
        ]);
    }
184056
public function copyFact(Fact $fact): void
    {
        $clipboard   = Session::get('clipboard', []);
        $record_type = $fact->record()::RECORD_TYPE;
        $fact_id     = $fact->id();

        // If we are copying the same fact twice, make sure the new one is at the end.
        unset($clipboard[$record_type][$fact_id]);

        $clipboard[$record_type][$fact_id] = [
            'factrec' => $fact->gedcom(),
            'fact'    => $fact->getTag(),
        ];

        // The clipboard only holds a limited number of facts.
        $clipboard[$record_type] = array_slice($clipboard[$record_type], -self::CLIPBOARD_SIZE);

        Session::put('clipboard', $clipboard);
    }
1227532
public void printTree(CommonTree t, int indent) {
        if ( t != null ) {
            StringBuffer sb = new StringBuffer(indent);
            for ( int i = 0; i < indent; i++ )
                sb = sb.append("   ");
            for ( int i = 0; i < t.getChildCount(); i++ ) {
                System.out.println("" + i + sb.toString() + t.getChild(i).toString());
                printTree((CommonTree)t.getChild(i), indent+1);
            }
        }
    }
804129
protected static function fetchGedcomRecord(string $xref, int $tree_id): ?string
    {
        // We don't know what type of object this is. Try each one in turn.
        $data = Individual::fetchGedcomRecord($xref, $tree_id);
        if ($data !== null) {
            return $data;
        }
        $data = Family::fetchGedcomRecord($xref, $tree_id);
        if ($data !== null) {
            return $data;
        }
        $data = Source::fetchGedcomRecord($xref, $tree_id);
        if ($data !== null) {
            return $data;
        }
        $data = Repository::fetchGedcomRecord($xref, $tree_id);
        if ($data !== null) {
            return $data;
        }
        $data = Media::fetchGedcomRecord($xref, $tree_id);
        if ($data !== null) {
            return $data;
        }
        $data = Note::fetchGedcomRecord($xref, $tree_id);
        if ($data !== null) {
            return $data;
        }

        // Some other type of record...
        return DB::table('other')
            ->where('o_file', '=', $tree_id)
            ->where('o_id', '=', $xref)
            ->value('o_gedcom');
    }
41378
protected function renderNode($tree, $depth = 0, $cap = 0) {

        $n = count($tree);
        foreach ($tree as $k => $next) {
            for ($pre = "", $i = $depth - 1; $i >= 0; $i--){
                $pre.= $cap >> $i & 1 ? "│  " : "   ";
            }
            echo $pre, --$n > 0 ? '├──' : '└──', $k, PHP_EOL;
            if (false === empty($next)){
                $this->renderNode($next, $depth + 1, ($cap << 1) | ($n > 0));
            }
        }
    }
328310
public function _log($level, $fmt, $allArgs) {
        // The $allArgs variable contains the $fmt string
        array_shift($allArgs);
        $text = vsprintf($fmt, $allArgs);

        $this->_rawLogRecord(array(
            'level' => $level,
            'message' => $text,
        ), $allArgs);

        $this->flushIfNeeded();
        return $text;
    }
597236
public function show(ServerRequestInterface $request, Tree $tree, ClipboardService $clipboard_service): ResponseInterface
    {
        $xref   = $request->get('xref', '');
        $record = Source::getInstance($xref, $tree);

        Auth::checkSourceAccess($record, false);

        return $this->viewResponse('source-page', [
            'clipboard_facts' => $clipboard_service->pastableFacts($record, new Collection()),
            'facts'           => $this->facts($record),
            'families'        => $record->linkedFamilies('SOUR'),
            'individuals'     => $record->linkedIndividuals('SOUR'),
            'meta_robots'     => 'index,follow',
            'notes'           => $record->linkedNotes('SOUR'),
            'media_objects'   => $record->linkedMedia('SOUR'),
            'source'          => $record,
            'title'           => $record->fullName(),
        ]);
    }
426644
public static function printMainNotes(Fact $fact, int $level): void
    {
        $factrec = $fact->gedcom();
        $parent  = $fact->record();
        $tree    = $parent->tree();

        if ($fact->isPendingAddition()) {
            $styleadd = ' new';
            $can_edit = $level === 1 && $fact->canEdit();
        } elseif ($fact->isPendingDeletion()) {
            $styleadd = ' old';
            $can_edit = false;
        } else {
            $styleadd = '';
            $can_edit = $level === 1 && $fact->canEdit();
        }

        $ct = preg_match_all("/$level NOTE (.*)/", $factrec, $match, PREG_SET_ORDER);
        for ($j = 0; $j < $ct; $j++) {
            // Note object, or inline note?
            if (preg_match("/$level NOTE @(.*)@/", $match[$j][0], $nmatch)) {
                $note = Note::getInstance($nmatch[1], $tree);
                if ($note && !$note->canShow()) {
                    continue;
                }
            } else {
                $note = null;
            }

            if ($level >= 2) {
                echo '<tr class="wt-level-two-note collapse"><th scope="row" class="rela ', $styleadd, '">';
            } else {
                echo '<tr><th scope="row" class="', $styleadd, '">';
            }
            if ($can_edit) {
                if ($level < 2) {
                    if ($note instanceof Note) {
                        echo '<a href="' . e($note->url()) . '">';
                        echo GedcomTag::getLabel('SHARED_NOTE');
                        echo view('icons/note');
                        echo '</a>';
                    } else {
                        echo GedcomTag::getLabel('NOTE');
                    }
                    echo '<div class="editfacts">';
                    echo view('edit/icon-fact-edit', ['fact' => $fact]);
                    echo view('edit/icon-fact-copy', ['fact' => $fact]);
                    echo view('edit/icon-fact-delete', ['fact' => $fact]);
                    echo '</div>';
                }
            } else {
                if ($level < 2) {
                    if ($tree->getPreference('SHOW_FACT_ICONS')) {
                        echo '<i class="icon-note"></i> ';
                    }
                    if ($note) {
                        echo GedcomTag::getLabel('SHARED_NOTE');
                    } else {
                        echo GedcomTag::getLabel('NOTE');
                    }
                }
                $factlines = explode("\n", $factrec); // 1 BIRT Y\n2 NOTE ...
                $factwords = explode(' ', $factlines[0]); // 1 BIRT Y
                $factname  = $factwords[1]; // BIRT
                if ($factname === 'EVEN' || $factname === 'FACT') {
                    // Add ' EVEN' to provide sensible output for an event with an empty TYPE record
                    $ct = preg_match('/2 TYPE (.*)/', $factrec, $ematch);
                    if ($ct > 0) {
                        $factname = trim($ematch[1]);
                        echo $factname;
                    } else {
                        echo GedcomTag::getLabel($factname, $parent);
                    }
                } elseif ($factname !== 'NOTE') {
                    // Note is already printed
                    echo GedcomTag::getLabel($factname, $parent);
                    if ($note) {
                        echo '<a class="btn btn-link" href="' . e($note->url()) . '" title="' . I18N::translate('View') . '"><span class="sr-only">' . I18N::translate('View') . '</span></a>';
                    }
                }
            }
            echo '</th>';
            if ($note) {
                // Note objects
                $text = Filter::formatText($note->getNote(), $tree);
            } else {
                // Inline notes
                $nrec = Functions::getSubRecord($level, "$level NOTE", $factrec, $j + 1);
                $text = $match[$j][1] . Functions::getCont($level + 1, $nrec);
                $text = Filter::formatText($text, $tree);
            }

            echo '<td class="optionbox', $styleadd, ' wrap">';
            echo $text;

            // 2 RESN tags. Note, there can be more than one, such as "privacy" and "locked"
            if (preg_match_all("/\n2 RESN (.+)/", $factrec, $matches)) {
                foreach ($matches[1] as $match) {
                    echo '<br><span class="label">', GedcomTag::getLabel('RESN'), ':</span> <span class="field">';
                    switch ($match) {
                        case 'none':
                            // Note: "2 RESN none" is not valid gedcom, and the GUI will not let you add it.
                            // However, webtrees privacy rules will interpret it as "show an otherwise private fact to public".
                            echo '<i class="icon-resn-none"></i> ', I18N::translate('Show to visitors');
                            break;
                        case 'privacy':
                            echo '<i class="icon-resn-privacy"></i> ', I18N::translate('Show to members');
                            break;
                        case 'confidential':
                            echo '<i class="icon-resn-confidential"></i> ', I18N::translate('Show to managers');
                            break;
                        case 'locked':
                            echo '<i class="icon-resn-locked"></i> ', I18N::translate('Only managers can edit');
                            break;
                        default:
                            echo $match;
                            break;
                    }
                    echo '</span>';
                }
            }
            echo '</td></tr>';
        }
    }
88525
public static function gedcomHeader(Tree $tree, string $char): string
    {
        // Default values for a new header
        $HEAD = '0 HEAD';
        $SOUR = "\n1 SOUR " . Webtrees::NAME . "\n2 NAME " . Webtrees::NAME . "\n2 VERS " . Webtrees::VERSION;
        $DEST = "\n1 DEST DISKETTE";
        $DATE = "\n1 DATE " . strtoupper(date('d M Y')) . "\n2 TIME " . date('H:i:s');
        $GEDC = "\n1 GEDC\n2 VERS 5.5.1\n2 FORM Lineage-Linked";
        $CHAR = "\n1 CHAR " . $char;
        $FILE = "\n1 FILE " . $tree->name();
        $COPR = '';
        $LANG = '';
        $SUBN = '';
        $SUBM = "\n1 SUBM @SUBM@\n0 @SUBM@ SUBM\n1 NAME " . Auth::user()->userName(); // The SUBM record is mandatory

        // Preserve some values from the original header
        $record = GedcomRecord::getInstance('HEAD', $tree);
        $fact   = $record->facts(['COPR'])->first();
        if ($fact instanceof Fact) {
            $COPR = "\n1 COPR " . $fact->value();
        }
        $fact = $record->facts(['LANG'])->first();
        if ($fact instanceof Fact) {
            $LANG = "\n1 LANG " . $fact->value();
        }
        // Link to actual SUBM/SUBN records, if they exist
        $subn = DB::table('other')
            ->where('o_type', '=', 'SUBN')
            ->where('o_file', '=', $tree->id())
            ->value('o_id');
        if ($subn) {
            $SUBN = "\n1 SUBN @{$subn}@";
        }
        $subm = DB::table('other')
            ->where('o_type', '=', 'SUBM')
            ->where('o_file', '=', $tree->id())
            ->value('o_id');
        if ($subm) {
            $SUBM = "\n1 SUBM @{$subm}@";
        }

        return $HEAD . $SOUR . $DEST . $DATE . $SUBM . $SUBN . $FILE . $COPR . $GEDC . $CHAR . $LANG . "\n";
    }
998682
protected static function fetchGedcomRecord(string $xref, int $tree_id): ?string
    {
        return DB::table('families')
            ->where('f_id', '=', $xref)
            ->where('f_file', '=', $tree_id)
            ->value('f_gedcom');
    }