|
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
|
|
|
|
|
18
|
|
|
declare(strict_types=1); |
|
19
|
|
|
|
|
20
|
|
|
namespace Fisharebest\Webtrees\Functions; |
|
21
|
|
|
|
|
22
|
|
|
use Fisharebest\Webtrees\Date; |
|
23
|
|
|
use Fisharebest\Webtrees\Exceptions\GedcomErrorException; |
|
24
|
|
|
use Fisharebest\Webtrees\Family; |
|
25
|
|
|
use Fisharebest\Webtrees\Gedcom; |
|
26
|
|
|
use Fisharebest\Webtrees\GedcomTag; |
|
27
|
|
|
use Fisharebest\Webtrees\Header; |
|
28
|
|
|
use Fisharebest\Webtrees\Individual; |
|
29
|
|
|
use Fisharebest\Webtrees\Location; |
|
30
|
|
|
use Fisharebest\Webtrees\Media; |
|
31
|
|
|
use Fisharebest\Webtrees\Note; |
|
32
|
|
|
use Fisharebest\Webtrees\Place; |
|
33
|
|
|
use Fisharebest\Webtrees\PlaceLocation; |
|
34
|
|
|
use Fisharebest\Webtrees\Registry; |
|
35
|
|
|
use Fisharebest\Webtrees\Repository; |
|
36
|
|
|
use Fisharebest\Webtrees\Services\GedcomService; |
|
37
|
|
|
use Fisharebest\Webtrees\Soundex; |
|
38
|
|
|
use Fisharebest\Webtrees\Source; |
|
39
|
|
|
use Fisharebest\Webtrees\Submission; |
|
40
|
|
|
use Fisharebest\Webtrees\Submitter; |
|
41
|
|
|
use Fisharebest\Webtrees\Tree; |
|
42
|
|
|
use Illuminate\Database\Capsule\Manager as DB; |
|
43
|
|
|
use Illuminate\Database\Query\JoinClause; |
|
44
|
|
|
|
|
45
|
|
|
use function app; |
|
46
|
|
|
use function array_chunk; |
|
47
|
|
|
use function array_intersect_key; |
|
48
|
|
|
use function array_map; |
|
49
|
|
|
use function array_unique; |
|
50
|
|
|
use function assert; |
|
51
|
|
|
use function date; |
|
52
|
|
|
use function explode; |
|
53
|
|
|
use function max; |
|
54
|
|
|
use function preg_match; |
|
55
|
|
|
use function preg_match_all; |
|
56
|
|
|
use function preg_replace; |
|
57
|
|
|
use function round; |
|
58
|
|
|
use function str_contains; |
|
59
|
|
|
use function str_replace; |
|
60
|
|
|
use function str_starts_with; |
|
61
|
|
|
use function strlen; |
|
62
|
|
|
use function strtolower; |
|
63
|
|
|
use function strtoupper; |
|
64
|
|
|
use function substr; |
|
65
|
|
|
use function trim; |
|
66
|
|
|
|
|
67
|
|
|
use const PREG_SET_ORDER; |
|
68
|
|
|
|
|
69
|
|
|
/** |
|
70
|
|
|
* Class FunctionsImport - common functions |
|
71
|
|
|
*/ |
|
72
|
|
|
class FunctionsImport |
|
73
|
|
|
{ |
|
74
|
|
|
/** |
|
75
|
|
|
* Tidy up a gedcom record on import, so that we can access it consistently/efficiently. |
|
76
|
|
|
* |
|
77
|
|
|
* @param string $rec |
|
78
|
|
|
* @param Tree $tree |
|
79
|
|
|
* |
|
80
|
|
|
* @return string |
|
81
|
|
|
*/ |
|
82
|
|
|
public static function reformatRecord(string $rec, Tree $tree): string |
|
83
|
|
|
{ |
|
84
|
|
|
$gedcom_service = app(GedcomService::class); |
|
85
|
|
|
assert($gedcom_service instanceof GedcomService); |
|
86
|
|
|
|
|
87
|
|
|
// Strip out mac/msdos line endings |
|
88
|
|
|
$rec = preg_replace("/[\r\n]+/", "\n", $rec); |
|
89
|
|
|
|
|
90
|
|
|
// Extract lines from the record; lines consist of: level + optional xref + tag + optional data |
|
91
|
|
|
$num_matches = preg_match_all('/^[ \t]*(\d+)[ \t]*(@[^@]*@)?[ \t]*(\w+)[ \t]?(.*)$/m', $rec, $matches, PREG_SET_ORDER); |
|
92
|
|
|
|
|
93
|
|
|
// Process the record line-by-line |
|
94
|
|
|
$newrec = ''; |
|
95
|
|
|
foreach ($matches as $n => $match) { |
|
96
|
|
|
[, $level, $xref, $tag, $data] = $match; |
|
97
|
|
|
|
|
98
|
|
|
$tag = $gedcom_service->canonicalTag($tag); |
|
99
|
|
|
|
|
100
|
|
|
switch ($tag) { |
|
101
|
|
|
case 'AFN': |
|
102
|
|
|
// AFN values are upper case |
|
103
|
|
|
$data = strtoupper($data); |
|
104
|
|
|
break; |
|
105
|
|
|
case 'DATE': |
|
106
|
|
|
// Preserve text from INT dates |
|
107
|
|
|
if (str_contains($data, '(')) { |
|
|
|
|
|
|
108
|
|
|
[$date, $text] = explode('(', $data, 2); |
|
109
|
|
|
$text = ' (' . $text; |
|
110
|
|
|
} else { |
|
111
|
|
|
$date = $data; |
|
112
|
|
|
$text = ''; |
|
113
|
|
|
} |
|
114
|
|
|
// Capitals |
|
115
|
|
|
$date = strtoupper($date); |
|
116
|
|
|
// Temporarily add leading/trailing spaces, to allow efficient matching below |
|
117
|
|
|
$date = " {$date} "; |
|
118
|
|
|
// Ensure space digits and letters |
|
119
|
|
|
$date = preg_replace('/([A-Z])(\d)/', '$1 $2', $date); |
|
120
|
|
|
$date = preg_replace('/(\d)([A-Z])/', '$1 $2', $date); |
|
121
|
|
|
// Ensure space before/after calendar escapes |
|
122
|
|
|
$date = preg_replace('/@#[^@]+@/', ' $0 ', $date); |
|
123
|
|
|
// "BET." => "BET" |
|
124
|
|
|
$date = preg_replace('/(\w\w)\./', '$1', $date); |
|
125
|
|
|
// "CIR" => "ABT" |
|
126
|
|
|
$date = str_replace(' CIR ', ' ABT ', $date); |
|
127
|
|
|
$date = str_replace(' APX ', ' ABT ', $date); |
|
128
|
|
|
// B.C. => BC (temporarily, to allow easier handling of ".") |
|
129
|
|
|
$date = str_replace(' B.C. ', ' BC ', $date); |
|
130
|
|
|
// TMG uses "EITHER X OR Y" |
|
131
|
|
|
$date = preg_replace('/^ EITHER (.+) OR (.+)/', ' BET $1 AND $2', $date); |
|
132
|
|
|
// "BET X - Y " => "BET X AND Y" |
|
133
|
|
|
$date = preg_replace('/^(.* BET .+) - (.+)/', '$1 AND $2', $date); |
|
134
|
|
|
$date = preg_replace('/^(.* FROM .+) - (.+)/', '$1 TO $2', $date); |
|
135
|
|
|
// "@#ESC@ FROM X TO Y" => "FROM @#ESC@ X TO @#ESC@ Y" |
|
136
|
|
|
$date = preg_replace('/^ +(@#[^@]+@) +FROM +(.+) +TO +(.+)/', ' FROM $1 $2 TO $1 $3', $date); |
|
137
|
|
|
$date = preg_replace('/^ +(@#[^@]+@) +BET +(.+) +AND +(.+)/', ' BET $1 $2 AND $1 $3', $date); |
|
138
|
|
|
// "@#ESC@ AFT X" => "AFT @#ESC@ X" |
|
139
|
|
|
$date = preg_replace('/^ +(@#[^@]+@) +(FROM|BET|TO|AND|BEF|AFT|CAL|EST|INT|ABT) +(.+)/', ' $2 $1 $3', $date); |
|
140
|
|
|
// Ignore any remaining punctuation, e.g. "14-MAY, 1900" => "14 MAY 1900" |
|
141
|
|
|
// (don't change "/" - it is used in NS/OS dates) |
|
142
|
|
|
$date = preg_replace('/[.,:;-]/', ' ', $date); |
|
143
|
|
|
// BC => B.C. |
|
144
|
|
|
$date = str_replace(' BC ', ' B.C. ', $date); |
|
145
|
|
|
// Append the "INT" text |
|
146
|
|
|
$data = $date . $text; |
|
147
|
|
|
break; |
|
148
|
|
|
case '_FILE': |
|
149
|
|
|
$tag = 'FILE'; |
|
150
|
|
|
break; |
|
151
|
|
|
case 'FORM': |
|
152
|
|
|
// Consistent commas |
|
153
|
|
|
$data = preg_replace('/ *, */', ', ', $data); |
|
154
|
|
|
break; |
|
155
|
|
|
case 'HEAD': |
|
156
|
|
|
// HEAD records don't have an XREF or DATA |
|
157
|
|
|
if ($level === '0') { |
|
158
|
|
|
$xref = ''; |
|
159
|
|
|
$data = ''; |
|
160
|
|
|
} |
|
161
|
|
|
break; |
|
162
|
|
|
case 'NAME': |
|
163
|
|
|
// Tidy up non-printing characters |
|
164
|
|
|
$data = preg_replace('/ +/', ' ', trim($data)); |
|
165
|
|
|
break; |
|
166
|
|
|
case 'PEDI': |
|
167
|
|
|
// PEDI values are lower case |
|
168
|
|
|
$data = strtolower($data); |
|
169
|
|
|
break; |
|
170
|
|
|
case 'PLAC': |
|
171
|
|
|
// Consistent commas |
|
172
|
|
|
$data = preg_replace('/ *[,,،] */u', ', ', $data); |
|
173
|
|
|
// The Master Genealogist stores LAT/LONG data in the PLAC field, e.g. Pennsylvania, USA, 395945N0751013W |
|
174
|
|
|
if (preg_match('/(.*), (\d\d)(\d\d)(\d\d)([NS])(\d\d\d)(\d\d)(\d\d)([EW])$/', $data, $match)) { |
|
175
|
|
|
$data = |
|
176
|
|
|
$match[1] . "\n" . |
|
177
|
|
|
($level + 1) . " MAP\n" . |
|
178
|
|
|
($level + 2) . ' LATI ' . ($match[5] . round($match[2] + ($match[3] / 60) + ($match[4] / 3600), 4)) . "\n" . |
|
179
|
|
|
($level + 2) . ' LONG ' . ($match[9] . round($match[6] + ($match[7] / 60) + ($match[8] / 3600), 4)); |
|
180
|
|
|
} |
|
181
|
|
|
break; |
|
182
|
|
|
case 'RESN': |
|
183
|
|
|
// RESN values are lower case (confidential, privacy, locked, none) |
|
184
|
|
|
$data = strtolower($data); |
|
185
|
|
|
if ($data === 'invisible') { |
|
186
|
|
|
$data = 'confidential'; // From old versions of Legacy. |
|
187
|
|
|
} |
|
188
|
|
|
break; |
|
189
|
|
|
case 'SEX': |
|
190
|
|
|
$data = strtoupper($data); |
|
191
|
|
|
break; |
|
192
|
|
|
case 'STAT': |
|
193
|
|
|
if ($data === 'CANCELLED') { |
|
194
|
|
|
// PhpGedView mis-spells this tag - correct it. |
|
195
|
|
|
$data = 'CANCELED'; |
|
196
|
|
|
} |
|
197
|
|
|
break; |
|
198
|
|
|
case 'TEMP': |
|
199
|
|
|
// Temple codes are upper case |
|
200
|
|
|
$data = strtoupper($data); |
|
201
|
|
|
break; |
|
202
|
|
|
case 'TRLR': |
|
203
|
|
|
// TRLR records don't have an XREF or DATA |
|
204
|
|
|
if ($level === '0') { |
|
205
|
|
|
$xref = ''; |
|
206
|
|
|
$data = ''; |
|
207
|
|
|
} |
|
208
|
|
|
break; |
|
209
|
|
|
} |
|
210
|
|
|
// Suppress "Y", for facts/events with a DATE or PLAC |
|
211
|
|
|
if ($data === 'y') { |
|
212
|
|
|
$data = 'Y'; |
|
213
|
|
|
} |
|
214
|
|
|
if ($level === '1' && $data === 'Y') { |
|
215
|
|
|
for ($i = $n + 1; $i < $num_matches - 1 && $matches[$i][1] !== '1'; ++$i) { |
|
216
|
|
|
if ($matches[$i][3] === 'DATE' || $matches[$i][3] === 'PLAC') { |
|
217
|
|
|
$data = ''; |
|
218
|
|
|
break; |
|
219
|
|
|
} |
|
220
|
|
|
} |
|
221
|
|
|
} |
|
222
|
|
|
// Reassemble components back into a single line |
|
223
|
|
|
switch ($tag) { |
|
224
|
|
|
default: |
|
225
|
|
|
// Remove tabs and multiple/leading/trailing spaces |
|
226
|
|
|
$data = strtr($data, ["\t" => ' ']); |
|
227
|
|
|
$data = trim($data, ' '); |
|
228
|
|
|
while (str_contains($data, ' ')) { |
|
|
|
|
|
|
229
|
|
|
$data = strtr($data, [' ' => ' ']); |
|
230
|
|
|
} |
|
231
|
|
|
$newrec .= ($newrec ? "\n" : '') . $level . ' ' . ($level === '0' && $xref ? $xref . ' ' : '') . $tag . ($data === '' && $tag !== 'NOTE' ? '' : ' ' . $data); |
|
232
|
|
|
break; |
|
233
|
|
|
case 'NOTE': |
|
234
|
|
|
case 'TEXT': |
|
235
|
|
|
case 'DATA': |
|
236
|
|
|
case 'CONT': |
|
237
|
|
|
$newrec .= ($newrec ? "\n" : '') . $level . ' ' . ($level === '0' && $xref ? $xref . ' ' : '') . $tag . ($data === '' && $tag !== 'NOTE' ? '' : ' ' . $data); |
|
238
|
|
|
break; |
|
239
|
|
|
case 'FILE': |
|
240
|
|
|
// Strip off the user-defined path prefix |
|
241
|
|
|
$GEDCOM_MEDIA_PATH = $tree->getPreference('GEDCOM_MEDIA_PATH'); |
|
242
|
|
|
if ($GEDCOM_MEDIA_PATH !== '' && str_starts_with($data, $GEDCOM_MEDIA_PATH)) { |
|
243
|
|
|
$data = substr($data, strlen($GEDCOM_MEDIA_PATH)); |
|
244
|
|
|
} |
|
245
|
|
|
// convert backslashes in filenames to forward slashes |
|
246
|
|
|
$data = preg_replace("/\\\\/", '/', $data); |
|
247
|
|
|
|
|
248
|
|
|
$newrec .= ($newrec ? "\n" : '') . $level . ' ' . ($level === '0' && $xref ? $xref . ' ' : '') . $tag . ($data === '' && $tag !== 'NOTE' ? '' : ' ' . $data); |
|
249
|
|
|
break; |
|
250
|
|
|
case 'CONC': |
|
251
|
|
|
// Merge CONC lines, to simplify access later on. |
|
252
|
|
|
$newrec .= ($tree->getPreference('WORD_WRAPPED_NOTES') ? ' ' : '') . $data; |
|
253
|
|
|
break; |
|
254
|
|
|
} |
|
255
|
|
|
} |
|
256
|
|
|
|
|
257
|
|
|
return $newrec; |
|
258
|
|
|
} |
|
259
|
|
|
|
|
260
|
|
|
/** |
|
261
|
|
|
* import record into database |
|
262
|
|
|
* this function will parse the given gedcom record and add it to the database |
|
263
|
|
|
* |
|
264
|
|
|
* @param string $gedrec the raw gedcom record to parse |
|
265
|
|
|
* @param Tree $tree import the record into this tree |
|
266
|
|
|
* @param bool $update whether or not this is an updated record that has been accepted |
|
267
|
|
|
* |
|
268
|
|
|
* @return void |
|
269
|
|
|
* @throws GedcomErrorException |
|
270
|
|
|
*/ |
|
271
|
|
|
public static function importRecord(string $gedrec, Tree $tree, bool $update): void |
|
272
|
|
|
{ |
|
273
|
|
|
$tree_id = $tree->id(); |
|
274
|
|
|
|
|
275
|
|
|
// Escaped @ signs (only if importing from file) |
|
276
|
|
|
if (!$update) { |
|
277
|
|
|
$gedrec = str_replace('@@', '@', $gedrec); |
|
278
|
|
|
} |
|
279
|
|
|
|
|
280
|
|
|
// Standardise gedcom format |
|
281
|
|
|
$gedrec = self::reformatRecord($gedrec, $tree); |
|
282
|
|
|
|
|
283
|
|
|
// import different types of records |
|
284
|
|
|
if (preg_match('/^0 @(' . Gedcom::REGEX_XREF . ')@ (' . Gedcom::REGEX_TAG . ')/', $gedrec, $match)) { |
|
285
|
|
|
[, $xref, $type] = $match; |
|
286
|
|
|
// check for a _UID, if the record doesn't have one, add one |
|
287
|
|
|
if ($tree->getPreference('GENERATE_UIDS') === '1' && !str_contains($gedrec, "\n1 _UID ")) { |
|
|
|
|
|
|
288
|
|
|
$gedrec .= "\n1 _UID " . GedcomTag::createUid(); |
|
289
|
|
|
} |
|
290
|
|
|
} elseif (preg_match('/0 (HEAD|TRLR|_PLAC |_PLAC_DEFN)/', $gedrec, $match)) { |
|
291
|
|
|
$type = $match[1]; |
|
292
|
|
|
$xref = $type; // For records without an XREF, use the type as a pseudo XREF. |
|
293
|
|
|
} else { |
|
294
|
|
|
throw new GedcomErrorException($gedrec); |
|
295
|
|
|
} |
|
296
|
|
|
|
|
297
|
|
|
// If the user has downloaded their GEDCOM data (containing media objects) and edited it |
|
298
|
|
|
// using an application which does not support (and deletes) media objects, then add them |
|
299
|
|
|
// back in. |
|
300
|
|
|
if ($tree->getPreference('keep_media')) { |
|
301
|
|
|
$old_linked_media = DB::table('link') |
|
302
|
|
|
->where('l_from', '=', $xref) |
|
303
|
|
|
->where('l_file', '=', $tree_id) |
|
304
|
|
|
->where('l_type', '=', 'OBJE') |
|
305
|
|
|
->pluck('l_to'); |
|
306
|
|
|
|
|
307
|
|
|
// Delete these links - so that we do not insert them again in updateLinks() |
|
308
|
|
|
DB::table('link') |
|
309
|
|
|
->where('l_from', '=', $xref) |
|
310
|
|
|
->where('l_file', '=', $tree_id) |
|
311
|
|
|
->where('l_type', '=', 'OBJE') |
|
312
|
|
|
->delete(); |
|
313
|
|
|
|
|
314
|
|
|
foreach ($old_linked_media as $media_id) { |
|
315
|
|
|
$gedrec .= "\n1 OBJE @" . $media_id . '@'; |
|
316
|
|
|
} |
|
317
|
|
|
} |
|
318
|
|
|
|
|
319
|
|
|
// Convert inline media into media objects |
|
320
|
|
|
$gedrec = self::convertInlineMedia($tree, $gedrec); |
|
321
|
|
|
|
|
322
|
|
|
switch ($type) { |
|
323
|
|
|
case Individual::RECORD_TYPE: |
|
324
|
|
|
$record = Registry::individualFactory()->new($xref, $gedrec, null, $tree); |
|
325
|
|
|
|
|
326
|
|
|
if (preg_match('/\n1 RIN (.+)/', $gedrec, $match)) { |
|
327
|
|
|
$rin = $match[1]; |
|
328
|
|
|
} else { |
|
329
|
|
|
$rin = $xref; |
|
330
|
|
|
} |
|
331
|
|
|
|
|
332
|
|
|
DB::table('individuals')->insert([ |
|
333
|
|
|
'i_id' => $xref, |
|
334
|
|
|
'i_file' => $tree_id, |
|
335
|
|
|
'i_rin' => $rin, |
|
336
|
|
|
'i_sex' => $record->sex(), |
|
337
|
|
|
'i_gedcom' => $gedrec, |
|
338
|
|
|
]); |
|
339
|
|
|
|
|
340
|
|
|
// Update the cross-reference/index tables. |
|
341
|
|
|
self::updatePlaces($xref, $tree, $gedrec); |
|
342
|
|
|
self::updateDates($xref, $tree_id, $gedrec); |
|
343
|
|
|
self::updateNames($xref, $tree_id, $record); |
|
344
|
|
|
break; |
|
345
|
|
|
|
|
346
|
|
|
case Family::RECORD_TYPE: |
|
347
|
|
|
if (preg_match('/\n1 HUSB @(' . Gedcom::REGEX_XREF . ')@/', $gedrec, $match)) { |
|
348
|
|
|
$husb = $match[1]; |
|
349
|
|
|
} else { |
|
350
|
|
|
$husb = ''; |
|
351
|
|
|
} |
|
352
|
|
|
if (preg_match('/\n1 WIFE @(' . Gedcom::REGEX_XREF . ')@/', $gedrec, $match)) { |
|
353
|
|
|
$wife = $match[1]; |
|
354
|
|
|
} else { |
|
355
|
|
|
$wife = ''; |
|
356
|
|
|
} |
|
357
|
|
|
$nchi = preg_match_all('/\n1 CHIL @(' . Gedcom::REGEX_XREF . ')@/', $gedrec, $match); |
|
358
|
|
|
if (preg_match('/\n1 NCHI (\d+)/', $gedrec, $match)) { |
|
359
|
|
|
$nchi = max($nchi, $match[1]); |
|
360
|
|
|
} |
|
361
|
|
|
|
|
362
|
|
|
DB::table('families')->insert([ |
|
363
|
|
|
'f_id' => $xref, |
|
364
|
|
|
'f_file' => $tree_id, |
|
365
|
|
|
'f_husb' => $husb, |
|
366
|
|
|
'f_wife' => $wife, |
|
367
|
|
|
'f_gedcom' => $gedrec, |
|
368
|
|
|
'f_numchil' => $nchi, |
|
369
|
|
|
]); |
|
370
|
|
|
|
|
371
|
|
|
// Update the cross-reference/index tables. |
|
372
|
|
|
self::updatePlaces($xref, $tree, $gedrec); |
|
373
|
|
|
self::updateDates($xref, $tree_id, $gedrec); |
|
374
|
|
|
break; |
|
375
|
|
|
|
|
376
|
|
|
case Source::RECORD_TYPE: |
|
377
|
|
|
if (preg_match('/\n1 TITL (.+)/', $gedrec, $match)) { |
|
378
|
|
|
$name = $match[1]; |
|
379
|
|
|
} elseif (preg_match('/\n1 ABBR (.+)/', $gedrec, $match)) { |
|
380
|
|
|
$name = $match[1]; |
|
381
|
|
|
} else { |
|
382
|
|
|
$name = $xref; |
|
383
|
|
|
} |
|
384
|
|
|
|
|
385
|
|
|
DB::table('sources')->insert([ |
|
386
|
|
|
's_id' => $xref, |
|
387
|
|
|
's_file' => $tree_id, |
|
388
|
|
|
's_name' => mb_substr($name, 0, 255), |
|
389
|
|
|
's_gedcom' => $gedrec, |
|
390
|
|
|
]); |
|
391
|
|
|
break; |
|
392
|
|
|
|
|
393
|
|
|
case Repository::RECORD_TYPE: |
|
394
|
|
|
case Note::RECORD_TYPE: |
|
395
|
|
|
case Submission::RECORD_TYPE: |
|
396
|
|
|
case Submitter::RECORD_TYPE: |
|
397
|
|
|
case Location::RECORD_TYPE: |
|
398
|
|
|
DB::table('other')->insert([ |
|
399
|
|
|
'o_id' => $xref, |
|
400
|
|
|
'o_file' => $tree_id, |
|
401
|
|
|
'o_type' => $type, |
|
402
|
|
|
'o_gedcom' => $gedrec, |
|
403
|
|
|
]); |
|
404
|
|
|
break; |
|
405
|
|
|
|
|
406
|
|
|
case Header::RECORD_TYPE: |
|
407
|
|
|
// Force HEAD records to have a creation date. |
|
408
|
|
|
if (!str_contains($gedrec, "\n1 DATE ")) { |
|
|
|
|
|
|
409
|
|
|
$today = strtoupper(date('d M Y')); |
|
410
|
|
|
$gedrec .= "\n1 DATE " . $today; |
|
411
|
|
|
} |
|
412
|
|
|
|
|
413
|
|
|
DB::table('other')->insert([ |
|
414
|
|
|
'o_id' => $xref, |
|
415
|
|
|
'o_file' => $tree_id, |
|
416
|
|
|
'o_type' => Header::RECORD_TYPE, |
|
417
|
|
|
'o_gedcom' => $gedrec, |
|
418
|
|
|
]); |
|
419
|
|
|
break; |
|
420
|
|
|
|
|
421
|
|
|
|
|
422
|
|
|
case Media::RECORD_TYPE: |
|
423
|
|
|
$record = Registry::mediaFactory()->new($xref, $gedrec, null, $tree); |
|
424
|
|
|
|
|
425
|
|
|
DB::table('media')->insert([ |
|
426
|
|
|
'm_id' => $xref, |
|
427
|
|
|
'm_file' => $tree_id, |
|
428
|
|
|
'm_gedcom' => $gedrec, |
|
429
|
|
|
]); |
|
430
|
|
|
|
|
431
|
|
|
foreach ($record->mediaFiles() as $media_file) { |
|
432
|
|
|
DB::table('media_file')->insert([ |
|
433
|
|
|
'm_id' => $xref, |
|
434
|
|
|
'm_file' => $tree_id, |
|
435
|
|
|
'multimedia_file_refn' => mb_substr($media_file->filename(), 0, 248), |
|
436
|
|
|
'multimedia_format' => mb_substr($media_file->format(), 0, 4), |
|
437
|
|
|
'source_media_type' => mb_substr($media_file->type(), 0, 15), |
|
438
|
|
|
'descriptive_title' => mb_substr($media_file->title(), 0, 248), |
|
439
|
|
|
]); |
|
440
|
|
|
} |
|
441
|
|
|
break; |
|
442
|
|
|
|
|
443
|
|
|
case '_PLAC ': |
|
444
|
|
|
self::importTNGPlac($gedrec); |
|
445
|
|
|
return; |
|
446
|
|
|
|
|
447
|
|
|
case '_PLAC_DEFN': |
|
448
|
|
|
self::importLegacyPlacDefn($gedrec); |
|
449
|
|
|
return; |
|
450
|
|
|
|
|
451
|
|
|
default: // Custom record types. |
|
452
|
|
|
DB::table('other')->insert([ |
|
453
|
|
|
'o_id' => $xref, |
|
454
|
|
|
'o_file' => $tree_id, |
|
455
|
|
|
'o_type' => mb_substr($type, 0, 15), |
|
456
|
|
|
'o_gedcom' => $gedrec, |
|
457
|
|
|
]); |
|
458
|
|
|
break; |
|
459
|
|
|
} |
|
460
|
|
|
|
|
461
|
|
|
// Update the cross-reference/index tables. |
|
462
|
|
|
self::updateLinks($xref, $tree_id, $gedrec); |
|
463
|
|
|
} |
|
464
|
|
|
|
|
465
|
|
|
/** |
|
466
|
|
|
* Legacy Family Tree software generates _PLAC_DEFN records containing LAT/LONG values |
|
467
|
|
|
* |
|
468
|
|
|
* @param string $gedcom |
|
469
|
|
|
*/ |
|
470
|
|
|
private static function importLegacyPlacDefn(string $gedcom): void |
|
471
|
|
|
{ |
|
472
|
|
|
$gedcom_service = new GedcomService(); |
|
473
|
|
|
|
|
474
|
|
|
if (preg_match('/\n1 PLAC (.+)/', $gedcom, $match)) { |
|
475
|
|
|
$place_name = $match[1]; |
|
476
|
|
|
} else { |
|
477
|
|
|
return; |
|
478
|
|
|
} |
|
479
|
|
|
|
|
480
|
|
|
if (preg_match('/\n3 LATI ([NS].+)/', $gedcom, $match)) { |
|
481
|
|
|
$latitude = $gedcom_service->readLatitude($match[1]); |
|
482
|
|
|
} else { |
|
483
|
|
|
return; |
|
484
|
|
|
} |
|
485
|
|
|
|
|
486
|
|
|
if (preg_match('/\n3 LONG ([EW].+)/', $gedcom, $match)) { |
|
487
|
|
|
$longitude = $gedcom_service->readLongitude($match[1]); |
|
488
|
|
|
} else { |
|
489
|
|
|
return; |
|
490
|
|
|
} |
|
491
|
|
|
|
|
492
|
|
|
$location = new PlaceLocation($place_name); |
|
493
|
|
|
|
|
494
|
|
|
if ($location->latitude() === 0.0 && $location->longitude() === 0.0) { |
|
|
|
|
|
|
495
|
|
|
DB::table('placelocation') |
|
496
|
|
|
->where('pl_id', '=', $location->id()) |
|
497
|
|
|
->update([ |
|
498
|
|
|
'pl_lati' => $latitude, |
|
499
|
|
|
'pl_long' => $longitude, |
|
500
|
|
|
]); |
|
501
|
|
|
} |
|
502
|
|
|
} |
|
503
|
|
|
|
|
504
|
|
|
/** |
|
505
|
|
|
* Legacy Family Tree software generates _PLAC_DEFN records containing LAT/LONG values |
|
506
|
|
|
* |
|
507
|
|
|
* @param string $gedcom |
|
508
|
|
|
*/ |
|
509
|
|
|
private static function importTNGPlac(string $gedcom): void |
|
510
|
|
|
{ |
|
511
|
|
|
if (preg_match('/^0 _PLAC (.+)/', $gedcom, $match)) { |
|
512
|
|
|
$place_name = $match[1]; |
|
513
|
|
|
} else { |
|
514
|
|
|
return; |
|
515
|
|
|
} |
|
516
|
|
|
|
|
517
|
|
|
if (preg_match('/\n2 LATI (.+)/', $gedcom, $match)) { |
|
518
|
|
|
$latitude = (float) $match[1]; |
|
519
|
|
|
} else { |
|
520
|
|
|
return; |
|
521
|
|
|
} |
|
522
|
|
|
|
|
523
|
|
|
if (preg_match('/\n2 LONG (.+)/', $gedcom, $match)) { |
|
524
|
|
|
$longitude = (float) $match[1]; |
|
525
|
|
|
} else { |
|
526
|
|
|
return; |
|
527
|
|
|
} |
|
528
|
|
|
|
|
529
|
|
|
$location = new PlaceLocation($place_name); |
|
530
|
|
|
|
|
531
|
|
|
if ($location->latitude() === 0.0 && $location->longitude() === 0.0) { |
|
|
|
|
|
|
532
|
|
|
DB::table('placelocation') |
|
533
|
|
|
->where('pl_id', '=', $location->id()) |
|
534
|
|
|
->update([ |
|
535
|
|
|
'pl_lati' => $latitude, |
|
536
|
|
|
'pl_long' => $longitude, |
|
537
|
|
|
]); |
|
538
|
|
|
} |
|
539
|
|
|
} |
|
540
|
|
|
|
|
541
|
|
|
/** |
|
542
|
|
|
* Extract all level 2 places from the given record and insert them into the places table |
|
543
|
|
|
* |
|
544
|
|
|
* @param string $xref |
|
545
|
|
|
* @param Tree $tree |
|
546
|
|
|
* @param string $gedrec |
|
547
|
|
|
* |
|
548
|
|
|
* @return void |
|
549
|
|
|
*/ |
|
550
|
|
|
public static function updatePlaces(string $xref, Tree $tree, string $gedrec): void |
|
551
|
|
|
{ |
|
552
|
|
|
// Insert all new rows together |
|
553
|
|
|
$rows = []; |
|
554
|
|
|
|
|
555
|
|
|
preg_match_all('/\n2 PLAC (.+)/', $gedrec, $matches); |
|
556
|
|
|
|
|
557
|
|
|
$places = array_unique($matches[1]); |
|
558
|
|
|
|
|
559
|
|
|
foreach ($places as $place_name) { |
|
560
|
|
|
$place = new Place($place_name, $tree); |
|
561
|
|
|
|
|
562
|
|
|
// Calling Place::id() will create the entry in the database, if it doesn't already exist. |
|
563
|
|
|
while ($place->id() !== 0) { |
|
564
|
|
|
$rows[] = [ |
|
565
|
|
|
'pl_p_id' => $place->id(), |
|
566
|
|
|
'pl_gid' => $xref, |
|
567
|
|
|
'pl_file' => $tree->id(), |
|
568
|
|
|
]; |
|
569
|
|
|
|
|
570
|
|
|
$place = $place->parent(); |
|
571
|
|
|
} |
|
572
|
|
|
} |
|
573
|
|
|
|
|
574
|
|
|
// array_unique doesn't work with arrays of arrays |
|
575
|
|
|
$rows = array_intersect_key($rows, array_unique(array_map('serialize', $rows))); |
|
576
|
|
|
|
|
577
|
|
|
// PDO has a limit of 65535 placeholders, and each row requires 3 placeholders. |
|
578
|
|
|
foreach (array_chunk($rows, 20000) as $chunk) { |
|
579
|
|
|
DB::table('placelinks')->insert($chunk); |
|
580
|
|
|
} |
|
581
|
|
|
} |
|
582
|
|
|
|
|
583
|
|
|
/** |
|
584
|
|
|
* Extract all the dates from the given record and insert them into the database. |
|
585
|
|
|
* |
|
586
|
|
|
* @param string $xref |
|
587
|
|
|
* @param int $ged_id |
|
588
|
|
|
* @param string $gedrec |
|
589
|
|
|
* |
|
590
|
|
|
* @return void |
|
591
|
|
|
*/ |
|
592
|
|
|
public static function updateDates(string $xref, int $ged_id, string $gedrec): void |
|
593
|
|
|
{ |
|
594
|
|
|
// Insert all new rows together |
|
595
|
|
|
$rows = []; |
|
596
|
|
|
|
|
597
|
|
|
preg_match_all("/\n1 (\w+).*(?:\n[2-9].*)*(?:\n2 DATE (.+))(?:\n[2-9].*)*/", $gedrec, $matches, PREG_SET_ORDER); |
|
598
|
|
|
|
|
599
|
|
|
foreach ($matches as $match) { |
|
600
|
|
|
$fact = $match[1]; |
|
601
|
|
|
$date = new Date($match[2]); |
|
602
|
|
|
$rows[] = [ |
|
603
|
|
|
'd_day' => $date->minimumDate()->day, |
|
604
|
|
|
'd_month' => $date->minimumDate()->format('%O'), |
|
605
|
|
|
'd_mon' => $date->minimumDate()->month, |
|
606
|
|
|
'd_year' => $date->minimumDate()->year, |
|
607
|
|
|
'd_julianday1' => $date->minimumDate()->minimumJulianDay(), |
|
608
|
|
|
'd_julianday2' => $date->minimumDate()->maximumJulianDay(), |
|
609
|
|
|
'd_fact' => $fact, |
|
610
|
|
|
'd_gid' => $xref, |
|
611
|
|
|
'd_file' => $ged_id, |
|
612
|
|
|
'd_type' => $date->minimumDate()->format('%@'), |
|
613
|
|
|
]; |
|
614
|
|
|
|
|
615
|
|
|
$rows[] = [ |
|
616
|
|
|
'd_day' => $date->maximumDate()->day, |
|
617
|
|
|
'd_month' => $date->maximumDate()->format('%O'), |
|
618
|
|
|
'd_mon' => $date->maximumDate()->month, |
|
619
|
|
|
'd_year' => $date->maximumDate()->year, |
|
620
|
|
|
'd_julianday1' => $date->maximumDate()->minimumJulianDay(), |
|
621
|
|
|
'd_julianday2' => $date->maximumDate()->maximumJulianDay(), |
|
622
|
|
|
'd_fact' => $fact, |
|
623
|
|
|
'd_gid' => $xref, |
|
624
|
|
|
'd_file' => $ged_id, |
|
625
|
|
|
'd_type' => $date->minimumDate()->format('%@'), |
|
626
|
|
|
]; |
|
627
|
|
|
} |
|
628
|
|
|
|
|
629
|
|
|
// array_unique doesn't work with arrays of arrays |
|
630
|
|
|
$rows = array_intersect_key($rows, array_unique(array_map('serialize', $rows))); |
|
631
|
|
|
|
|
632
|
|
|
DB::table('dates')->insert($rows); |
|
633
|
|
|
} |
|
634
|
|
|
|
|
635
|
|
|
/** |
|
636
|
|
|
* Extract all the links from the given record and insert them into the database |
|
637
|
|
|
* |
|
638
|
|
|
* @param string $xref |
|
639
|
|
|
* @param int $ged_id |
|
640
|
|
|
* @param string $gedrec |
|
641
|
|
|
* |
|
642
|
|
|
* @return void |
|
643
|
|
|
*/ |
|
644
|
|
|
public static function updateLinks(string $xref, int $ged_id, string $gedrec): void |
|
645
|
|
|
{ |
|
646
|
|
|
// Insert all new rows together |
|
647
|
|
|
$rows = []; |
|
648
|
|
|
|
|
649
|
|
|
preg_match_all('/\n\d+ (' . Gedcom::REGEX_TAG . ') @(' . Gedcom::REGEX_XREF . ')@/', $gedrec, $matches, PREG_SET_ORDER); |
|
650
|
|
|
|
|
651
|
|
|
foreach ($matches as $match) { |
|
652
|
|
|
// Take care of "duplicates" that differ on case/collation, e.g. "SOUR @S1@" and "SOUR @s1@" |
|
653
|
|
|
$rows[$match[1] . strtoupper($match[2])] = [ |
|
654
|
|
|
'l_from' => $xref, |
|
655
|
|
|
'l_to' => $match[2], |
|
656
|
|
|
'l_type' => $match[1], |
|
657
|
|
|
'l_file' => $ged_id, |
|
658
|
|
|
]; |
|
659
|
|
|
} |
|
660
|
|
|
|
|
661
|
|
|
DB::table('link')->insert($rows); |
|
662
|
|
|
} |
|
663
|
|
|
|
|
664
|
|
|
/** |
|
665
|
|
|
* Extract all the names from the given record and insert them into the database. |
|
666
|
|
|
* |
|
667
|
|
|
* @param string $xref |
|
668
|
|
|
* @param int $ged_id |
|
669
|
|
|
* @param Individual $record |
|
670
|
|
|
* |
|
671
|
|
|
* @return void |
|
672
|
|
|
*/ |
|
673
|
|
|
public static function updateNames(string $xref, int $ged_id, Individual $record): void |
|
674
|
|
|
{ |
|
675
|
|
|
// Insert all new rows together |
|
676
|
|
|
$rows = []; |
|
677
|
|
|
|
|
678
|
|
|
foreach ($record->getAllNames() as $n => $name) { |
|
679
|
|
|
if ($name['givn'] === '@P.N.') { |
|
680
|
|
|
$soundex_givn_std = null; |
|
681
|
|
|
$soundex_givn_dm = null; |
|
682
|
|
|
} else { |
|
683
|
|
|
$soundex_givn_std = Soundex::russell($name['givn']); |
|
684
|
|
|
$soundex_givn_dm = Soundex::daitchMokotoff($name['givn']); |
|
685
|
|
|
} |
|
686
|
|
|
|
|
687
|
|
|
if ($name['surn'] === '@N.N.') { |
|
688
|
|
|
$soundex_surn_std = null; |
|
689
|
|
|
$soundex_surn_dm = null; |
|
690
|
|
|
} else { |
|
691
|
|
|
$soundex_surn_std = Soundex::russell($name['surname']); |
|
692
|
|
|
$soundex_surn_dm = Soundex::daitchMokotoff($name['surname']); |
|
693
|
|
|
} |
|
694
|
|
|
|
|
695
|
|
|
$rows[] = [ |
|
696
|
|
|
'n_file' => $ged_id, |
|
697
|
|
|
'n_id' => $xref, |
|
698
|
|
|
'n_num' => $n, |
|
699
|
|
|
'n_type' => $name['type'], |
|
700
|
|
|
'n_sort' => mb_substr($name['sort'], 0, 255), |
|
701
|
|
|
'n_full' => mb_substr($name['fullNN'], 0, 255), |
|
702
|
|
|
'n_surname' => mb_substr($name['surname'], 0, 255), |
|
703
|
|
|
'n_surn' => mb_substr($name['surn'], 0, 255), |
|
704
|
|
|
'n_givn' => mb_substr($name['givn'], 0, 255), |
|
705
|
|
|
'n_soundex_givn_std' => $soundex_givn_std, |
|
706
|
|
|
'n_soundex_surn_std' => $soundex_surn_std, |
|
707
|
|
|
'n_soundex_givn_dm' => $soundex_givn_dm, |
|
708
|
|
|
'n_soundex_surn_dm' => $soundex_surn_dm, |
|
709
|
|
|
]; |
|
710
|
|
|
} |
|
711
|
|
|
|
|
712
|
|
|
DB::table('name')->insert($rows); |
|
713
|
|
|
} |
|
714
|
|
|
|
|
715
|
|
|
/** |
|
716
|
|
|
* Extract inline media data, and convert to media objects. |
|
717
|
|
|
* |
|
718
|
|
|
* @param Tree $tree |
|
719
|
|
|
* @param string $gedcom |
|
720
|
|
|
* |
|
721
|
|
|
* @return string |
|
722
|
|
|
*/ |
|
723
|
|
|
public static function convertInlineMedia(Tree $tree, string $gedcom): string |
|
724
|
|
|
{ |
|
725
|
|
|
while (preg_match('/\n1 OBJE(?:\n[2-9].+)+/', $gedcom, $match)) { |
|
726
|
|
|
$xref = self::createMediaObject($match[0], $tree); |
|
727
|
|
|
$gedcom = strtr($gedcom, [$match[0] => "\n1 OBJE @" . $xref . '@']); |
|
728
|
|
|
} |
|
729
|
|
|
while (preg_match('/\n2 OBJE(?:\n[3-9].+)+/', $gedcom, $match)) { |
|
730
|
|
|
$xref = self::createMediaObject($match[0], $tree); |
|
731
|
|
|
$gedcom = strtr($gedcom, [$match[0] => "\n2 OBJE @" . $xref . '@']); |
|
732
|
|
|
} |
|
733
|
|
|
while (preg_match('/\n3 OBJE(?:\n[4-9].+)+/', $gedcom, $match)) { |
|
734
|
|
|
$xref = self::createMediaObject($match[0], $tree); |
|
735
|
|
|
$gedcom = strtr($gedcom, [$match[0] => "\n3 OBJE @" . $xref . '@']); |
|
736
|
|
|
} |
|
737
|
|
|
|
|
738
|
|
|
return $gedcom; |
|
739
|
|
|
} |
|
740
|
|
|
|
|
741
|
|
|
/** |
|
742
|
|
|
* Create a new media object, from inline media data. |
|
743
|
|
|
* |
|
744
|
|
|
* GEDCOM 5.5.1 specifies: +1 FILE / +2 FORM / +3 MEDI / +1 TITL |
|
745
|
|
|
* GEDCOM 5.5 specifies: +1 FILE / +1 FORM / +1 TITL |
|
746
|
|
|
* GEDCOM 5.5.1 says that GEDCOM 5.5 specifies: +1 FILE / +1 FORM / +2 MEDI |
|
747
|
|
|
* |
|
748
|
|
|
* Legacy generates: +1 FORM / +1 FILE / +1 TITL / +1 _SCBK / +1 _PRIM / +1 _TYPE / +1 NOTE |
|
749
|
|
|
* RootsMagic generates: +1 FILE / +1 FORM / +1 TITL |
|
750
|
|
|
* |
|
751
|
|
|
* @param string $gedcom |
|
752
|
|
|
* @param Tree $tree |
|
753
|
|
|
* |
|
754
|
|
|
* @return string |
|
755
|
|
|
*/ |
|
756
|
|
|
public static function createMediaObject(string $gedcom, Tree $tree): string |
|
757
|
|
|
{ |
|
758
|
|
|
preg_match('/\n\d FILE (.+)/', $gedcom, $match); |
|
759
|
|
|
$file = $match[1] ?? ''; |
|
760
|
|
|
|
|
761
|
|
|
preg_match('/\n\d TITL (.+)/', $gedcom, $match); |
|
762
|
|
|
$title = $match[1] ?? ''; |
|
763
|
|
|
|
|
764
|
|
|
preg_match('/\n\d FORM (.+)/', $gedcom, $match); |
|
765
|
|
|
$format = $match[1] ?? ''; |
|
766
|
|
|
|
|
767
|
|
|
preg_match('/\n\d MEDI (.+)/', $gedcom, $match); |
|
768
|
|
|
$media = $match[1] ?? ''; |
|
769
|
|
|
|
|
770
|
|
|
preg_match('/\n\d _SCBK (.+)/', $gedcom, $match); |
|
771
|
|
|
$scrapbook = $match[1] ?? ''; |
|
772
|
|
|
|
|
773
|
|
|
preg_match('/\n\d _PRIM (.+)/', $gedcom, $match); |
|
774
|
|
|
$primary = $match[1] ?? ''; |
|
775
|
|
|
|
|
776
|
|
|
preg_match('/\n\d _TYPE (.+)/', $gedcom, $match); |
|
777
|
|
|
if ($media === '') { |
|
778
|
|
|
// Legacy uses _TYPE instead of MEDI |
|
779
|
|
|
$media = $match[1] ?? ''; |
|
780
|
|
|
$type2 = ''; |
|
781
|
|
|
} else { |
|
782
|
|
|
$type2 = $match[1] ?? ''; |
|
783
|
|
|
} |
|
784
|
|
|
|
|
785
|
|
|
preg_match('/\n\d NOTE (.+(?:\n\d CONT.+)*)/', $gedcom, $match); |
|
786
|
|
|
$note = $match[1] ?? ''; |
|
787
|
|
|
|
|
788
|
|
|
// Have we already created a media object with the same title/filename? |
|
789
|
|
|
$xref = DB::table('media_file') |
|
790
|
|
|
->where('m_file', '=', $tree->id()) |
|
791
|
|
|
->where('descriptive_title', '=', mb_substr($title, 0, 248)) |
|
792
|
|
|
->where('multimedia_file_refn', '=', mb_substr($file, 0, 248)) |
|
793
|
|
|
->value('m_id'); |
|
794
|
|
|
|
|
795
|
|
|
if ($xref === null && $file !== '') { |
|
796
|
|
|
$xref = Registry::xrefFactory()->make(Media::RECORD_TYPE); |
|
797
|
|
|
|
|
798
|
|
|
// convert to a media-object |
|
799
|
|
|
$gedcom = '0 @' . $xref . "@ OBJE\n1 FILE " . $file; |
|
800
|
|
|
|
|
801
|
|
|
if ($format !== '') { |
|
802
|
|
|
$gedcom .= "\n2 FORM " . $format; |
|
803
|
|
|
|
|
804
|
|
|
if ($media !== '') { |
|
805
|
|
|
$gedcom .= "\n3 TYPE " . $media; |
|
806
|
|
|
} |
|
807
|
|
|
} |
|
808
|
|
|
|
|
809
|
|
|
if ($title !== '') { |
|
810
|
|
|
$gedcom .= "\n3 TITL " . $title; |
|
811
|
|
|
} |
|
812
|
|
|
|
|
813
|
|
|
if ($scrapbook !== '') { |
|
814
|
|
|
$gedcom .= "\n1 _SCBK " . $scrapbook; |
|
815
|
|
|
} |
|
816
|
|
|
|
|
817
|
|
|
if ($primary !== '') { |
|
818
|
|
|
$gedcom .= "\n1 _PRIM " . $primary; |
|
819
|
|
|
} |
|
820
|
|
|
|
|
821
|
|
|
if ($type2 !== '') { |
|
822
|
|
|
$gedcom .= "\n1 _TYPE " . $type2; |
|
823
|
|
|
} |
|
824
|
|
|
|
|
825
|
|
|
if ($note !== '') { |
|
826
|
|
|
$gedcom .= "\n1 NOTE " . strtr($note, ["\n3 CONT" => "\n2 CONT", "\n4 CONT" => "\n3 CONT"]); |
|
827
|
|
|
} |
|
828
|
|
|
|
|
829
|
|
|
DB::table('media')->insert([ |
|
830
|
|
|
'm_id' => $xref, |
|
831
|
|
|
'm_file' => $tree->id(), |
|
832
|
|
|
'm_gedcom' => $gedcom, |
|
833
|
|
|
]); |
|
834
|
|
|
|
|
835
|
|
|
DB::table('media_file')->insert([ |
|
836
|
|
|
'm_id' => $xref, |
|
837
|
|
|
'm_file' => $tree->id(), |
|
838
|
|
|
'multimedia_file_refn' => mb_substr($file, 0, 248), |
|
839
|
|
|
'multimedia_format' => mb_substr($format, 0, 4), |
|
840
|
|
|
'source_media_type' => mb_substr($media, 0, 15), |
|
841
|
|
|
'descriptive_title' => mb_substr($title, 0, 248), |
|
842
|
|
|
]); |
|
843
|
|
|
} |
|
844
|
|
|
|
|
845
|
|
|
return $xref; |
|
846
|
|
|
} |
|
847
|
|
|
|
|
848
|
|
|
/** |
|
849
|
|
|
* update a record in the database |
|
850
|
|
|
* |
|
851
|
|
|
* @param string $gedrec |
|
852
|
|
|
* @param Tree $tree |
|
853
|
|
|
* @param bool $delete |
|
854
|
|
|
* |
|
855
|
|
|
* @return void |
|
856
|
|
|
* @throws GedcomErrorException |
|
857
|
|
|
*/ |
|
858
|
|
|
public static function updateRecord(string $gedrec, Tree $tree, bool $delete): void |
|
859
|
|
|
{ |
|
860
|
|
|
if (preg_match('/^0 @(' . Gedcom::REGEX_XREF . ')@ (' . Gedcom::REGEX_TAG . ')/', $gedrec, $match)) { |
|
861
|
|
|
[, $gid, $type] = $match; |
|
862
|
|
|
} elseif (preg_match('/^0 (HEAD)(?:\n|$)/', $gedrec, $match)) { |
|
863
|
|
|
// The HEAD record has no XREF. Any others? |
|
864
|
|
|
$gid = $match[1]; |
|
865
|
|
|
$type = $match[1]; |
|
866
|
|
|
} else { |
|
867
|
|
|
throw new GedcomErrorException($gedrec); |
|
868
|
|
|
} |
|
869
|
|
|
|
|
870
|
|
|
// Place links |
|
871
|
|
|
DB::table('placelinks') |
|
872
|
|
|
->where('pl_gid', '=', $gid) |
|
873
|
|
|
->where('pl_file', '=', $tree->id()) |
|
874
|
|
|
->delete(); |
|
875
|
|
|
|
|
876
|
|
|
// Orphaned places. If we're deleting "Westminster, London, England", |
|
877
|
|
|
// then we may also need to delete "London, England" and "England". |
|
878
|
|
|
do { |
|
879
|
|
|
$affected = DB::table('places') |
|
880
|
|
|
->leftJoin('placelinks', static function (JoinClause $join): void { |
|
881
|
|
|
$join |
|
882
|
|
|
->on('p_id', '=', 'pl_p_id') |
|
883
|
|
|
->on('p_file', '=', 'pl_file'); |
|
884
|
|
|
}) |
|
885
|
|
|
->whereNull('pl_p_id') |
|
886
|
|
|
->delete(); |
|
887
|
|
|
} while ($affected > 0); |
|
888
|
|
|
|
|
889
|
|
|
DB::table('dates') |
|
890
|
|
|
->where('d_gid', '=', $gid) |
|
891
|
|
|
->where('d_file', '=', $tree->id()) |
|
892
|
|
|
->delete(); |
|
893
|
|
|
|
|
894
|
|
|
DB::table('name') |
|
895
|
|
|
->where('n_id', '=', $gid) |
|
896
|
|
|
->where('n_file', '=', $tree->id()) |
|
897
|
|
|
->delete(); |
|
898
|
|
|
|
|
899
|
|
|
DB::table('link') |
|
900
|
|
|
->where('l_from', '=', $gid) |
|
901
|
|
|
->where('l_file', '=', $tree->id()) |
|
902
|
|
|
->delete(); |
|
903
|
|
|
|
|
904
|
|
|
switch ($type) { |
|
905
|
|
|
case Individual::RECORD_TYPE: |
|
906
|
|
|
DB::table('individuals') |
|
907
|
|
|
->where('i_id', '=', $gid) |
|
908
|
|
|
->where('i_file', '=', $tree->id()) |
|
909
|
|
|
->delete(); |
|
910
|
|
|
break; |
|
911
|
|
|
|
|
912
|
|
|
case Family::RECORD_TYPE: |
|
913
|
|
|
DB::table('families') |
|
914
|
|
|
->where('f_id', '=', $gid) |
|
915
|
|
|
->where('f_file', '=', $tree->id()) |
|
916
|
|
|
->delete(); |
|
917
|
|
|
break; |
|
918
|
|
|
|
|
919
|
|
|
case Source::RECORD_TYPE: |
|
920
|
|
|
DB::table('sources') |
|
921
|
|
|
->where('s_id', '=', $gid) |
|
922
|
|
|
->where('s_file', '=', $tree->id()) |
|
923
|
|
|
->delete(); |
|
924
|
|
|
break; |
|
925
|
|
|
|
|
926
|
|
|
case Media::RECORD_TYPE: |
|
927
|
|
|
DB::table('media_file') |
|
928
|
|
|
->where('m_id', '=', $gid) |
|
929
|
|
|
->where('m_file', '=', $tree->id()) |
|
930
|
|
|
->delete(); |
|
931
|
|
|
|
|
932
|
|
|
DB::table('media') |
|
933
|
|
|
->where('m_id', '=', $gid) |
|
934
|
|
|
->where('m_file', '=', $tree->id()) |
|
935
|
|
|
->delete(); |
|
936
|
|
|
break; |
|
937
|
|
|
|
|
938
|
|
|
default: |
|
939
|
|
|
DB::table('other') |
|
940
|
|
|
->where('o_id', '=', $gid) |
|
941
|
|
|
->where('o_file', '=', $tree->id()) |
|
942
|
|
|
->delete(); |
|
943
|
|
|
break; |
|
944
|
|
|
} |
|
945
|
|
|
|
|
946
|
|
|
if (!$delete) { |
|
947
|
|
|
self::importRecord($gedrec, $tree, true); |
|
948
|
|
|
} |
|
949
|
|
|
} |
|
950
|
|
|
} |
|
951
|
|
|
|
This function has been deprecated. The supplier of the function has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.