xref: /webtrees/app/Factories/GedcomRecordFactory.php (revision 1270d2767576ed4a83917769b0ee3613e3b010bf)
1<?php
2
3/**
4 * webtrees: online genealogy
5 * Copyright (C) 2023 webtrees development team
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17
18declare(strict_types=1);
19
20namespace Fisharebest\Webtrees\Factories;
21
22use Closure;
23use Fisharebest\Webtrees\Contracts\GedcomRecordFactoryInterface;
24use Fisharebest\Webtrees\DB;
25use Fisharebest\Webtrees\Family;
26use Fisharebest\Webtrees\Gedcom;
27use Fisharebest\Webtrees\GedcomRecord;
28use Fisharebest\Webtrees\Header;
29use Fisharebest\Webtrees\Individual;
30use Fisharebest\Webtrees\Location;
31use Fisharebest\Webtrees\Media;
32use Fisharebest\Webtrees\Note;
33use Fisharebest\Webtrees\Registry;
34use Fisharebest\Webtrees\Repository;
35use Fisharebest\Webtrees\Source;
36use Fisharebest\Webtrees\Submission;
37use Fisharebest\Webtrees\Submitter;
38use Fisharebest\Webtrees\Tree;
39use InvalidArgumentException;
40
41/**
42 * Make a GedcomRecord object.
43 */
44class GedcomRecordFactory extends AbstractGedcomRecordFactory implements GedcomRecordFactoryInterface
45{
46    /**
47     * Create a GedcomRecord object.
48     */
49    public function make(string $xref, Tree $tree, string|null $gedcom = null): GedcomRecord|null
50    {
51        // We know the type of the record.  Return it directly.
52        if ($gedcom !== null && preg_match('/^0(?: @[^@]+@)? ([A-Z_]+)/', $gedcom, $match)) {
53            switch ($match[1]) {
54                case Family::RECORD_TYPE:
55                    return Registry::familyFactory()->make($xref, $tree, $gedcom);
56                case Header::RECORD_TYPE:
57                    return Registry::headerFactory()->make($xref, $tree, $gedcom);
58                case Individual::RECORD_TYPE:
59                    return Registry::individualFactory()->make($xref, $tree, $gedcom);
60                case Location::RECORD_TYPE:
61                    return Registry::locationFactory()->make($xref, $tree, $gedcom);
62                case Media::RECORD_TYPE:
63                    return Registry::mediaFactory()->make($xref, $tree, $gedcom);
64                case Note::RECORD_TYPE:
65                    return Registry::noteFactory()->make($xref, $tree, $gedcom);
66                case Repository::RECORD_TYPE:
67                    return Registry::repositoryFactory()->make($xref, $tree, $gedcom);
68                case Source::RECORD_TYPE:
69                    return Registry::sourceFactory()->make($xref, $tree, $gedcom);
70                case Submitter::RECORD_TYPE:
71                    return Registry::submitterFactory()->make($xref, $tree, $gedcom);
72                case Submission::RECORD_TYPE:
73                    return Registry::submissionFactory()->make($xref, $tree, $gedcom);
74            }
75        }
76
77        // We do not know the type of the record.  Try them all in turn.
78        return
79            Registry::familyFactory()->make($xref, $tree, $gedcom) ??
80            Registry::individualFactory()->make($xref, $tree, $gedcom) ??
81            Registry::mediaFactory()->make($xref, $tree, $gedcom) ??
82            Registry::noteFactory()->make($xref, $tree, $gedcom) ??
83            Registry::repositoryFactory()->make($xref, $tree, $gedcom) ??
84            Registry::sourceFactory()->make($xref, $tree, $gedcom) ??
85            Registry::submitterFactory()->make($xref, $tree, $gedcom) ??
86            Registry::submissionFactory()->make($xref, $tree, $gedcom) ??
87            Registry::locationFactory()->make($xref, $tree, $gedcom) ??
88            Registry::headerFactory()->make($xref, $tree, $gedcom) ??
89            Registry::cache()->array()->remember(self::class . $xref . '@' . $tree->id(), function () use ($xref, $tree, $gedcom) {
90                $gedcom ??= $this->gedcom($xref, $tree);
91
92                $pending = $this->pendingChanges($tree)->get($xref);
93
94                if ($gedcom === null && $pending === null) {
95                    return null;
96                }
97
98                $xref = $this->extractXref($gedcom ?? $pending, $xref);
99                $type = $this->extractType($gedcom ?? $pending);
100
101                return $this->newGedcomRecord($type, $xref, $gedcom ?? '', $pending, $tree);
102            });
103    }
104
105    /**
106     * Create a GedcomRecord object from raw GEDCOM data.
107     *
108     * @param string      $xref
109     * @param string      $gedcom  an empty string for new/pending records
110     * @param string|null $pending null for a record with no pending edits,
111     *                             empty string for records with pending deletions
112     * @param Tree        $tree
113     *
114     * @return GedcomRecord
115     */
116    public function new(string $xref, string $gedcom, string|null $pending, Tree $tree): GedcomRecord
117    {
118        return new GedcomRecord($xref, $gedcom, $pending, $tree);
119    }
120
121    /**
122     * Create a GedcomRecord object from a row in the database.
123     *
124     * @param Tree $tree
125     *
126     * @return Closure(object):GedcomRecord
127     */
128    public function mapper(Tree $tree): Closure
129    {
130        return fn (object $row): GedcomRecord => $this->make($row->o_id, $tree, $row->o_gedcom);
131    }
132
133    /**
134     * @param string      $type
135     * @param string      $xref
136     * @param string      $gedcom
137     * @param string|null $pending
138     * @param Tree        $tree
139     *
140     * @return GedcomRecord
141     */
142    private function newGedcomRecord(string $type, string $xref, string $gedcom, string|null $pending, Tree $tree): GedcomRecord
143    {
144        switch ($type) {
145            case Family::RECORD_TYPE:
146                return Registry::familyFactory()->new($xref, $gedcom, $pending, $tree);
147
148            case Header::RECORD_TYPE:
149                return Registry::headerFactory()->new($xref, $gedcom, $pending, $tree);
150
151            case Individual::RECORD_TYPE:
152                return Registry::individualFactory()->new($xref, $gedcom, $pending, $tree);
153
154            case Media::RECORD_TYPE:
155                return Registry::mediaFactory()->new($xref, $gedcom, $pending, $tree);
156
157            case Note::RECORD_TYPE:
158                return Registry::noteFactory()->new($xref, $gedcom, $pending, $tree);
159
160            case Repository::RECORD_TYPE:
161                return Registry::repositoryFactory()->new($xref, $gedcom, $pending, $tree);
162
163            case Source::RECORD_TYPE:
164                return Registry::sourceFactory()->new($xref, $gedcom, $pending, $tree);
165
166            case Submission::RECORD_TYPE:
167                return Registry::submissionFactory()->new($xref, $gedcom, $pending, $tree);
168
169            case Submitter::RECORD_TYPE:
170                return Registry::submitterFactory()->new($xref, $gedcom, $pending, $tree);
171
172            default:
173                return $this->new($xref, $gedcom, $pending, $tree);
174        }
175    }
176
177    /**
178     * Extract the type of a GEDCOM record
179     *
180     * @param string $gedcom
181     *
182     * @return string
183     * @throws InvalidArgumentException
184     */
185    private function extractType(string $gedcom): string
186    {
187        if (preg_match('/^0(?: @' . Gedcom::REGEX_XREF . '@)? ([_A-Z0-9]+)/', $gedcom, $match)) {
188            return $match[1];
189        }
190
191        throw new InvalidArgumentException('Invalid GEDCOM record: ' . $gedcom);
192    }
193
194    /**
195     * Fetch GEDCOM data from the database.
196     *
197     * @param string $xref
198     * @param Tree   $tree
199     *
200     * @return string|null
201     */
202    private function gedcom(string $xref, Tree $tree): string|null
203    {
204        return DB::table('other')
205            ->where('o_id', '=', $xref)
206            ->where('o_file', '=', $tree->id())
207            ->whereNotIn('o_type', [
208                Header::RECORD_TYPE,
209                Note::RECORD_TYPE,
210                Repository::RECORD_TYPE,
211                Submission::RECORD_TYPE,
212                Submitter::RECORD_TYPE,
213            ])
214            ->value('o_gedcom');
215    }
216}
217