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 * @param string $xref 50 * @param Tree $tree 51 * @param string|null $gedcom 52 * 53 * @return GedcomRecord|null 54 */ 55 public function make(string $xref, Tree $tree, string $gedcom = null): ?GedcomRecord 56 { 57 // We know the type of the record. Return it directly. 58 if ($gedcom !== null && preg_match('/^0(?: @[^@]+@)? ([A-Z_]+)/', $gedcom, $match)) { 59 switch ($match[1]) { 60 case Family::RECORD_TYPE: 61 return Registry::familyFactory()->make($xref, $tree, $gedcom); 62 case Header::RECORD_TYPE: 63 return Registry::headerFactory()->make($xref, $tree, $gedcom); 64 case Individual::RECORD_TYPE: 65 return Registry::individualFactory()->make($xref, $tree, $gedcom); 66 case Location::RECORD_TYPE: 67 return Registry::locationFactory()->make($xref, $tree, $gedcom); 68 case Media::RECORD_TYPE: 69 return Registry::mediaFactory()->make($xref, $tree, $gedcom); 70 case Note::RECORD_TYPE: 71 return Registry::noteFactory()->make($xref, $tree, $gedcom); 72 case Repository::RECORD_TYPE: 73 return Registry::repositoryFactory()->make($xref, $tree, $gedcom); 74 case Source::RECORD_TYPE: 75 return Registry::sourceFactory()->make($xref, $tree, $gedcom); 76 case Submitter::RECORD_TYPE: 77 return Registry::submitterFactory()->make($xref, $tree, $gedcom); 78 case Submission::RECORD_TYPE: 79 return Registry::submissionFactory()->make($xref, $tree, $gedcom); 80 } 81 } 82 83 // We do not know the type of the record. Try them all in turn. 84 return 85 Registry::familyFactory()->make($xref, $tree, $gedcom) ?? 86 Registry::individualFactory()->make($xref, $tree, $gedcom) ?? 87 Registry::mediaFactory()->make($xref, $tree, $gedcom) ?? 88 Registry::noteFactory()->make($xref, $tree, $gedcom) ?? 89 Registry::repositoryFactory()->make($xref, $tree, $gedcom) ?? 90 Registry::sourceFactory()->make($xref, $tree, $gedcom) ?? 91 Registry::submitterFactory()->make($xref, $tree, $gedcom) ?? 92 Registry::submissionFactory()->make($xref, $tree, $gedcom) ?? 93 Registry::locationFactory()->make($xref, $tree, $gedcom) ?? 94 Registry::headerFactory()->make($xref, $tree, $gedcom) ?? 95 Registry::cache()->array()->remember(self::class . $xref . '@' . $tree->id(), function () use ($xref, $tree, $gedcom) { 96 $gedcom ??= $this->gedcom($xref, $tree); 97 98 $pending = $this->pendingChanges($tree)->get($xref); 99 100 if ($gedcom === null && $pending === null) { 101 return null; 102 } 103 104 $xref = $this->extractXref($gedcom ?? $pending, $xref); 105 $type = $this->extractType($gedcom ?? $pending); 106 107 return $this->newGedcomRecord($type, $xref, $gedcom ?? '', $pending, $tree); 108 }); 109 } 110 111 /** 112 * Create a GedcomRecord object from raw GEDCOM data. 113 * 114 * @param string $xref 115 * @param string $gedcom an empty string for new/pending records 116 * @param string|null $pending null for a record with no pending edits, 117 * empty string for records with pending deletions 118 * @param Tree $tree 119 * 120 * @return GedcomRecord 121 */ 122 public function new(string $xref, string $gedcom, ?string $pending, Tree $tree): GedcomRecord 123 { 124 return new GedcomRecord($xref, $gedcom, $pending, $tree); 125 } 126 127 /** 128 * Create a GedcomRecord object from a row in the database. 129 * 130 * @param Tree $tree 131 * 132 * @return Closure(object):GedcomRecord 133 */ 134 public function mapper(Tree $tree): Closure 135 { 136 return fn (object $row): GedcomRecord => $this->make($row->o_id, $tree, $row->o_gedcom); 137 } 138 139 /** 140 * @param string $type 141 * @param string $xref 142 * @param string $gedcom 143 * @param string|null $pending 144 * @param Tree $tree 145 * 146 * @return GedcomRecord 147 */ 148 private function newGedcomRecord(string $type, string $xref, string $gedcom, ?string $pending, Tree $tree): GedcomRecord 149 { 150 switch ($type) { 151 case Family::RECORD_TYPE: 152 return Registry::familyFactory()->new($xref, $gedcom, $pending, $tree); 153 154 case Header::RECORD_TYPE: 155 return Registry::headerFactory()->new($xref, $gedcom, $pending, $tree); 156 157 case Individual::RECORD_TYPE: 158 return Registry::individualFactory()->new($xref, $gedcom, $pending, $tree); 159 160 case Media::RECORD_TYPE: 161 return Registry::mediaFactory()->new($xref, $gedcom, $pending, $tree); 162 163 case Note::RECORD_TYPE: 164 return Registry::noteFactory()->new($xref, $gedcom, $pending, $tree); 165 166 case Repository::RECORD_TYPE: 167 return Registry::repositoryFactory()->new($xref, $gedcom, $pending, $tree); 168 169 case Source::RECORD_TYPE: 170 return Registry::sourceFactory()->new($xref, $gedcom, $pending, $tree); 171 172 case Submission::RECORD_TYPE: 173 return Registry::submissionFactory()->new($xref, $gedcom, $pending, $tree); 174 175 case Submitter::RECORD_TYPE: 176 return Registry::submitterFactory()->new($xref, $gedcom, $pending, $tree); 177 178 default: 179 return $this->new($xref, $gedcom, $pending, $tree); 180 } 181 } 182 183 /** 184 * Extract the type of a GEDCOM record 185 * 186 * @param string $gedcom 187 * 188 * @return string 189 * @throws InvalidArgumentException 190 */ 191 private function extractType(string $gedcom): string 192 { 193 if (preg_match('/^0(?: @' . Gedcom::REGEX_XREF . '@)? ([_A-Z0-9]+)/', $gedcom, $match)) { 194 return $match[1]; 195 } 196 197 throw new InvalidArgumentException('Invalid GEDCOM record: ' . $gedcom); 198 } 199 200 /** 201 * Fetch GEDCOM data from the database. 202 * 203 * @param string $xref 204 * @param Tree $tree 205 * 206 * @return string|null 207 */ 208 private function gedcom(string $xref, Tree $tree): ?string 209 { 210 return DB::table('other') 211 ->where('o_id', '=', $xref) 212 ->where('o_file', '=', $tree->id()) 213 ->whereNotIn('o_type', [ 214 Header::RECORD_TYPE, 215 Note::RECORD_TYPE, 216 Repository::RECORD_TYPE, 217 Submission::RECORD_TYPE, 218 Submitter::RECORD_TYPE, 219 ]) 220 ->value('o_gedcom'); 221 } 222} 223