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