1<?php 2 3/** 4 * webtrees: online genealogy 5 * Copyright (C) 2020 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 <http://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\Factory; 25use Fisharebest\Webtrees\Family; 26use Fisharebest\Webtrees\Gedcom; 27use Fisharebest\Webtrees\GedcomRecord; 28use Fisharebest\Webtrees\Header; 29use Fisharebest\Webtrees\Individual; 30use Fisharebest\Webtrees\Media; 31use Fisharebest\Webtrees\Note; 32use Fisharebest\Webtrees\Repository; 33use Fisharebest\Webtrees\Source; 34use Fisharebest\Webtrees\Submission; 35use Fisharebest\Webtrees\Submitter; 36use Fisharebest\Webtrees\Tree; 37use Illuminate\Database\Capsule\Manager as DB; 38use InvalidArgumentException; 39use stdClass; 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 do not know the type of the record. Try them all in turn. 60 return 61 Factory::family()->make($xref, $tree, $gedcom) ?? 62 Factory::individual()->make($xref, $tree, $gedcom) ?? 63 Factory::media()->make($xref, $tree, $gedcom) ?? 64 Factory::note()->make($xref, $tree, $gedcom) ?? 65 Factory::repository()->make($xref, $tree, $gedcom) ?? 66 Factory::source()->make($xref, $tree, $gedcom) ?? 67 Factory::submitter()->make($xref, $tree, $gedcom) ?? 68 Factory::submission()->make($xref, $tree, $gedcom) ?? 69 Factory::location()->make($xref, $tree, $gedcom) ?? 70 Factory::header()->make($xref, $tree, $gedcom) ?? 71 $this->cache->remember(__CLASS__ . $xref . '@' . $tree->id(), function () use ($xref, $tree, $gedcom) { 72 $gedcom = $gedcom ?? $this->gedcom($xref, $tree); 73 74 $pending = $this->pendingChanges($tree)->get($xref); 75 76 if ($gedcom === null && $pending === null) { 77 return null; 78 } 79 80 $xref = $this->extractXref($gedcom ?? $pending, $xref); 81 $type = $this->extractType($gedcom ?? $pending); 82 83 return $this->newGedcomRecord($type, $xref, $gedcom ?? '', $pending, $tree); 84 }); 85 } 86 87 /** 88 * Create a GedcomRecord object from raw GEDCOM data. 89 * 90 * @param string $xref 91 * @param string $gedcom an empty string for new/pending records 92 * @param string|null $pending null for a record with no pending edits, 93 * empty string for records with pending deletions 94 * @param Tree $tree 95 * 96 * @return GedcomRecord 97 */ 98 public function new(string $xref, string $gedcom, ?string $pending, Tree $tree): GedcomRecord 99 { 100 return new GedcomRecord($xref, $gedcom, $pending, $tree); 101 } 102 103 /** 104 * Create a GedcomRecord object from a row in the database. 105 * 106 * @param Tree $tree 107 * 108 * @return Closure 109 */ 110 public function mapper(Tree $tree): Closure 111 { 112 return function (stdClass $row) use ($tree): GedcomRecord { 113 $record = $this->make($row->o_id, $tree, $row->o_gedcom); 114 assert($record instanceof GedcomRecord); 115 116 return $record; 117 }; 118 } 119 120 /** 121 * @param string $type 122 * @param string $xref 123 * @param string $gedcom 124 * @param string|null $pending 125 * @param Tree $tree 126 * 127 * @return GedcomRecord 128 */ 129 private function newGedcomRecord(string $type, string $xref, string $gedcom, ?string $pending, Tree $tree): GedcomRecord 130 { 131 switch ($type) { 132 case Family::RECORD_TYPE: 133 return Factory::family()->new($xref, $gedcom, $pending, $tree); 134 135 case Header::RECORD_TYPE: 136 return Factory::header()->new($xref, $gedcom, $pending, $tree); 137 138 case Individual::RECORD_TYPE: 139 return Factory::individual()->new($xref, $gedcom, $pending, $tree); 140 141 case Media::RECORD_TYPE: 142 return Factory::media()->new($xref, $gedcom, $pending, $tree); 143 144 case Note::RECORD_TYPE: 145 return Factory::note()->new($xref, $gedcom, $pending, $tree); 146 147 case Repository::RECORD_TYPE: 148 return Factory::repository()->new($xref, $gedcom, $pending, $tree); 149 150 case Source::RECORD_TYPE: 151 return Factory::source()->new($xref, $gedcom, $pending, $tree); 152 153 case Submission::RECORD_TYPE: 154 return Factory::submission()->new($xref, $gedcom, $pending, $tree); 155 156 case Submitter::RECORD_TYPE: 157 return Factory::submitter()->new($xref, $gedcom, $pending, $tree); 158 159 default: 160 return $this->new($xref, $gedcom, $pending, $tree); 161 } 162 } 163 164 /** 165 * Extract the type of a GEDCOM record 166 * 167 * @param string $gedcom 168 * 169 * @return string 170 * @throws InvalidArgumentException 171 */ 172 private function extractType(string $gedcom): string 173 { 174 if (preg_match('/^0(?: @' . Gedcom::REGEX_XREF . '@)? ([_A-Z0-9]+)/', $gedcom, $match)) { 175 return $match[1]; 176 } 177 178 throw new InvalidArgumentException('Invalid GEDCOM record: ' . $gedcom); 179 } 180 181 /** 182 * Fetch GEDCOM data from the database. 183 * 184 * @param string $xref 185 * @param Tree $tree 186 * 187 * @return string|null 188 */ 189 private function gedcom(string $xref, Tree $tree): ?string 190 { 191 return DB::table('other') 192 ->where('o_id', '=', $xref) 193 ->where('o_file', '=', $tree->id()) 194 ->whereNotIn('o_type', [ 195 Header::RECORD_TYPE, 196 Note::RECORD_TYPE, 197 Repository::RECORD_TYPE, 198 Submission::RECORD_TYPE, 199 Submitter::RECORD_TYPE, 200 ]) 201 ->value('o_gedcom'); 202 } 203} 204