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