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