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