Passed
Push — 1.7 ( 23cbb7...8df8a8 )
by Greg
08:15
created
app/Functions/FunctionsImport.php 3 patches
Indentation   +1075 added lines, -1075 removed lines patch added patch discarded remove patch
@@ -34,1121 +34,1121 @@
 block discarded – undo
34 34
  * Class FunctionsImport - common functions
35 35
  */
36 36
 class FunctionsImport {
37
-	/**
38
-	 * Tidy up a gedcom record on import, so that we can access it consistently/efficiently.
39
-	 *
40
-	 * @param string $rec
41
-	 * @param Tree   $tree
42
-	 *
43
-	 * @return string
44
-	 */
45
-	public static function reformatRecord($rec, Tree $tree) {
46
-		// Strip out UTF8 formatting characters
47
-		$rec = str_replace(array(WT_UTF8_BOM, WT_UTF8_LRM, WT_UTF8_RLM), '', $rec);
37
+    /**
38
+     * Tidy up a gedcom record on import, so that we can access it consistently/efficiently.
39
+     *
40
+     * @param string $rec
41
+     * @param Tree   $tree
42
+     *
43
+     * @return string
44
+     */
45
+    public static function reformatRecord($rec, Tree $tree) {
46
+        // Strip out UTF8 formatting characters
47
+        $rec = str_replace(array(WT_UTF8_BOM, WT_UTF8_LRM, WT_UTF8_RLM), '', $rec);
48 48
 
49
-		// Strip out mac/msdos line endings
50
-		$rec = preg_replace("/[\r\n]+/", "\n", $rec);
49
+        // Strip out mac/msdos line endings
50
+        $rec = preg_replace("/[\r\n]+/", "\n", $rec);
51 51
 
52
-		// Extract lines from the record; lines consist of: level + optional xref + tag + optional data
53
-		$num_matches = preg_match_all('/^[ \t]*(\d+)[ \t]*(@[^@]*@)?[ \t]*(\w+)[ \t]?(.*)$/m', $rec, $matches, PREG_SET_ORDER);
52
+        // Extract lines from the record; lines consist of: level + optional xref + tag + optional data
53
+        $num_matches = preg_match_all('/^[ \t]*(\d+)[ \t]*(@[^@]*@)?[ \t]*(\w+)[ \t]?(.*)$/m', $rec, $matches, PREG_SET_ORDER);
54 54
 
55
-		// Process the record line-by-line
56
-		$newrec = '';
57
-		foreach ($matches as $n => $match) {
58
-			list(, $level, $xref, $tag, $data) = $match;
59
-			$tag                               = strtoupper($tag); // Tags should always be upper case
60
-			switch ($tag) {
61
-				// Convert PhpGedView tags to WT
62
-			case '_PGVU':
63
-				$tag = '_WT_USER';
64
-				break;
65
-			case '_PGV_OBJS':
66
-				$tag = '_WT_OBJE_SORT';
67
-				break;
68
-				// Convert FTM-style "TAG_FORMAL_NAME" into "TAG".
69
-			case 'ABBREVIATION':
70
-				$tag = 'ABBR';
71
-				break;
72
-			case 'ADDRESS':
73
-				$tag = 'ADDR';
74
-				break;
75
-			case 'ADDRESS1':
76
-				$tag = 'ADR1';
77
-				break;
78
-			case 'ADDRESS2':
79
-				$tag = 'ADR2';
80
-				break;
81
-			case 'ADDRESS3':
82
-				$tag = 'ADR3';
83
-				break;
84
-			case 'ADOPTION':
85
-				$tag = 'ADOP';
86
-				break;
87
-			case 'ADULT_CHRISTENING':
88
-				$tag = 'CHRA';
89
-				break;
90
-			case 'AFN':
91
-				// AFN values are upper case
92
-				$data = strtoupper($data);
93
-				break;
94
-			case 'AGENCY':
95
-				$tag = 'AGNC';
96
-				break;
97
-			case 'ALIAS':
98
-				$tag = 'ALIA';
99
-				break;
100
-			case 'ANCESTORS':
101
-				$tag = 'ANCE';
102
-				break;
103
-			case 'ANCES_INTEREST':
104
-				$tag = 'ANCI';
105
-				break;
106
-			case 'ANNULMENT':
107
-				$tag = 'ANUL';
108
-				break;
109
-			case 'ASSOCIATES':
110
-				$tag = 'ASSO';
111
-				break;
112
-			case 'AUTHOR':
113
-				$tag = 'AUTH';
114
-				break;
115
-			case 'BAPTISM':
116
-				$tag = 'BAPM';
117
-				break;
118
-			case 'BAPTISM_LDS':
119
-				$tag = 'BAPL';
120
-				break;
121
-			case 'BAR_MITZVAH':
122
-				$tag = 'BARM';
123
-				break;
124
-			case 'BAS_MITZVAH':
125
-				$tag = 'BASM';
126
-				break;
127
-			case 'BIRTH':
128
-				$tag = 'BIRT';
129
-				break;
130
-			case 'BLESSING':
131
-				$tag = 'BLES';
132
-				break;
133
-			case 'BURIAL':
134
-				$tag = 'BURI';
135
-				break;
136
-			case 'CALL_NUMBER':
137
-				$tag = 'CALN';
138
-				break;
139
-			case 'CASTE':
140
-				$tag = 'CAST';
141
-				break;
142
-			case 'CAUSE':
143
-				$tag = 'CAUS';
144
-				break;
145
-			case 'CENSUS':
146
-				$tag = 'CENS';
147
-				break;
148
-			case 'CHANGE':
149
-				$tag = 'CHAN';
150
-				break;
151
-			case 'CHARACTER':
152
-				$tag = 'CHAR';
153
-				break;
154
-			case 'CHILD':
155
-				$tag = 'CHIL';
156
-				break;
157
-			case 'CHILDREN_COUNT':
158
-				$tag = 'NCHI';
159
-				break;
160
-			case 'CHRISTENING':
161
-				$tag = 'CHR';
162
-				break;
163
-			case 'CONCATENATION':
164
-				$tag = 'CONC';
165
-				break;
166
-			case 'CONFIRMATION':
167
-				$tag = 'CONF';
168
-				break;
169
-			case 'CONFIRMATION_LDS':
170
-				$tag = 'CONL';
171
-				break;
172
-			case 'CONTINUED':
173
-				$tag = 'CONT';
174
-				break;
175
-			case 'COPYRIGHT':
176
-				$tag = 'COPR';
177
-				break;
178
-			case 'CORPORATE':
179
-				$tag = 'CORP';
180
-				break;
181
-			case 'COUNTRY':
182
-				$tag = 'CTRY';
183
-				break;
184
-			case 'CREMATION':
185
-				$tag = 'CREM';
186
-				break;
187
-			case 'DATE':
188
-				// Preserve text from INT dates
189
-				if (strpos($data, '(') !== false) {
190
-					list($date, $text) = explode('(', $data, 2);
191
-					$text              = ' (' . $text;
192
-				} else {
193
-					$date = $data;
194
-					$text = '';
195
-				}
196
-				// Capitals
197
-				$date = strtoupper($date);
198
-				// Temporarily add leading/trailing spaces, to allow efficient matching below
199
-				$date = " {$date} ";
200
-				// Ensure space digits and letters
201
-				$date = preg_replace('/([A-Z])(\d)/', '$1 $2', $date);
202
-				$date = preg_replace('/(\d)([A-Z])/', '$1 $2', $date);
203
-				// Ensure space before/after calendar escapes
204
-				$date = preg_replace('/@#[^@]+@/', ' $0 ', $date);
205
-				// "BET." => "BET"
206
-				$date = preg_replace('/(\w\w)\./', '$1', $date);
207
-				// "CIR" => "ABT"
208
-				$date = str_replace(' CIR ', ' ABT ', $date);
209
-				$date = str_replace(' APX ', ' ABT ', $date);
210
-				// B.C. => BC (temporarily, to allow easier handling of ".")
211
-				$date = str_replace(' B.C. ', ' BC ', $date);
212
-				// "BET X - Y " => "BET X AND Y"
213
-				$date = preg_replace('/^(.* BET .+) - (.+)/', '$1 AND $2', $date);
214
-				$date = preg_replace('/^(.* FROM .+) - (.+)/', '$1 TO $2', $date);
215
-				// "@#ESC@ FROM X TO Y" => "FROM @#ESC@ X TO @#ESC@ Y"
216
-				$date = preg_replace('/^ +(@#[^@]+@) +FROM +(.+) +TO +(.+)/', ' FROM $1 $2 TO $1 $3', $date);
217
-				$date = preg_replace('/^ +(@#[^@]+@) +BET +(.+) +AND +(.+)/', ' BET $1 $2 AND $1 $3', $date);
218
-				// "@#ESC@ AFT X" => "AFT @#ESC@ X"
219
-				$date = preg_replace('/^ +(@#[^@]+@) +(FROM|BET|TO|AND|BEF|AFT|CAL|EST|INT|ABT) +(.+)/', ' $2 $1 $3', $date);
220
-				// Ignore any remaining punctuation, e.g. "14-MAY, 1900" => "14 MAY 1900"
221
-				// (don't change "/" - it is used in NS/OS dates)
222
-				$date = preg_replace('/[.,:;-]/', ' ', $date);
223
-				// BC => B.C.
224
-				$date = str_replace(' BC ', ' B.C. ', $date);
225
-				// Append the "INT" text
226
-				$data = $date . $text;
227
-				break;
228
-			case 'DEATH':
229
-				$tag = 'DEAT';
230
-				break;
231
-			case '_DEATH_OF_SPOUSE':
232
-				$tag = '_DETS';
233
-				break;
234
-			case '_DEGREE':
235
-				$tag = '_DEG';
236
-				break;
237
-			case 'DESCENDANTS':
238
-				$tag = 'DESC';
239
-				break;
240
-			case 'DESCENDANT_INT':
241
-				$tag = 'DESI';
242
-				break;
243
-			case 'DESTINATION':
244
-				$tag = 'DEST';
245
-				break;
246
-			case 'DIVORCE':
247
-				$tag = 'DIV';
248
-				break;
249
-			case 'DIVORCE_FILED':
250
-				$tag = 'DIVF';
251
-				break;
252
-			case 'EDUCATION':
253
-				$tag = 'EDUC';
254
-				break;
255
-			case 'EMIGRATION':
256
-				$tag = 'EMIG';
257
-				break;
258
-			case 'ENDOWMENT':
259
-				$tag = 'ENDL';
260
-				break;
261
-			case 'ENGAGEMENT':
262
-				$tag = 'ENGA';
263
-				break;
264
-			case 'EVENT':
265
-				$tag = 'EVEN';
266
-				break;
267
-			case 'FACSIMILE':
268
-				$tag = 'FAX';
269
-				break;
270
-			case 'FAMILY':
271
-				$tag = 'FAM';
272
-				break;
273
-			case 'FAMILY_CHILD':
274
-				$tag = 'FAMC';
275
-				break;
276
-			case 'FAMILY_FILE':
277
-				$tag = 'FAMF';
278
-				break;
279
-			case 'FAMILY_SPOUSE':
280
-				$tag = 'FAMS';
281
-				break;
282
-			case 'FIRST_COMMUNION':
283
-				$tag = 'FCOM';
284
-				break;
285
-			case '_FILE':
286
-				$tag = 'FILE';
287
-				break;
288
-			case 'FORMAT':
289
-				$tag = 'FORM';
290
-			case 'FORM':
291
-				// Consistent commas
292
-				$data = preg_replace('/ *, */', ', ', $data);
293
-				break;
294
-			case 'GEDCOM':
295
-				$tag = 'GEDC';
296
-				break;
297
-			case 'GIVEN_NAME':
298
-				$tag = 'GIVN';
299
-				break;
300
-			case 'GRADUATION':
301
-				$tag = 'GRAD';
302
-				break;
303
-			case 'HEADER':
304
-				$tag = 'HEAD';
305
-			case 'HEAD':
306
-				// HEAD records don't have an XREF or DATA
307
-				if ($level == '0') {
308
-					$xref = '';
309
-					$data = '';
310
-				}
311
-				break;
312
-			case 'HUSBAND':
313
-				$tag = 'HUSB';
314
-				break;
315
-			case 'IDENT_NUMBER':
316
-				$tag = 'IDNO';
317
-				break;
318
-			case 'IMMIGRATION':
319
-				$tag = 'IMMI';
320
-				break;
321
-			case 'INDIVIDUAL':
322
-				$tag = 'INDI';
323
-				break;
324
-			case 'LANGUAGE':
325
-				$tag = 'LANG';
326
-				break;
327
-			case 'LATITUDE':
328
-				$tag = 'LATI';
329
-				break;
330
-			case 'LONGITUDE':
331
-				$tag = 'LONG';
332
-				break;
333
-			case 'MARRIAGE':
334
-				$tag = 'MARR';
335
-				break;
336
-			case 'MARRIAGE_BANN':
337
-				$tag = 'MARB';
338
-				break;
339
-			case 'MARRIAGE_COUNT':
340
-				$tag = 'NMR';
341
-				break;
342
-			case 'MARRIAGE_CONTRACT':
343
-				$tag = 'MARC';
344
-				break;
345
-			case 'MARRIAGE_LICENSE':
346
-				$tag = 'MARL';
347
-				break;
348
-			case 'MARRIAGE_SETTLEMENT':
349
-				$tag = 'MARS';
350
-				break;
351
-			case 'MEDIA':
352
-				$tag = 'MEDI';
353
-				break;
354
-			case '_MEDICAL':
355
-				$tag = '_MDCL';
356
-				break;
357
-			case '_MILITARY_SERVICE':
358
-				$tag = '_MILT';
359
-				break;
360
-			case 'NAME':
361
-				// Tidy up whitespace
362
-				$data = preg_replace('/  +/', ' ', trim($data));
363
-				break;
364
-			case 'NAME_PREFIX':
365
-				$tag = 'NPFX';
366
-				break;
367
-			case 'NAME_SUFFIX':
368
-				$tag = 'NSFX';
369
-				break;
370
-			case 'NATIONALITY':
371
-				$tag = 'NATI';
372
-				break;
373
-			case 'NATURALIZATION':
374
-				$tag = 'NATU';
375
-				break;
376
-			case 'NICKNAME':
377
-				$tag = 'NICK';
378
-				break;
379
-			case 'OBJECT':
380
-				$tag = 'OBJE';
381
-				break;
382
-			case 'OCCUPATION':
383
-				$tag = 'OCCU';
384
-				break;
385
-			case 'ORDINANCE':
386
-				$tag = 'ORDI';
387
-				break;
388
-			case 'ORDINATION':
389
-				$tag = 'ORDN';
390
-				break;
391
-			case 'PEDIGREE':
392
-				$tag = 'PEDI';
393
-			case 'PEDI':
394
-				// PEDI values are lower case
395
-				$data = strtolower($data);
396
-				break;
397
-			case 'PHONE':
398
-				$tag = 'PHON';
399
-				break;
400
-			case 'PHONETIC':
401
-				$tag = 'FONE';
402
-				break;
403
-			case 'PHY_DESCRIPTION':
404
-				$tag = 'DSCR';
405
-				break;
406
-			case 'PLACE':
407
-				$tag = 'PLAC';
408
-			case 'PLAC':
409
-				// Consistent commas
410
-				$data = preg_replace('/ *(،|,) */', ', ', $data);
411
-				// The Master Genealogist stores LAT/LONG data in the PLAC field, e.g. Pennsylvania, USA, 395945N0751013W
412
-				if (preg_match('/(.*), (\d\d)(\d\d)(\d\d)([NS])(\d\d\d)(\d\d)(\d\d)([EW])$/', $data, $match)) {
413
-					$data =
414
-						$match[1] . "\n" .
415
-						($level + 1) . " MAP\n" .
416
-						($level + 2) . " LATI " . ($match[5] . (round($match[2] + ($match[3] / 60) + ($match[4] / 3600), 4))) . "\n" .
417
-						($level + 2) . " LONG " . ($match[9] . (round($match[6] + ($match[7] / 60) + ($match[8] / 3600), 4)));
418
-				}
419
-				break;
420
-			case 'POSTAL_CODE':
421
-				$tag = 'POST';
422
-				break;
423
-			case 'PROBATE':
424
-				$tag = 'PROB';
425
-				break;
426
-			case 'PROPERTY':
427
-				$tag = 'PROP';
428
-				break;
429
-			case 'PUBLICATION':
430
-				$tag = 'PUBL';
431
-				break;
432
-			case 'QUALITY_OF_DATA':
433
-				$tag = 'QUAL';
434
-				break;
435
-			case 'REC_FILE_NUMBER':
436
-				$tag = 'RFN';
437
-				break;
438
-			case 'REC_ID_NUMBER':
439
-				$tag = 'RIN';
440
-				break;
441
-			case 'REFERENCE':
442
-				$tag = 'REFN';
443
-				break;
444
-			case 'RELATIONSHIP':
445
-				$tag = 'RELA';
446
-				break;
447
-			case 'RELIGION':
448
-				$tag = 'RELI';
449
-				break;
450
-			case 'REPOSITORY':
451
-				$tag = 'REPO';
452
-				break;
453
-			case 'RESIDENCE':
454
-				$tag = 'RESI';
455
-				break;
456
-			case 'RESTRICTION':
457
-				$tag = 'RESN';
458
-			case 'RESN':
459
-				// RESN values are lower case (confidential, privacy, locked, none)
460
-				$data = strtolower($data);
461
-				if ($data == 'invisible') {
462
-					$data = 'confidential'; // From old versions of Legacy.
463
-				}
464
-				break;
465
-			case 'RETIREMENT':
466
-				$tag = 'RETI';
467
-				break;
468
-			case 'ROMANIZED':
469
-				$tag = 'ROMN';
470
-				break;
471
-			case 'SEALING_CHILD':
472
-				$tag = 'SLGC';
473
-				break;
474
-			case 'SEALING_SPOUSE':
475
-				$tag = 'SLGS';
476
-				break;
477
-			case 'SOC_SEC_NUMBER':
478
-				$tag = 'SSN';
479
-				break;
480
-			case 'SEX':
481
-				$data = strtoupper($data);
482
-				break;
483
-			case 'SOURCE':
484
-				$tag = 'SOUR';
485
-				break;
486
-			case 'STATE':
487
-				$tag = 'STAE';
488
-				break;
489
-			case 'STATUS':
490
-				$tag = 'STAT';
491
-			case 'STAT':
492
-				if ($data == 'CANCELLED') {
493
-					// PhpGedView mis-spells this tag - correct it.
494
-					$data = 'CANCELED';
495
-				}
496
-				break;
497
-			case 'SUBMISSION':
498
-				$tag = 'SUBN';
499
-				break;
500
-			case 'SUBMITTER':
501
-				$tag = 'SUBM';
502
-				break;
503
-			case 'SURNAME':
504
-				$tag = 'SURN';
505
-				break;
506
-			case 'SURN_PREFIX':
507
-				$tag = 'SPFX';
508
-				break;
509
-			case 'TEMPLE':
510
-				$tag = 'TEMP';
511
-			case 'TEMP':
512
-				// Temple codes are upper case
513
-				$data = strtoupper($data);
514
-				break;
515
-			case 'TITLE':
516
-				$tag = 'TITL';
517
-				break;
518
-			case 'TRAILER':
519
-				$tag = 'TRLR';
520
-			case 'TRLR':
521
-				// TRLR records don't have an XREF or DATA
522
-				if ($level == '0') {
523
-					$xref = '';
524
-					$data = '';
525
-				}
526
-				break;
527
-			case 'VERSION':
528
-				$tag = 'VERS';
529
-				break;
530
-			case 'WEB':
531
-				$tag = 'WWW';
532
-				break;
533
-			}
534
-			// Suppress "Y", for facts/events with a DATE or PLAC
535
-			if ($data == 'y') {
536
-				$data = 'Y';
537
-			}
538
-			if ($level == '1' && $data == 'Y') {
539
-				for ($i = $n + 1; $i < $num_matches - 1 && $matches[$i][1] != '1'; ++$i) {
540
-					if ($matches[$i][3] == 'DATE' || $matches[$i][3] == 'PLAC') {
541
-						$data = '';
542
-						break;
543
-					}
544
-				}
545
-			}
546
-			// Reassemble components back into a single line
547
-			switch ($tag) {
548
-			default:
549
-				// Remove tabs and multiple/leading/trailing spaces
550
-				if (strpos($data, "\t") !== false) {
551
-					$data = str_replace("\t", ' ', $data);
552
-				}
553
-				if (substr($data, 0, 1) == ' ' || substr($data, -1, 1) == ' ') {
554
-					$data = trim($data);
555
-				}
556
-				while (strpos($data, '  ')) {
557
-					$data = str_replace('  ', ' ', $data);
558
-				}
559
-				$newrec .= ($newrec ? "\n" : '') . $level . ' ' . ($level == '0' && $xref ? $xref . ' ' : '') . $tag . ($data === '' && $tag != "NOTE" ? '' : ' ' . $data);
560
-				break;
561
-			case 'NOTE':
562
-			case 'TEXT':
563
-			case 'DATA':
564
-			case 'CONT':
565
-				$newrec .= ($newrec ? "\n" : '') . $level . ' ' . ($level == '0' && $xref ? $xref . ' ' : '') . $tag . ($data === '' && $tag != "NOTE" ? '' : ' ' . $data);
566
-				break;
567
-			case 'FILE':
568
-				// Strip off the user-defined path prefix
569
-				$GEDCOM_MEDIA_PATH = $tree->getPreference('GEDCOM_MEDIA_PATH');
570
-				if ($GEDCOM_MEDIA_PATH && strpos($data, $GEDCOM_MEDIA_PATH) === 0) {
571
-					$data = substr($data, strlen($GEDCOM_MEDIA_PATH));
572
-				}
573
-				// convert backslashes in filenames to forward slashes
574
-				$data = preg_replace("/\\\/", "/", $data);
55
+        // Process the record line-by-line
56
+        $newrec = '';
57
+        foreach ($matches as $n => $match) {
58
+            list(, $level, $xref, $tag, $data) = $match;
59
+            $tag                               = strtoupper($tag); // Tags should always be upper case
60
+            switch ($tag) {
61
+                // Convert PhpGedView tags to WT
62
+            case '_PGVU':
63
+                $tag = '_WT_USER';
64
+                break;
65
+            case '_PGV_OBJS':
66
+                $tag = '_WT_OBJE_SORT';
67
+                break;
68
+                // Convert FTM-style "TAG_FORMAL_NAME" into "TAG".
69
+            case 'ABBREVIATION':
70
+                $tag = 'ABBR';
71
+                break;
72
+            case 'ADDRESS':
73
+                $tag = 'ADDR';
74
+                break;
75
+            case 'ADDRESS1':
76
+                $tag = 'ADR1';
77
+                break;
78
+            case 'ADDRESS2':
79
+                $tag = 'ADR2';
80
+                break;
81
+            case 'ADDRESS3':
82
+                $tag = 'ADR3';
83
+                break;
84
+            case 'ADOPTION':
85
+                $tag = 'ADOP';
86
+                break;
87
+            case 'ADULT_CHRISTENING':
88
+                $tag = 'CHRA';
89
+                break;
90
+            case 'AFN':
91
+                // AFN values are upper case
92
+                $data = strtoupper($data);
93
+                break;
94
+            case 'AGENCY':
95
+                $tag = 'AGNC';
96
+                break;
97
+            case 'ALIAS':
98
+                $tag = 'ALIA';
99
+                break;
100
+            case 'ANCESTORS':
101
+                $tag = 'ANCE';
102
+                break;
103
+            case 'ANCES_INTEREST':
104
+                $tag = 'ANCI';
105
+                break;
106
+            case 'ANNULMENT':
107
+                $tag = 'ANUL';
108
+                break;
109
+            case 'ASSOCIATES':
110
+                $tag = 'ASSO';
111
+                break;
112
+            case 'AUTHOR':
113
+                $tag = 'AUTH';
114
+                break;
115
+            case 'BAPTISM':
116
+                $tag = 'BAPM';
117
+                break;
118
+            case 'BAPTISM_LDS':
119
+                $tag = 'BAPL';
120
+                break;
121
+            case 'BAR_MITZVAH':
122
+                $tag = 'BARM';
123
+                break;
124
+            case 'BAS_MITZVAH':
125
+                $tag = 'BASM';
126
+                break;
127
+            case 'BIRTH':
128
+                $tag = 'BIRT';
129
+                break;
130
+            case 'BLESSING':
131
+                $tag = 'BLES';
132
+                break;
133
+            case 'BURIAL':
134
+                $tag = 'BURI';
135
+                break;
136
+            case 'CALL_NUMBER':
137
+                $tag = 'CALN';
138
+                break;
139
+            case 'CASTE':
140
+                $tag = 'CAST';
141
+                break;
142
+            case 'CAUSE':
143
+                $tag = 'CAUS';
144
+                break;
145
+            case 'CENSUS':
146
+                $tag = 'CENS';
147
+                break;
148
+            case 'CHANGE':
149
+                $tag = 'CHAN';
150
+                break;
151
+            case 'CHARACTER':
152
+                $tag = 'CHAR';
153
+                break;
154
+            case 'CHILD':
155
+                $tag = 'CHIL';
156
+                break;
157
+            case 'CHILDREN_COUNT':
158
+                $tag = 'NCHI';
159
+                break;
160
+            case 'CHRISTENING':
161
+                $tag = 'CHR';
162
+                break;
163
+            case 'CONCATENATION':
164
+                $tag = 'CONC';
165
+                break;
166
+            case 'CONFIRMATION':
167
+                $tag = 'CONF';
168
+                break;
169
+            case 'CONFIRMATION_LDS':
170
+                $tag = 'CONL';
171
+                break;
172
+            case 'CONTINUED':
173
+                $tag = 'CONT';
174
+                break;
175
+            case 'COPYRIGHT':
176
+                $tag = 'COPR';
177
+                break;
178
+            case 'CORPORATE':
179
+                $tag = 'CORP';
180
+                break;
181
+            case 'COUNTRY':
182
+                $tag = 'CTRY';
183
+                break;
184
+            case 'CREMATION':
185
+                $tag = 'CREM';
186
+                break;
187
+            case 'DATE':
188
+                // Preserve text from INT dates
189
+                if (strpos($data, '(') !== false) {
190
+                    list($date, $text) = explode('(', $data, 2);
191
+                    $text              = ' (' . $text;
192
+                } else {
193
+                    $date = $data;
194
+                    $text = '';
195
+                }
196
+                // Capitals
197
+                $date = strtoupper($date);
198
+                // Temporarily add leading/trailing spaces, to allow efficient matching below
199
+                $date = " {$date} ";
200
+                // Ensure space digits and letters
201
+                $date = preg_replace('/([A-Z])(\d)/', '$1 $2', $date);
202
+                $date = preg_replace('/(\d)([A-Z])/', '$1 $2', $date);
203
+                // Ensure space before/after calendar escapes
204
+                $date = preg_replace('/@#[^@]+@/', ' $0 ', $date);
205
+                // "BET." => "BET"
206
+                $date = preg_replace('/(\w\w)\./', '$1', $date);
207
+                // "CIR" => "ABT"
208
+                $date = str_replace(' CIR ', ' ABT ', $date);
209
+                $date = str_replace(' APX ', ' ABT ', $date);
210
+                // B.C. => BC (temporarily, to allow easier handling of ".")
211
+                $date = str_replace(' B.C. ', ' BC ', $date);
212
+                // "BET X - Y " => "BET X AND Y"
213
+                $date = preg_replace('/^(.* BET .+) - (.+)/', '$1 AND $2', $date);
214
+                $date = preg_replace('/^(.* FROM .+) - (.+)/', '$1 TO $2', $date);
215
+                // "@#ESC@ FROM X TO Y" => "FROM @#ESC@ X TO @#ESC@ Y"
216
+                $date = preg_replace('/^ +(@#[^@]+@) +FROM +(.+) +TO +(.+)/', ' FROM $1 $2 TO $1 $3', $date);
217
+                $date = preg_replace('/^ +(@#[^@]+@) +BET +(.+) +AND +(.+)/', ' BET $1 $2 AND $1 $3', $date);
218
+                // "@#ESC@ AFT X" => "AFT @#ESC@ X"
219
+                $date = preg_replace('/^ +(@#[^@]+@) +(FROM|BET|TO|AND|BEF|AFT|CAL|EST|INT|ABT) +(.+)/', ' $2 $1 $3', $date);
220
+                // Ignore any remaining punctuation, e.g. "14-MAY, 1900" => "14 MAY 1900"
221
+                // (don't change "/" - it is used in NS/OS dates)
222
+                $date = preg_replace('/[.,:;-]/', ' ', $date);
223
+                // BC => B.C.
224
+                $date = str_replace(' BC ', ' B.C. ', $date);
225
+                // Append the "INT" text
226
+                $data = $date . $text;
227
+                break;
228
+            case 'DEATH':
229
+                $tag = 'DEAT';
230
+                break;
231
+            case '_DEATH_OF_SPOUSE':
232
+                $tag = '_DETS';
233
+                break;
234
+            case '_DEGREE':
235
+                $tag = '_DEG';
236
+                break;
237
+            case 'DESCENDANTS':
238
+                $tag = 'DESC';
239
+                break;
240
+            case 'DESCENDANT_INT':
241
+                $tag = 'DESI';
242
+                break;
243
+            case 'DESTINATION':
244
+                $tag = 'DEST';
245
+                break;
246
+            case 'DIVORCE':
247
+                $tag = 'DIV';
248
+                break;
249
+            case 'DIVORCE_FILED':
250
+                $tag = 'DIVF';
251
+                break;
252
+            case 'EDUCATION':
253
+                $tag = 'EDUC';
254
+                break;
255
+            case 'EMIGRATION':
256
+                $tag = 'EMIG';
257
+                break;
258
+            case 'ENDOWMENT':
259
+                $tag = 'ENDL';
260
+                break;
261
+            case 'ENGAGEMENT':
262
+                $tag = 'ENGA';
263
+                break;
264
+            case 'EVENT':
265
+                $tag = 'EVEN';
266
+                break;
267
+            case 'FACSIMILE':
268
+                $tag = 'FAX';
269
+                break;
270
+            case 'FAMILY':
271
+                $tag = 'FAM';
272
+                break;
273
+            case 'FAMILY_CHILD':
274
+                $tag = 'FAMC';
275
+                break;
276
+            case 'FAMILY_FILE':
277
+                $tag = 'FAMF';
278
+                break;
279
+            case 'FAMILY_SPOUSE':
280
+                $tag = 'FAMS';
281
+                break;
282
+            case 'FIRST_COMMUNION':
283
+                $tag = 'FCOM';
284
+                break;
285
+            case '_FILE':
286
+                $tag = 'FILE';
287
+                break;
288
+            case 'FORMAT':
289
+                $tag = 'FORM';
290
+            case 'FORM':
291
+                // Consistent commas
292
+                $data = preg_replace('/ *, */', ', ', $data);
293
+                break;
294
+            case 'GEDCOM':
295
+                $tag = 'GEDC';
296
+                break;
297
+            case 'GIVEN_NAME':
298
+                $tag = 'GIVN';
299
+                break;
300
+            case 'GRADUATION':
301
+                $tag = 'GRAD';
302
+                break;
303
+            case 'HEADER':
304
+                $tag = 'HEAD';
305
+            case 'HEAD':
306
+                // HEAD records don't have an XREF or DATA
307
+                if ($level == '0') {
308
+                    $xref = '';
309
+                    $data = '';
310
+                }
311
+                break;
312
+            case 'HUSBAND':
313
+                $tag = 'HUSB';
314
+                break;
315
+            case 'IDENT_NUMBER':
316
+                $tag = 'IDNO';
317
+                break;
318
+            case 'IMMIGRATION':
319
+                $tag = 'IMMI';
320
+                break;
321
+            case 'INDIVIDUAL':
322
+                $tag = 'INDI';
323
+                break;
324
+            case 'LANGUAGE':
325
+                $tag = 'LANG';
326
+                break;
327
+            case 'LATITUDE':
328
+                $tag = 'LATI';
329
+                break;
330
+            case 'LONGITUDE':
331
+                $tag = 'LONG';
332
+                break;
333
+            case 'MARRIAGE':
334
+                $tag = 'MARR';
335
+                break;
336
+            case 'MARRIAGE_BANN':
337
+                $tag = 'MARB';
338
+                break;
339
+            case 'MARRIAGE_COUNT':
340
+                $tag = 'NMR';
341
+                break;
342
+            case 'MARRIAGE_CONTRACT':
343
+                $tag = 'MARC';
344
+                break;
345
+            case 'MARRIAGE_LICENSE':
346
+                $tag = 'MARL';
347
+                break;
348
+            case 'MARRIAGE_SETTLEMENT':
349
+                $tag = 'MARS';
350
+                break;
351
+            case 'MEDIA':
352
+                $tag = 'MEDI';
353
+                break;
354
+            case '_MEDICAL':
355
+                $tag = '_MDCL';
356
+                break;
357
+            case '_MILITARY_SERVICE':
358
+                $tag = '_MILT';
359
+                break;
360
+            case 'NAME':
361
+                // Tidy up whitespace
362
+                $data = preg_replace('/  +/', ' ', trim($data));
363
+                break;
364
+            case 'NAME_PREFIX':
365
+                $tag = 'NPFX';
366
+                break;
367
+            case 'NAME_SUFFIX':
368
+                $tag = 'NSFX';
369
+                break;
370
+            case 'NATIONALITY':
371
+                $tag = 'NATI';
372
+                break;
373
+            case 'NATURALIZATION':
374
+                $tag = 'NATU';
375
+                break;
376
+            case 'NICKNAME':
377
+                $tag = 'NICK';
378
+                break;
379
+            case 'OBJECT':
380
+                $tag = 'OBJE';
381
+                break;
382
+            case 'OCCUPATION':
383
+                $tag = 'OCCU';
384
+                break;
385
+            case 'ORDINANCE':
386
+                $tag = 'ORDI';
387
+                break;
388
+            case 'ORDINATION':
389
+                $tag = 'ORDN';
390
+                break;
391
+            case 'PEDIGREE':
392
+                $tag = 'PEDI';
393
+            case 'PEDI':
394
+                // PEDI values are lower case
395
+                $data = strtolower($data);
396
+                break;
397
+            case 'PHONE':
398
+                $tag = 'PHON';
399
+                break;
400
+            case 'PHONETIC':
401
+                $tag = 'FONE';
402
+                break;
403
+            case 'PHY_DESCRIPTION':
404
+                $tag = 'DSCR';
405
+                break;
406
+            case 'PLACE':
407
+                $tag = 'PLAC';
408
+            case 'PLAC':
409
+                // Consistent commas
410
+                $data = preg_replace('/ *(،|,) */', ', ', $data);
411
+                // The Master Genealogist stores LAT/LONG data in the PLAC field, e.g. Pennsylvania, USA, 395945N0751013W
412
+                if (preg_match('/(.*), (\d\d)(\d\d)(\d\d)([NS])(\d\d\d)(\d\d)(\d\d)([EW])$/', $data, $match)) {
413
+                    $data =
414
+                        $match[1] . "\n" .
415
+                        ($level + 1) . " MAP\n" .
416
+                        ($level + 2) . " LATI " . ($match[5] . (round($match[2] + ($match[3] / 60) + ($match[4] / 3600), 4))) . "\n" .
417
+                        ($level + 2) . " LONG " . ($match[9] . (round($match[6] + ($match[7] / 60) + ($match[8] / 3600), 4)));
418
+                }
419
+                break;
420
+            case 'POSTAL_CODE':
421
+                $tag = 'POST';
422
+                break;
423
+            case 'PROBATE':
424
+                $tag = 'PROB';
425
+                break;
426
+            case 'PROPERTY':
427
+                $tag = 'PROP';
428
+                break;
429
+            case 'PUBLICATION':
430
+                $tag = 'PUBL';
431
+                break;
432
+            case 'QUALITY_OF_DATA':
433
+                $tag = 'QUAL';
434
+                break;
435
+            case 'REC_FILE_NUMBER':
436
+                $tag = 'RFN';
437
+                break;
438
+            case 'REC_ID_NUMBER':
439
+                $tag = 'RIN';
440
+                break;
441
+            case 'REFERENCE':
442
+                $tag = 'REFN';
443
+                break;
444
+            case 'RELATIONSHIP':
445
+                $tag = 'RELA';
446
+                break;
447
+            case 'RELIGION':
448
+                $tag = 'RELI';
449
+                break;
450
+            case 'REPOSITORY':
451
+                $tag = 'REPO';
452
+                break;
453
+            case 'RESIDENCE':
454
+                $tag = 'RESI';
455
+                break;
456
+            case 'RESTRICTION':
457
+                $tag = 'RESN';
458
+            case 'RESN':
459
+                // RESN values are lower case (confidential, privacy, locked, none)
460
+                $data = strtolower($data);
461
+                if ($data == 'invisible') {
462
+                    $data = 'confidential'; // From old versions of Legacy.
463
+                }
464
+                break;
465
+            case 'RETIREMENT':
466
+                $tag = 'RETI';
467
+                break;
468
+            case 'ROMANIZED':
469
+                $tag = 'ROMN';
470
+                break;
471
+            case 'SEALING_CHILD':
472
+                $tag = 'SLGC';
473
+                break;
474
+            case 'SEALING_SPOUSE':
475
+                $tag = 'SLGS';
476
+                break;
477
+            case 'SOC_SEC_NUMBER':
478
+                $tag = 'SSN';
479
+                break;
480
+            case 'SEX':
481
+                $data = strtoupper($data);
482
+                break;
483
+            case 'SOURCE':
484
+                $tag = 'SOUR';
485
+                break;
486
+            case 'STATE':
487
+                $tag = 'STAE';
488
+                break;
489
+            case 'STATUS':
490
+                $tag = 'STAT';
491
+            case 'STAT':
492
+                if ($data == 'CANCELLED') {
493
+                    // PhpGedView mis-spells this tag - correct it.
494
+                    $data = 'CANCELED';
495
+                }
496
+                break;
497
+            case 'SUBMISSION':
498
+                $tag = 'SUBN';
499
+                break;
500
+            case 'SUBMITTER':
501
+                $tag = 'SUBM';
502
+                break;
503
+            case 'SURNAME':
504
+                $tag = 'SURN';
505
+                break;
506
+            case 'SURN_PREFIX':
507
+                $tag = 'SPFX';
508
+                break;
509
+            case 'TEMPLE':
510
+                $tag = 'TEMP';
511
+            case 'TEMP':
512
+                // Temple codes are upper case
513
+                $data = strtoupper($data);
514
+                break;
515
+            case 'TITLE':
516
+                $tag = 'TITL';
517
+                break;
518
+            case 'TRAILER':
519
+                $tag = 'TRLR';
520
+            case 'TRLR':
521
+                // TRLR records don't have an XREF or DATA
522
+                if ($level == '0') {
523
+                    $xref = '';
524
+                    $data = '';
525
+                }
526
+                break;
527
+            case 'VERSION':
528
+                $tag = 'VERS';
529
+                break;
530
+            case 'WEB':
531
+                $tag = 'WWW';
532
+                break;
533
+            }
534
+            // Suppress "Y", for facts/events with a DATE or PLAC
535
+            if ($data == 'y') {
536
+                $data = 'Y';
537
+            }
538
+            if ($level == '1' && $data == 'Y') {
539
+                for ($i = $n + 1; $i < $num_matches - 1 && $matches[$i][1] != '1'; ++$i) {
540
+                    if ($matches[$i][3] == 'DATE' || $matches[$i][3] == 'PLAC') {
541
+                        $data = '';
542
+                        break;
543
+                    }
544
+                }
545
+            }
546
+            // Reassemble components back into a single line
547
+            switch ($tag) {
548
+            default:
549
+                // Remove tabs and multiple/leading/trailing spaces
550
+                if (strpos($data, "\t") !== false) {
551
+                    $data = str_replace("\t", ' ', $data);
552
+                }
553
+                if (substr($data, 0, 1) == ' ' || substr($data, -1, 1) == ' ') {
554
+                    $data = trim($data);
555
+                }
556
+                while (strpos($data, '  ')) {
557
+                    $data = str_replace('  ', ' ', $data);
558
+                }
559
+                $newrec .= ($newrec ? "\n" : '') . $level . ' ' . ($level == '0' && $xref ? $xref . ' ' : '') . $tag . ($data === '' && $tag != "NOTE" ? '' : ' ' . $data);
560
+                break;
561
+            case 'NOTE':
562
+            case 'TEXT':
563
+            case 'DATA':
564
+            case 'CONT':
565
+                $newrec .= ($newrec ? "\n" : '') . $level . ' ' . ($level == '0' && $xref ? $xref . ' ' : '') . $tag . ($data === '' && $tag != "NOTE" ? '' : ' ' . $data);
566
+                break;
567
+            case 'FILE':
568
+                // Strip off the user-defined path prefix
569
+                $GEDCOM_MEDIA_PATH = $tree->getPreference('GEDCOM_MEDIA_PATH');
570
+                if ($GEDCOM_MEDIA_PATH && strpos($data, $GEDCOM_MEDIA_PATH) === 0) {
571
+                    $data = substr($data, strlen($GEDCOM_MEDIA_PATH));
572
+                }
573
+                // convert backslashes in filenames to forward slashes
574
+                $data = preg_replace("/\\\/", "/", $data);
575 575
 
576
-				$newrec .= ($newrec ? "\n" : '') . $level . ' ' . ($level == '0' && $xref ? $xref . ' ' : '') . $tag . ($data === '' && $tag != "NOTE" ? '' : ' ' . $data);
577
-				break;
578
-			case 'CONC':
579
-				// Merge CONC lines, to simplify access later on.
580
-				$newrec .= ($tree->getPreference('WORD_WRAPPED_NOTES') ? ' ' : '') . $data;
581
-				break;
582
-			}
583
-		}
576
+                $newrec .= ($newrec ? "\n" : '') . $level . ' ' . ($level == '0' && $xref ? $xref . ' ' : '') . $tag . ($data === '' && $tag != "NOTE" ? '' : ' ' . $data);
577
+                break;
578
+            case 'CONC':
579
+                // Merge CONC lines, to simplify access later on.
580
+                $newrec .= ($tree->getPreference('WORD_WRAPPED_NOTES') ? ' ' : '') . $data;
581
+                break;
582
+            }
583
+        }
584 584
 
585
-		return $newrec;
586
-	}
585
+        return $newrec;
586
+    }
587 587
 
588
-	/**
589
-	 * import record into database
590
-	 *
591
-	 * this function will parse the given gedcom record and add it to the database
592
-	 *
593
-	 * @param string $gedrec the raw gedcom record to parse
594
-	 * @param Tree   $tree   import the record into this tree
595
-	 * @param bool   $update whether or not this is an updated record that has been accepted
596
-	 */
597
-	public static function importRecord($gedrec, Tree $tree, $update) {
598
-		$tree_id = $tree->getTreeId();
588
+    /**
589
+     * import record into database
590
+     *
591
+     * this function will parse the given gedcom record and add it to the database
592
+     *
593
+     * @param string $gedrec the raw gedcom record to parse
594
+     * @param Tree   $tree   import the record into this tree
595
+     * @param bool   $update whether or not this is an updated record that has been accepted
596
+     */
597
+    public static function importRecord($gedrec, Tree $tree, $update) {
598
+        $tree_id = $tree->getTreeId();
599 599
 
600
-		// Escaped @ signs (only if importing from file)
601
-		if (!$update) {
602
-			$gedrec = str_replace('@@', '@', $gedrec);
603
-		}
600
+        // Escaped @ signs (only if importing from file)
601
+        if (!$update) {
602
+            $gedrec = str_replace('@@', '@', $gedrec);
603
+        }
604 604
 
605
-		// Standardise gedcom format
606
-		$gedrec = self::reformatRecord($gedrec, $tree);
605
+        // Standardise gedcom format
606
+        $gedrec = self::reformatRecord($gedrec, $tree);
607 607
 
608
-		// import different types of records
609
-		if (preg_match('/^0 @(' . WT_REGEX_XREF . ')@ (' . WT_REGEX_TAG . ')/', $gedrec, $match)) {
610
-			list(, $xref, $type) = $match;
611
-			// check for a _UID, if the record doesn't have one, add one
612
-			if ($tree->getPreference('GENERATE_UIDS') && !strpos($gedrec, "\n1 _UID ")) {
613
-				$gedrec .= "\n1 _UID " . GedcomTag::createUid();
614
-			}
615
-		} elseif (preg_match('/0 (HEAD|TRLR)/', $gedrec, $match)) {
616
-			$type = $match[1];
617
-			$xref = $type; // For HEAD/TRLR, use type as pseudo XREF.
618
-		} else {
619
-			echo I18N::translate('Invalid GEDCOM format'), '<br><pre>', $gedrec, '</pre>';
608
+        // import different types of records
609
+        if (preg_match('/^0 @(' . WT_REGEX_XREF . ')@ (' . WT_REGEX_TAG . ')/', $gedrec, $match)) {
610
+            list(, $xref, $type) = $match;
611
+            // check for a _UID, if the record doesn't have one, add one
612
+            if ($tree->getPreference('GENERATE_UIDS') && !strpos($gedrec, "\n1 _UID ")) {
613
+                $gedrec .= "\n1 _UID " . GedcomTag::createUid();
614
+            }
615
+        } elseif (preg_match('/0 (HEAD|TRLR)/', $gedrec, $match)) {
616
+            $type = $match[1];
617
+            $xref = $type; // For HEAD/TRLR, use type as pseudo XREF.
618
+        } else {
619
+            echo I18N::translate('Invalid GEDCOM format'), '<br><pre>', $gedrec, '</pre>';
620 620
 
621
-			return;
622
-		}
621
+            return;
622
+        }
623 623
 
624
-		// If the user has downloaded their GEDCOM data (containing media objects) and edited it
625
-		// using an application which does not support (and deletes) media objects, then add them
626
-		// back in.
627
-		if ($tree->getPreference('keep_media') && $xref) {
628
-			$old_linked_media =
629
-				Database::prepare("SELECT l_to FROM `##link` WHERE l_from=? AND l_file=? AND l_type='OBJE'")
630
-					->execute(array($xref, $tree_id))
631
-					->fetchOneColumn();
632
-			foreach ($old_linked_media as $media_id) {
633
-				$gedrec .= "\n1 OBJE @" . $media_id . "@";
634
-			}
635
-		}
624
+        // If the user has downloaded their GEDCOM data (containing media objects) and edited it
625
+        // using an application which does not support (and deletes) media objects, then add them
626
+        // back in.
627
+        if ($tree->getPreference('keep_media') && $xref) {
628
+            $old_linked_media =
629
+                Database::prepare("SELECT l_to FROM `##link` WHERE l_from=? AND l_file=? AND l_type='OBJE'")
630
+                    ->execute(array($xref, $tree_id))
631
+                    ->fetchOneColumn();
632
+            foreach ($old_linked_media as $media_id) {
633
+                $gedrec .= "\n1 OBJE @" . $media_id . "@";
634
+            }
635
+        }
636 636
 
637
-		switch ($type) {
638
-		case 'INDI':
639
-			// Convert inline media into media objects
640
-			$gedrec = self::convertInlineMedia($tree, $gedrec);
637
+        switch ($type) {
638
+        case 'INDI':
639
+            // Convert inline media into media objects
640
+            $gedrec = self::convertInlineMedia($tree, $gedrec);
641 641
 
642
-			$record = new Individual($xref, $gedrec, null, $tree);
643
-			if (preg_match('/\n1 RIN (.+)/', $gedrec, $match)) {
644
-				$rin = $match[1];
645
-			} else {
646
-				$rin = $xref;
647
-			}
648
-			Database::prepare(
649
-				"INSERT INTO `##individuals` (i_id, i_file, i_rin, i_sex, i_gedcom) VALUES (?, ?, ?, ?, ?)"
650
-			)->execute(array(
651
-				$xref, $tree_id, $rin, $record->getSex(), $gedrec,
652
-			));
653
-			// Update the cross-reference/index tables.
654
-			self::updatePlaces($xref, $tree_id, $gedrec);
655
-			self::updateDates($xref, $tree_id, $gedrec);
656
-			self::updateLinks($xref, $tree_id, $gedrec);
657
-			self::updateNames($xref, $tree_id, $record);
658
-			break;
659
-		case 'FAM':
660
-			// Convert inline media into media objects
661
-			$gedrec = self::convertInlineMedia($tree, $gedrec);
642
+            $record = new Individual($xref, $gedrec, null, $tree);
643
+            if (preg_match('/\n1 RIN (.+)/', $gedrec, $match)) {
644
+                $rin = $match[1];
645
+            } else {
646
+                $rin = $xref;
647
+            }
648
+            Database::prepare(
649
+                "INSERT INTO `##individuals` (i_id, i_file, i_rin, i_sex, i_gedcom) VALUES (?, ?, ?, ?, ?)"
650
+            )->execute(array(
651
+                $xref, $tree_id, $rin, $record->getSex(), $gedrec,
652
+            ));
653
+            // Update the cross-reference/index tables.
654
+            self::updatePlaces($xref, $tree_id, $gedrec);
655
+            self::updateDates($xref, $tree_id, $gedrec);
656
+            self::updateLinks($xref, $tree_id, $gedrec);
657
+            self::updateNames($xref, $tree_id, $record);
658
+            break;
659
+        case 'FAM':
660
+            // Convert inline media into media objects
661
+            $gedrec = self::convertInlineMedia($tree, $gedrec);
662 662
 
663
-			if (preg_match('/\n1 HUSB @(' . WT_REGEX_XREF . ')@/', $gedrec, $match)) {
664
-				$husb = $match[1];
665
-			} else {
666
-				$husb = '';
667
-			}
668
-			if (preg_match('/\n1 WIFE @(' . WT_REGEX_XREF . ')@/', $gedrec, $match)) {
669
-				$wife = $match[1];
670
-			} else {
671
-				$wife = '';
672
-			}
673
-			$nchi = preg_match_all('/\n1 CHIL @(' . WT_REGEX_XREF . ')@/', $gedrec, $match);
674
-			if (preg_match('/\n1 NCHI (\d+)/', $gedrec, $match)) {
675
-				$nchi = max($nchi, $match[1]);
676
-			}
677
-			Database::prepare(
678
-				"INSERT INTO `##families` (f_id, f_file, f_husb, f_wife, f_gedcom, f_numchil) VALUES (?, ?, ?, ?, ?, ?)"
679
-			)->execute(array(
680
-				$xref, $tree_id, $husb, $wife, $gedrec, $nchi,
681
-			));
682
-			// Update the cross-reference/index tables.
683
-			self::updatePlaces($xref, $tree_id, $gedrec);
684
-			self::updateDates($xref, $tree_id, $gedrec);
685
-			self::updateLinks($xref, $tree_id, $gedrec);
686
-			break;
687
-		case 'SOUR':
688
-			// Convert inline media into media objects
689
-			$gedrec = self::convertInlineMedia($tree, $gedrec);
663
+            if (preg_match('/\n1 HUSB @(' . WT_REGEX_XREF . ')@/', $gedrec, $match)) {
664
+                $husb = $match[1];
665
+            } else {
666
+                $husb = '';
667
+            }
668
+            if (preg_match('/\n1 WIFE @(' . WT_REGEX_XREF . ')@/', $gedrec, $match)) {
669
+                $wife = $match[1];
670
+            } else {
671
+                $wife = '';
672
+            }
673
+            $nchi = preg_match_all('/\n1 CHIL @(' . WT_REGEX_XREF . ')@/', $gedrec, $match);
674
+            if (preg_match('/\n1 NCHI (\d+)/', $gedrec, $match)) {
675
+                $nchi = max($nchi, $match[1]);
676
+            }
677
+            Database::prepare(
678
+                "INSERT INTO `##families` (f_id, f_file, f_husb, f_wife, f_gedcom, f_numchil) VALUES (?, ?, ?, ?, ?, ?)"
679
+            )->execute(array(
680
+                $xref, $tree_id, $husb, $wife, $gedrec, $nchi,
681
+            ));
682
+            // Update the cross-reference/index tables.
683
+            self::updatePlaces($xref, $tree_id, $gedrec);
684
+            self::updateDates($xref, $tree_id, $gedrec);
685
+            self::updateLinks($xref, $tree_id, $gedrec);
686
+            break;
687
+        case 'SOUR':
688
+            // Convert inline media into media objects
689
+            $gedrec = self::convertInlineMedia($tree, $gedrec);
690 690
 
691
-			$record = new Source($xref, $gedrec, null, $tree);
692
-			if (preg_match('/\n1 TITL (.+)/', $gedrec, $match)) {
693
-				$name = $match[1];
694
-			} elseif (preg_match('/\n1 ABBR (.+)/', $gedrec, $match)) {
695
-				$name = $match[1];
696
-			} else {
697
-				$name = $xref;
698
-			}
699
-			Database::prepare(
700
-				"INSERT INTO `##sources` (s_id, s_file, s_name, s_gedcom) VALUES (?, ?, LEFT(?, 255), ?)"
701
-			)->execute(array(
702
-				$xref, $tree_id, $name, $gedrec,
703
-			));
704
-			// Update the cross-reference/index tables.
705
-			self::updateLinks($xref, $tree_id, $gedrec);
706
-			self::updateNames($xref, $tree_id, $record);
707
-			break;
708
-		case 'REPO':
709
-			// Convert inline media into media objects
710
-			$gedrec = self::convertInlineMedia($tree, $gedrec);
691
+            $record = new Source($xref, $gedrec, null, $tree);
692
+            if (preg_match('/\n1 TITL (.+)/', $gedrec, $match)) {
693
+                $name = $match[1];
694
+            } elseif (preg_match('/\n1 ABBR (.+)/', $gedrec, $match)) {
695
+                $name = $match[1];
696
+            } else {
697
+                $name = $xref;
698
+            }
699
+            Database::prepare(
700
+                "INSERT INTO `##sources` (s_id, s_file, s_name, s_gedcom) VALUES (?, ?, LEFT(?, 255), ?)"
701
+            )->execute(array(
702
+                $xref, $tree_id, $name, $gedrec,
703
+            ));
704
+            // Update the cross-reference/index tables.
705
+            self::updateLinks($xref, $tree_id, $gedrec);
706
+            self::updateNames($xref, $tree_id, $record);
707
+            break;
708
+        case 'REPO':
709
+            // Convert inline media into media objects
710
+            $gedrec = self::convertInlineMedia($tree, $gedrec);
711 711
 
712
-			$record = new Repository($xref, $gedrec, null, $tree);
713
-			Database::prepare(
714
-				"INSERT INTO `##other` (o_id, o_file, o_type, o_gedcom) VALUES (?, ?, 'REPO', ?)"
715
-			)->execute(array(
716
-				$xref, $tree_id, $gedrec,
717
-			));
718
-			// Update the cross-reference/index tables.
719
-			self::updateLinks($xref, $tree_id, $gedrec);
720
-			self::updateNames($xref, $tree_id, $record);
721
-			break;
722
-		case 'NOTE':
723
-			$record = new Note($xref, $gedrec, null, $tree);
724
-			Database::prepare(
725
-				"INSERT INTO `##other` (o_id, o_file, o_type, o_gedcom) VALUES (?, ?, 'NOTE', ?)"
726
-			)->execute(array(
727
-				$xref, $tree_id, $gedrec,
728
-			));
729
-			// Update the cross-reference/index tables.
730
-			self::updateLinks($xref, $tree_id, $gedrec);
731
-			self::updateNames($xref, $tree_id, $record);
732
-			break;
733
-		case 'OBJE':
734
-			$record = new Media($xref, $gedrec, null, $tree);
735
-			Database::prepare(
736
-				"INSERT INTO `##media` (m_id, m_ext, m_type, m_titl, m_filename, m_file, m_gedcom) VALUES (?, LEFT(?, 6), LEFT(?, 60), LEFT(?, 255), left(?, 512), ?, ?)"
737
-			)->execute(array(
738
-				$xref, $record->extension(), $record->getMediaType(), $record->getTitle(), $record->getFilename(), $tree_id, $gedrec,
739
-			));
740
-			// Update the cross-reference/index tables.
741
-			self::updateLinks($xref, $tree_id, $gedrec);
742
-			self::updateNames($xref, $tree_id, $record);
743
-			break;
744
-		default: // HEAD, TRLR, SUBM, SUBN, and custom record types.
745
-			// Force HEAD records to have a creation date.
746
-			if ($type === 'HEAD' && strpos($gedrec, "\n1 DATE ") === false) {
747
-				$gedrec .= "\n1 DATE " . date('j M Y');
748
-			}
749
-			Database::prepare(
750
-				"INSERT INTO `##other` (o_id, o_file, o_type, o_gedcom) VALUES (?, ?, LEFT(?, 15), ?)"
751
-			)->execute(array($xref, $tree_id, $type, $gedrec));
752
-			// Update the cross-reference/index tables.
753
-			self::updateLinks($xref, $tree_id, $gedrec);
754
-			break;
755
-		}
756
-	}
712
+            $record = new Repository($xref, $gedrec, null, $tree);
713
+            Database::prepare(
714
+                "INSERT INTO `##other` (o_id, o_file, o_type, o_gedcom) VALUES (?, ?, 'REPO', ?)"
715
+            )->execute(array(
716
+                $xref, $tree_id, $gedrec,
717
+            ));
718
+            // Update the cross-reference/index tables.
719
+            self::updateLinks($xref, $tree_id, $gedrec);
720
+            self::updateNames($xref, $tree_id, $record);
721
+            break;
722
+        case 'NOTE':
723
+            $record = new Note($xref, $gedrec, null, $tree);
724
+            Database::prepare(
725
+                "INSERT INTO `##other` (o_id, o_file, o_type, o_gedcom) VALUES (?, ?, 'NOTE', ?)"
726
+            )->execute(array(
727
+                $xref, $tree_id, $gedrec,
728
+            ));
729
+            // Update the cross-reference/index tables.
730
+            self::updateLinks($xref, $tree_id, $gedrec);
731
+            self::updateNames($xref, $tree_id, $record);
732
+            break;
733
+        case 'OBJE':
734
+            $record = new Media($xref, $gedrec, null, $tree);
735
+            Database::prepare(
736
+                "INSERT INTO `##media` (m_id, m_ext, m_type, m_titl, m_filename, m_file, m_gedcom) VALUES (?, LEFT(?, 6), LEFT(?, 60), LEFT(?, 255), left(?, 512), ?, ?)"
737
+            )->execute(array(
738
+                $xref, $record->extension(), $record->getMediaType(), $record->getTitle(), $record->getFilename(), $tree_id, $gedrec,
739
+            ));
740
+            // Update the cross-reference/index tables.
741
+            self::updateLinks($xref, $tree_id, $gedrec);
742
+            self::updateNames($xref, $tree_id, $record);
743
+            break;
744
+        default: // HEAD, TRLR, SUBM, SUBN, and custom record types.
745
+            // Force HEAD records to have a creation date.
746
+            if ($type === 'HEAD' && strpos($gedrec, "\n1 DATE ") === false) {
747
+                $gedrec .= "\n1 DATE " . date('j M Y');
748
+            }
749
+            Database::prepare(
750
+                "INSERT INTO `##other` (o_id, o_file, o_type, o_gedcom) VALUES (?, ?, LEFT(?, 15), ?)"
751
+            )->execute(array($xref, $tree_id, $type, $gedrec));
752
+            // Update the cross-reference/index tables.
753
+            self::updateLinks($xref, $tree_id, $gedrec);
754
+            break;
755
+        }
756
+    }
757 757
 
758
-	/**
759
-	 * extract all places from the given record and insert them into the places table
760
-	 *
761
-	 * @param string $gid
762
-	 * @param int    $ged_id
763
-	 * @param string $gedrec
764
-	 */
765
-	public static function updatePlaces($gid, $ged_id, $gedrec) {
766
-		global $placecache;
758
+    /**
759
+     * extract all places from the given record and insert them into the places table
760
+     *
761
+     * @param string $gid
762
+     * @param int    $ged_id
763
+     * @param string $gedrec
764
+     */
765
+    public static function updatePlaces($gid, $ged_id, $gedrec) {
766
+        global $placecache;
767 767
 
768
-		if (!isset($placecache)) {
769
-			$placecache = array();
770
-		}
771
-		$personplace = array();
772
-		// import all place locations, but not control info such as
773
-		// 0 HEAD/1 PLAC or 0 _EVDEF/1 PLAC
774
-		$pt = preg_match_all("/^[2-9] PLAC (.+)/m", $gedrec, $match, PREG_SET_ORDER);
775
-		for ($i = 0; $i < $pt; $i++) {
776
-			$place    = trim($match[$i][1]);
777
-			$lowplace = I18N::strtolower($place);
778
-			//-- if we have already visited this place for this person then we don't need to again
779
-			if (isset($personplace[$lowplace])) {
780
-				continue;
781
-			}
782
-			$personplace[$lowplace] = 1;
783
-			$places                 = explode(',', $place);
784
-			//-- reverse the array to start at the highest level
785
-			$secalp    = array_reverse($places);
786
-			$parent_id = 0;
787
-			$search    = true;
768
+        if (!isset($placecache)) {
769
+            $placecache = array();
770
+        }
771
+        $personplace = array();
772
+        // import all place locations, but not control info such as
773
+        // 0 HEAD/1 PLAC or 0 _EVDEF/1 PLAC
774
+        $pt = preg_match_all("/^[2-9] PLAC (.+)/m", $gedrec, $match, PREG_SET_ORDER);
775
+        for ($i = 0; $i < $pt; $i++) {
776
+            $place    = trim($match[$i][1]);
777
+            $lowplace = I18N::strtolower($place);
778
+            //-- if we have already visited this place for this person then we don't need to again
779
+            if (isset($personplace[$lowplace])) {
780
+                continue;
781
+            }
782
+            $personplace[$lowplace] = 1;
783
+            $places                 = explode(',', $place);
784
+            //-- reverse the array to start at the highest level
785
+            $secalp    = array_reverse($places);
786
+            $parent_id = 0;
787
+            $search    = true;
788 788
 
789
-			foreach ($secalp as $place) {
790
-				$place = trim($place);
791
-				$key   = strtolower(mb_substr($place, 0, 150) . "_" . $parent_id);
792
-				//-- if this place has already been added then we don't need to add it again
793
-				if (isset($placecache[$key])) {
794
-					$parent_id = $placecache[$key];
795
-					if (!isset($personplace[$key])) {
796
-						$personplace[$key] = 1;
797
-						// Use INSERT IGNORE as a (temporary) fix for https://bugs.launchpad.net/webtrees/+bug/582226
798
-						// It ignores places that utf8_unicode_ci consider to be the same (i.e. accents).
799
-						// For example Québec and Quebec
800
-						// We need a better solution that attaches multiple names to single places
801
-						Database::prepare(
802
-							"INSERT IGNORE INTO `##placelinks` (pl_p_id, pl_gid, pl_file) VALUES (?, ?, ?)"
803
-						)->execute(array(
804
-							$parent_id, $gid, $ged_id,
805
-						));
806
-					}
807
-					continue;
808
-				}
789
+            foreach ($secalp as $place) {
790
+                $place = trim($place);
791
+                $key   = strtolower(mb_substr($place, 0, 150) . "_" . $parent_id);
792
+                //-- if this place has already been added then we don't need to add it again
793
+                if (isset($placecache[$key])) {
794
+                    $parent_id = $placecache[$key];
795
+                    if (!isset($personplace[$key])) {
796
+                        $personplace[$key] = 1;
797
+                        // Use INSERT IGNORE as a (temporary) fix for https://bugs.launchpad.net/webtrees/+bug/582226
798
+                        // It ignores places that utf8_unicode_ci consider to be the same (i.e. accents).
799
+                        // For example Québec and Quebec
800
+                        // We need a better solution that attaches multiple names to single places
801
+                        Database::prepare(
802
+                            "INSERT IGNORE INTO `##placelinks` (pl_p_id, pl_gid, pl_file) VALUES (?, ?, ?)"
803
+                        )->execute(array(
804
+                            $parent_id, $gid, $ged_id,
805
+                        ));
806
+                    }
807
+                    continue;
808
+                }
809 809
 
810
-				//-- only search the database while we are finding places in it
811
-				if ($search) {
812
-					//-- check if this place and level has already been added
813
-					$tmp = Database::prepare(
814
-						"SELECT p_id FROM `##places` WHERE p_file = ? AND p_parent_id = ? AND p_place = LEFT(?, 150)"
815
-					)->execute(array(
816
-						$ged_id, $parent_id, $place,
817
-					))->fetchOne();
818
-					if ($tmp) {
819
-						$p_id = $tmp;
820
-					} else {
821
-						$search = false;
822
-					}
823
-				}
810
+                //-- only search the database while we are finding places in it
811
+                if ($search) {
812
+                    //-- check if this place and level has already been added
813
+                    $tmp = Database::prepare(
814
+                        "SELECT p_id FROM `##places` WHERE p_file = ? AND p_parent_id = ? AND p_place = LEFT(?, 150)"
815
+                    )->execute(array(
816
+                        $ged_id, $parent_id, $place,
817
+                    ))->fetchOne();
818
+                    if ($tmp) {
819
+                        $p_id = $tmp;
820
+                    } else {
821
+                        $search = false;
822
+                    }
823
+                }
824 824
 
825
-				//-- if we are not searching then we have to insert the place into the db
826
-				if (!$search) {
827
-					$std_soundex = Soundex::russell($place);
828
-					$dm_soundex  = Soundex::daitchMokotoff($place);
829
-					Database::prepare(
830
-						"INSERT INTO `##places` (p_place, p_parent_id, p_file, p_std_soundex, p_dm_soundex) VALUES (LEFT(?, 150), ?, ?, ?, ?)"
831
-					)->execute(array(
832
-						$place, $parent_id, $ged_id, $std_soundex, $dm_soundex,
833
-					));
834
-					$p_id = Database::getInstance()->lastInsertId();
835
-				}
825
+                //-- if we are not searching then we have to insert the place into the db
826
+                if (!$search) {
827
+                    $std_soundex = Soundex::russell($place);
828
+                    $dm_soundex  = Soundex::daitchMokotoff($place);
829
+                    Database::prepare(
830
+                        "INSERT INTO `##places` (p_place, p_parent_id, p_file, p_std_soundex, p_dm_soundex) VALUES (LEFT(?, 150), ?, ?, ?, ?)"
831
+                    )->execute(array(
832
+                        $place, $parent_id, $ged_id, $std_soundex, $dm_soundex,
833
+                    ));
834
+                    $p_id = Database::getInstance()->lastInsertId();
835
+                }
836 836
 
837
-				Database::prepare(
838
-					"INSERT IGNORE INTO `##placelinks` (pl_p_id, pl_gid, pl_file) VALUES (?, ?, ?)"
839
-				)->execute(array(
840
-					$p_id, $gid, $ged_id,
841
-				));
842
-				//-- increment the level and assign the parent id for the next place level
843
-				$parent_id         = $p_id;
844
-				$placecache[$key]  = $p_id;
845
-				$personplace[$key] = 1;
846
-			}
847
-		}
848
-	}
837
+                Database::prepare(
838
+                    "INSERT IGNORE INTO `##placelinks` (pl_p_id, pl_gid, pl_file) VALUES (?, ?, ?)"
839
+                )->execute(array(
840
+                    $p_id, $gid, $ged_id,
841
+                ));
842
+                //-- increment the level and assign the parent id for the next place level
843
+                $parent_id         = $p_id;
844
+                $placecache[$key]  = $p_id;
845
+                $personplace[$key] = 1;
846
+            }
847
+        }
848
+    }
849 849
 
850
-	/**
851
-	 * Extract all the dates from the given record and insert them into the database.
852
-	 *
853
-	 * @param string $xref
854
-	 * @param int    $ged_id
855
-	 * @param string $gedrec
856
-	 */
857
-	public static function updateDates($xref, $ged_id, $gedrec) {
858
-		if (strpos($gedrec, '2 DATE ') && preg_match_all("/\n1 (\w+).*(?:\n[2-9].*)*(?:\n2 DATE (.+))(?:\n[2-9].*)*/", $gedrec, $matches, PREG_SET_ORDER)) {
859
-			foreach ($matches as $match) {
860
-				$fact = $match[1];
861
-				if (($fact == 'FACT' || $fact == 'EVEN') && preg_match("/\n2 TYPE ([A-Z]{3,5})/", $match[0], $tmatch)) {
862
-					$fact = $tmatch[1];
863
-				}
864
-				$date = new Date($match[2]);
865
-				Database::prepare(
866
-					"INSERT INTO `##dates` (d_day,d_month,d_mon,d_year,d_julianday1,d_julianday2,d_fact,d_gid,d_file,d_type) VALUES (?,?,?,?,?,?,?,?,?,?)"
867
-				)->execute(array(
868
-					$date->minimumDate()->d,
869
-					$date->minimumDate()->format('%O'),
870
-					$date->minimumDate()->m,
871
-					$date->minimumDate()->y,
872
-					$date->minimumDate()->minJD,
873
-					$date->minimumDate()->maxJD,
874
-					$fact,
875
-					$xref,
876
-					$ged_id,
877
-					$date->minimumDate()->format('%@'),
878
-				));
879
-				if ($date->minimumDate() !== $date->maximumDate()) {
880
-					Database::prepare(
881
-						"INSERT INTO `##dates` (d_day,d_month,d_mon,d_year,d_julianday1,d_julianday2,d_fact,d_gid,d_file,d_type) VALUES (?,?,?,?,?,?,?,?,?,?)"
882
-					)->execute(array(
883
-						$date->maximumDate()->d,
884
-						$date->maximumDate()->format('%O'),
885
-						$date->maximumDate()->m,
886
-						$date->maximumDate()->y,
887
-						$date->maximumDate()->minJD,
888
-						$date->maximumDate()->maxJD,
889
-						$fact,
890
-						$xref,
891
-						$ged_id,
892
-						$date->maximumDate()->format('%@'),
893
-					));
894
-				}
895
-			}
896
-		}
897
-	}
850
+    /**
851
+     * Extract all the dates from the given record and insert them into the database.
852
+     *
853
+     * @param string $xref
854
+     * @param int    $ged_id
855
+     * @param string $gedrec
856
+     */
857
+    public static function updateDates($xref, $ged_id, $gedrec) {
858
+        if (strpos($gedrec, '2 DATE ') && preg_match_all("/\n1 (\w+).*(?:\n[2-9].*)*(?:\n2 DATE (.+))(?:\n[2-9].*)*/", $gedrec, $matches, PREG_SET_ORDER)) {
859
+            foreach ($matches as $match) {
860
+                $fact = $match[1];
861
+                if (($fact == 'FACT' || $fact == 'EVEN') && preg_match("/\n2 TYPE ([A-Z]{3,5})/", $match[0], $tmatch)) {
862
+                    $fact = $tmatch[1];
863
+                }
864
+                $date = new Date($match[2]);
865
+                Database::prepare(
866
+                    "INSERT INTO `##dates` (d_day,d_month,d_mon,d_year,d_julianday1,d_julianday2,d_fact,d_gid,d_file,d_type) VALUES (?,?,?,?,?,?,?,?,?,?)"
867
+                )->execute(array(
868
+                    $date->minimumDate()->d,
869
+                    $date->minimumDate()->format('%O'),
870
+                    $date->minimumDate()->m,
871
+                    $date->minimumDate()->y,
872
+                    $date->minimumDate()->minJD,
873
+                    $date->minimumDate()->maxJD,
874
+                    $fact,
875
+                    $xref,
876
+                    $ged_id,
877
+                    $date->minimumDate()->format('%@'),
878
+                ));
879
+                if ($date->minimumDate() !== $date->maximumDate()) {
880
+                    Database::prepare(
881
+                        "INSERT INTO `##dates` (d_day,d_month,d_mon,d_year,d_julianday1,d_julianday2,d_fact,d_gid,d_file,d_type) VALUES (?,?,?,?,?,?,?,?,?,?)"
882
+                    )->execute(array(
883
+                        $date->maximumDate()->d,
884
+                        $date->maximumDate()->format('%O'),
885
+                        $date->maximumDate()->m,
886
+                        $date->maximumDate()->y,
887
+                        $date->maximumDate()->minJD,
888
+                        $date->maximumDate()->maxJD,
889
+                        $fact,
890
+                        $xref,
891
+                        $ged_id,
892
+                        $date->maximumDate()->format('%@'),
893
+                    ));
894
+                }
895
+            }
896
+        }
897
+    }
898 898
 
899
-	/**
900
-	 * Extract all the links from the given record and insert them into the database
901
-	 *
902
-	 * @param string $xref
903
-	 * @param int    $ged_id
904
-	 * @param string $gedrec
905
-	 */
906
-	public static function updateLinks($xref, $ged_id, $gedrec) {
907
-		if (preg_match_all('/^\d+ (' . WT_REGEX_TAG . ') @(' . WT_REGEX_XREF . ')@/m', $gedrec, $matches, PREG_SET_ORDER)) {
908
-			$data = array();
909
-			foreach ($matches as $match) {
910
-				// Include each link once only.
911
-				if (!in_array($match[1] . $match[2], $data)) {
912
-					$data[] = $match[1] . $match[2];
913
-					// Ignore any errors, which may be caused by "duplicates" that differ on case/collation, e.g. "S1" and "s1"
914
-					try {
915
-						Database::prepare(
916
-							"INSERT INTO `##link` (l_from, l_to, l_type, l_file) VALUES (?, ?, ?, ?)"
917
-						)->execute(array(
918
-							$xref, $match[2], $match[1], $ged_id,
919
-						));
920
-					} catch (PDOException $e) {
921
-						// We could display a warning here....
922
-					}
923
-				}
924
-			}
925
-		}
926
-	}
899
+    /**
900
+     * Extract all the links from the given record and insert them into the database
901
+     *
902
+     * @param string $xref
903
+     * @param int    $ged_id
904
+     * @param string $gedrec
905
+     */
906
+    public static function updateLinks($xref, $ged_id, $gedrec) {
907
+        if (preg_match_all('/^\d+ (' . WT_REGEX_TAG . ') @(' . WT_REGEX_XREF . ')@/m', $gedrec, $matches, PREG_SET_ORDER)) {
908
+            $data = array();
909
+            foreach ($matches as $match) {
910
+                // Include each link once only.
911
+                if (!in_array($match[1] . $match[2], $data)) {
912
+                    $data[] = $match[1] . $match[2];
913
+                    // Ignore any errors, which may be caused by "duplicates" that differ on case/collation, e.g. "S1" and "s1"
914
+                    try {
915
+                        Database::prepare(
916
+                            "INSERT INTO `##link` (l_from, l_to, l_type, l_file) VALUES (?, ?, ?, ?)"
917
+                        )->execute(array(
918
+                            $xref, $match[2], $match[1], $ged_id,
919
+                        ));
920
+                    } catch (PDOException $e) {
921
+                        // We could display a warning here....
922
+                    }
923
+                }
924
+            }
925
+        }
926
+    }
927 927
 
928
-	/**
929
-	 * Extract all the names from the given record and insert them into the database.
930
-	 *
931
-	 * @param string       $xref
932
-	 * @param int          $ged_id
933
-	 * @param GedcomRecord $record
934
-	 */
935
-	public static function updateNames($xref, $ged_id, GedcomRecord $record) {
936
-		foreach ($record->getAllNames() as $n => $name) {
937
-			if ($record instanceof Individual) {
938
-				if ($name['givn'] === '@P.N.') {
939
-					$soundex_givn_std = null;
940
-					$soundex_givn_dm  = null;
941
-				} else {
942
-					$soundex_givn_std = Soundex::russell($name['givn']);
943
-					$soundex_givn_dm  = Soundex::daitchMokotoff($name['givn']);
944
-				}
945
-				if ($name['surn'] === '@N.N.') {
946
-					$soundex_surn_std = null;
947
-					$soundex_surn_dm  = null;
948
-				} else {
949
-					$soundex_surn_std = Soundex::russell($name['surname']);
950
-					$soundex_surn_dm  = Soundex::daitchMokotoff($name['surname']);
951
-				}
952
-				Database::prepare(
953
-					"INSERT INTO `##name` (n_file,n_id,n_num,n_type,n_sort,n_full,n_surname,n_surn,n_givn,n_soundex_givn_std,n_soundex_surn_std,n_soundex_givn_dm,n_soundex_surn_dm) VALUES (?, ?, ?, ?, LEFT(?, 255), LEFT(?, 255), LEFT(?, 255), LEFT(?, 255), ?, ?, ?, ?, ?)"
954
-				)->execute(array(
955
-					$ged_id, $xref, $n, $name['type'], $name['sort'], $name['fullNN'], $name['surname'], $name['surn'], $name['givn'], $soundex_givn_std, $soundex_surn_std, $soundex_givn_dm, $soundex_surn_dm,
956
-				));
957
-			} else {
958
-				Database::prepare(
959
-					"INSERT INTO `##name` (n_file,n_id,n_num,n_type,n_sort,n_full) VALUES (?, ?, ?, ?, LEFT(?, 255), LEFT(?, 255))"
960
-				)->execute(array(
961
-					$ged_id, $xref, $n, $name['type'], $name['sort'], $name['fullNN'],
962
-				));
963
-			}
964
-		}
965
-	}
928
+    /**
929
+     * Extract all the names from the given record and insert them into the database.
930
+     *
931
+     * @param string       $xref
932
+     * @param int          $ged_id
933
+     * @param GedcomRecord $record
934
+     */
935
+    public static function updateNames($xref, $ged_id, GedcomRecord $record) {
936
+        foreach ($record->getAllNames() as $n => $name) {
937
+            if ($record instanceof Individual) {
938
+                if ($name['givn'] === '@P.N.') {
939
+                    $soundex_givn_std = null;
940
+                    $soundex_givn_dm  = null;
941
+                } else {
942
+                    $soundex_givn_std = Soundex::russell($name['givn']);
943
+                    $soundex_givn_dm  = Soundex::daitchMokotoff($name['givn']);
944
+                }
945
+                if ($name['surn'] === '@N.N.') {
946
+                    $soundex_surn_std = null;
947
+                    $soundex_surn_dm  = null;
948
+                } else {
949
+                    $soundex_surn_std = Soundex::russell($name['surname']);
950
+                    $soundex_surn_dm  = Soundex::daitchMokotoff($name['surname']);
951
+                }
952
+                Database::prepare(
953
+                    "INSERT INTO `##name` (n_file,n_id,n_num,n_type,n_sort,n_full,n_surname,n_surn,n_givn,n_soundex_givn_std,n_soundex_surn_std,n_soundex_givn_dm,n_soundex_surn_dm) VALUES (?, ?, ?, ?, LEFT(?, 255), LEFT(?, 255), LEFT(?, 255), LEFT(?, 255), ?, ?, ?, ?, ?)"
954
+                )->execute(array(
955
+                    $ged_id, $xref, $n, $name['type'], $name['sort'], $name['fullNN'], $name['surname'], $name['surn'], $name['givn'], $soundex_givn_std, $soundex_surn_std, $soundex_givn_dm, $soundex_surn_dm,
956
+                ));
957
+            } else {
958
+                Database::prepare(
959
+                    "INSERT INTO `##name` (n_file,n_id,n_num,n_type,n_sort,n_full) VALUES (?, ?, ?, ?, LEFT(?, 255), LEFT(?, 255))"
960
+                )->execute(array(
961
+                    $ged_id, $xref, $n, $name['type'], $name['sort'], $name['fullNN'],
962
+                ));
963
+            }
964
+        }
965
+    }
966 966
 
967
-	/**
968
-	 * Extract inline media data, and convert to media objects.
969
-	 *
970
-	 * @param Tree   $tree
971
-	 * @param string $gedrec
972
-	 *
973
-	 * @return string
974
-	 */
975
-	public static function convertInlineMedia(Tree $tree, $gedrec) {
976
-		while (preg_match('/\n1 OBJE(?:\n[2-9].+)+/', $gedrec, $match)) {
977
-			$gedrec = str_replace($match[0], self::createMediaObject(1, $match[0], $tree), $gedrec);
978
-		}
979
-		while (preg_match('/\n2 OBJE(?:\n[3-9].+)+/', $gedrec, $match)) {
980
-			$gedrec = str_replace($match[0], self::createMediaObject(2, $match[0], $tree), $gedrec);
981
-		}
982
-		while (preg_match('/\n3 OBJE(?:\n[4-9].+)+/', $gedrec, $match)) {
983
-			$gedrec = str_replace($match[0], self::createMediaObject(3, $match[0], $tree), $gedrec);
984
-		}
967
+    /**
968
+     * Extract inline media data, and convert to media objects.
969
+     *
970
+     * @param Tree   $tree
971
+     * @param string $gedrec
972
+     *
973
+     * @return string
974
+     */
975
+    public static function convertInlineMedia(Tree $tree, $gedrec) {
976
+        while (preg_match('/\n1 OBJE(?:\n[2-9].+)+/', $gedrec, $match)) {
977
+            $gedrec = str_replace($match[0], self::createMediaObject(1, $match[0], $tree), $gedrec);
978
+        }
979
+        while (preg_match('/\n2 OBJE(?:\n[3-9].+)+/', $gedrec, $match)) {
980
+            $gedrec = str_replace($match[0], self::createMediaObject(2, $match[0], $tree), $gedrec);
981
+        }
982
+        while (preg_match('/\n3 OBJE(?:\n[4-9].+)+/', $gedrec, $match)) {
983
+            $gedrec = str_replace($match[0], self::createMediaObject(3, $match[0], $tree), $gedrec);
984
+        }
985 985
 
986
-		return $gedrec;
987
-	}
986
+        return $gedrec;
987
+    }
988 988
 
989
-	/**
990
-	 * Create a new media object, from inline media data.
991
-	 *
992
-	 * @param int    $level
993
-	 * @param string $gedrec
994
-	 * @param Tree   $tree
995
-	 *
996
-	 * @return string
997
-	 */
998
-	public static function createMediaObject($level, $gedrec, Tree $tree) {
999
-		if (preg_match('/\n\d FILE (.+)/', $gedrec, $file_match)) {
1000
-			$file = $file_match[1];
1001
-		} else {
1002
-			$file = '';
1003
-		}
989
+    /**
990
+     * Create a new media object, from inline media data.
991
+     *
992
+     * @param int    $level
993
+     * @param string $gedrec
994
+     * @param Tree   $tree
995
+     *
996
+     * @return string
997
+     */
998
+    public static function createMediaObject($level, $gedrec, Tree $tree) {
999
+        if (preg_match('/\n\d FILE (.+)/', $gedrec, $file_match)) {
1000
+            $file = $file_match[1];
1001
+        } else {
1002
+            $file = '';
1003
+        }
1004 1004
 
1005
-		if (preg_match('/\n\d TITL (.+)/', $gedrec, $file_match)) {
1006
-			$titl = $file_match[1];
1007
-		} else {
1008
-			$titl = '';
1009
-		}
1005
+        if (preg_match('/\n\d TITL (.+)/', $gedrec, $file_match)) {
1006
+            $titl = $file_match[1];
1007
+        } else {
1008
+            $titl = '';
1009
+        }
1010 1010
 
1011
-		// Have we already created a media object with the same title/filename?
1012
-		$xref = Database::prepare(
1013
-			"SELECT m_id FROM `##media` WHERE m_filename = ? AND m_titl = ? AND m_file = ?"
1014
-		)->execute(array(
1015
-			$file, $titl, $tree->getTreeId(),
1016
-		))->fetchOne();
1011
+        // Have we already created a media object with the same title/filename?
1012
+        $xref = Database::prepare(
1013
+            "SELECT m_id FROM `##media` WHERE m_filename = ? AND m_titl = ? AND m_file = ?"
1014
+        )->execute(array(
1015
+            $file, $titl, $tree->getTreeId(),
1016
+        ))->fetchOne();
1017 1017
 
1018
-		if (!$xref) {
1019
-			$xref = $tree->getNewXref('OBJE');
1020
-			// renumber the lines
1021
-			$gedrec = preg_replace_callback('/\n(\d+)/', function ($m) use ($level) {
1022
-				return "\n" . ($m[1] - $level);
1023
-			}, $gedrec);
1024
-			// convert to an object
1025
-			$gedrec = str_replace("\n0 OBJE\n", '0 @' . $xref . "@ OBJE\n", $gedrec);
1026
-			// Fix Legacy GEDCOMS
1027
-			$gedrec = preg_replace('/\n1 FORM (.+)\n1 FILE (.+)\n1 TITL (.+)/', "\n1 FILE $2\n2 FORM $1\n2 TITL $3", $gedrec);
1028
-			// Fix FTB GEDCOMS
1029
-			$gedrec = preg_replace('/\n1 FORM (.+)\n1 TITL (.+)\n1 FILE (.+)/', "\n1 FILE $3\n2 FORM $1\n2 TITL $2", $gedrec);
1030
-			// Create new record
1031
-			$record = new Media($xref, $gedrec, null, $tree);
1032
-			Database::prepare(
1033
-				"INSERT INTO `##media` (m_id, m_ext, m_type, m_titl, m_filename, m_file, m_gedcom) VALUES (?, ?, ?, ?, ?, ?, ?)"
1034
-			)->execute(array(
1035
-				$xref, $record->extension(), $record->getMediaType(), $record->getTitle(), $record->getFilename(), $tree->getTreeId(), $gedrec,
1036
-			));
1037
-		}
1018
+        if (!$xref) {
1019
+            $xref = $tree->getNewXref('OBJE');
1020
+            // renumber the lines
1021
+            $gedrec = preg_replace_callback('/\n(\d+)/', function ($m) use ($level) {
1022
+                return "\n" . ($m[1] - $level);
1023
+            }, $gedrec);
1024
+            // convert to an object
1025
+            $gedrec = str_replace("\n0 OBJE\n", '0 @' . $xref . "@ OBJE\n", $gedrec);
1026
+            // Fix Legacy GEDCOMS
1027
+            $gedrec = preg_replace('/\n1 FORM (.+)\n1 FILE (.+)\n1 TITL (.+)/', "\n1 FILE $2\n2 FORM $1\n2 TITL $3", $gedrec);
1028
+            // Fix FTB GEDCOMS
1029
+            $gedrec = preg_replace('/\n1 FORM (.+)\n1 TITL (.+)\n1 FILE (.+)/', "\n1 FILE $3\n2 FORM $1\n2 TITL $2", $gedrec);
1030
+            // Create new record
1031
+            $record = new Media($xref, $gedrec, null, $tree);
1032
+            Database::prepare(
1033
+                "INSERT INTO `##media` (m_id, m_ext, m_type, m_titl, m_filename, m_file, m_gedcom) VALUES (?, ?, ?, ?, ?, ?, ?)"
1034
+            )->execute(array(
1035
+                $xref, $record->extension(), $record->getMediaType(), $record->getTitle(), $record->getFilename(), $tree->getTreeId(), $gedrec,
1036
+            ));
1037
+        }
1038 1038
 
1039
-		return "\n" . $level . ' OBJE @' . $xref . '@';
1040
-	}
1039
+        return "\n" . $level . ' OBJE @' . $xref . '@';
1040
+    }
1041 1041
 
1042
-	/**
1043
-	 * Accept all pending changes for a specified record.
1044
-	 *
1045
-	 * @param string $xref
1046
-	 * @param int    $ged_id
1047
-	 */
1048
-	public static function acceptAllChanges($xref, $ged_id) {
1049
-		$changes = Database::prepare(
1050
-			"SELECT change_id, gedcom_name, old_gedcom, new_gedcom" .
1051
-			" FROM `##change` c" .
1052
-			" JOIN `##gedcom` g USING (gedcom_id)" .
1053
-			" WHERE c.status='pending' AND xref=? AND gedcom_id=?" .
1054
-			" ORDER BY change_id"
1055
-		)->execute(array($xref, $ged_id))->fetchAll();
1056
-		foreach ($changes as $change) {
1057
-			if (empty($change->new_gedcom)) {
1058
-				// delete
1059
-				self::updateRecord($change->old_gedcom, $ged_id, true);
1060
-			} else {
1061
-				// add/update
1062
-				self::updateRecord($change->new_gedcom, $ged_id, false);
1063
-			}
1064
-			Database::prepare(
1065
-				"UPDATE `##change`" .
1066
-				" SET status='accepted'" .
1067
-				" WHERE status='pending' AND xref=? AND gedcom_id=?"
1068
-			)->execute(array($xref, $ged_id));
1069
-			Log::addEditLog("Accepted change {$change->change_id} for {$xref} / {$change->gedcom_name} into database");
1070
-		}
1071
-	}
1042
+    /**
1043
+     * Accept all pending changes for a specified record.
1044
+     *
1045
+     * @param string $xref
1046
+     * @param int    $ged_id
1047
+     */
1048
+    public static function acceptAllChanges($xref, $ged_id) {
1049
+        $changes = Database::prepare(
1050
+            "SELECT change_id, gedcom_name, old_gedcom, new_gedcom" .
1051
+            " FROM `##change` c" .
1052
+            " JOIN `##gedcom` g USING (gedcom_id)" .
1053
+            " WHERE c.status='pending' AND xref=? AND gedcom_id=?" .
1054
+            " ORDER BY change_id"
1055
+        )->execute(array($xref, $ged_id))->fetchAll();
1056
+        foreach ($changes as $change) {
1057
+            if (empty($change->new_gedcom)) {
1058
+                // delete
1059
+                self::updateRecord($change->old_gedcom, $ged_id, true);
1060
+            } else {
1061
+                // add/update
1062
+                self::updateRecord($change->new_gedcom, $ged_id, false);
1063
+            }
1064
+            Database::prepare(
1065
+                "UPDATE `##change`" .
1066
+                " SET status='accepted'" .
1067
+                " WHERE status='pending' AND xref=? AND gedcom_id=?"
1068
+            )->execute(array($xref, $ged_id));
1069
+            Log::addEditLog("Accepted change {$change->change_id} for {$xref} / {$change->gedcom_name} into database");
1070
+        }
1071
+    }
1072 1072
 
1073
-	/**
1074
-	 * Accept all pending changes for a specified record.
1075
-	 *
1076
-	 * @param GedcomRecord $record
1077
-	 */
1078
-	public static function rejectAllChanges(GedcomRecord $record) {
1079
-		Database::prepare(
1080
-			"UPDATE `##change`" .
1081
-			" SET status = 'rejected'" .
1082
-			" WHERE status = 'pending' AND xref = :xref AND gedcom_id = :tree_id"
1083
-		)->execute(array(
1084
-			'xref'    => $record->getXref(),
1085
-			'tree_id' => $record->getTree()->getTreeId(),
1086
-		));
1087
-	}
1073
+    /**
1074
+     * Accept all pending changes for a specified record.
1075
+     *
1076
+     * @param GedcomRecord $record
1077
+     */
1078
+    public static function rejectAllChanges(GedcomRecord $record) {
1079
+        Database::prepare(
1080
+            "UPDATE `##change`" .
1081
+            " SET status = 'rejected'" .
1082
+            " WHERE status = 'pending' AND xref = :xref AND gedcom_id = :tree_id"
1083
+        )->execute(array(
1084
+            'xref'    => $record->getXref(),
1085
+            'tree_id' => $record->getTree()->getTreeId(),
1086
+        ));
1087
+    }
1088 1088
 
1089
-	/**
1090
-	 * update a record in the database
1091
-	 *
1092
-	 * @param string $gedrec
1093
-	 * @param int    $ged_id
1094
-	 * @param bool   $delete
1095
-	 */
1096
-	public static function updateRecord($gedrec, $ged_id, $delete) {
1097
-		if (preg_match('/^0 @(' . WT_REGEX_XREF . ')@ (' . WT_REGEX_TAG . ')/', $gedrec, $match)) {
1098
-			list(, $gid, $type) = $match;
1099
-		} elseif (preg_match('/^0 (HEAD)(?:\n|$)/', $gedrec, $match)) {
1100
-			// The HEAD record has no XREF.  Any others?
1101
-			$gid  = $match[1];
1102
-			$type = $match[1];
1103
-		} else {
1104
-			echo "ERROR: Invalid gedcom record.";
1089
+    /**
1090
+     * update a record in the database
1091
+     *
1092
+     * @param string $gedrec
1093
+     * @param int    $ged_id
1094
+     * @param bool   $delete
1095
+     */
1096
+    public static function updateRecord($gedrec, $ged_id, $delete) {
1097
+        if (preg_match('/^0 @(' . WT_REGEX_XREF . ')@ (' . WT_REGEX_TAG . ')/', $gedrec, $match)) {
1098
+            list(, $gid, $type) = $match;
1099
+        } elseif (preg_match('/^0 (HEAD)(?:\n|$)/', $gedrec, $match)) {
1100
+            // The HEAD record has no XREF.  Any others?
1101
+            $gid  = $match[1];
1102
+            $type = $match[1];
1103
+        } else {
1104
+            echo "ERROR: Invalid gedcom record.";
1105 1105
 
1106
-			return;
1107
-		}
1106
+            return;
1107
+        }
1108 1108
 
1109
-		// TODO deleting unlinked places can be done more efficiently in a single query
1110
-		$placeids =
1111
-			Database::prepare("SELECT pl_p_id FROM `##placelinks` WHERE pl_gid=? AND pl_file=?")
1112
-				->execute(array($gid, $ged_id))
1113
-				->fetchOneColumn();
1109
+        // TODO deleting unlinked places can be done more efficiently in a single query
1110
+        $placeids =
1111
+            Database::prepare("SELECT pl_p_id FROM `##placelinks` WHERE pl_gid=? AND pl_file=?")
1112
+                ->execute(array($gid, $ged_id))
1113
+                ->fetchOneColumn();
1114 1114
 
1115
-		Database::prepare("DELETE FROM `##placelinks` WHERE pl_gid=? AND pl_file=?")->execute(array($gid, $ged_id));
1116
-		Database::prepare("DELETE FROM `##dates`      WHERE d_gid =? AND d_file =?")->execute(array($gid, $ged_id));
1115
+        Database::prepare("DELETE FROM `##placelinks` WHERE pl_gid=? AND pl_file=?")->execute(array($gid, $ged_id));
1116
+        Database::prepare("DELETE FROM `##dates`      WHERE d_gid =? AND d_file =?")->execute(array($gid, $ged_id));
1117 1117
 
1118
-		//-- delete any unlinked places
1119
-		foreach ($placeids as $p_id) {
1120
-			$num =
1121
-				Database::prepare("SELECT count(pl_p_id) FROM `##placelinks` WHERE pl_p_id=? AND pl_file=?")
1122
-					->execute(array($p_id, $ged_id))
1123
-					->fetchOne();
1124
-			if ($num == 0) {
1125
-				Database::prepare("DELETE FROM `##places` WHERE p_id=? AND p_file=?")->execute(array($p_id, $ged_id));
1126
-			}
1127
-		}
1118
+        //-- delete any unlinked places
1119
+        foreach ($placeids as $p_id) {
1120
+            $num =
1121
+                Database::prepare("SELECT count(pl_p_id) FROM `##placelinks` WHERE pl_p_id=? AND pl_file=?")
1122
+                    ->execute(array($p_id, $ged_id))
1123
+                    ->fetchOne();
1124
+            if ($num == 0) {
1125
+                Database::prepare("DELETE FROM `##places` WHERE p_id=? AND p_file=?")->execute(array($p_id, $ged_id));
1126
+            }
1127
+        }
1128 1128
 
1129
-		Database::prepare("DELETE FROM `##name` WHERE n_id=? AND n_file=?")->execute(array($gid, $ged_id));
1130
-		Database::prepare("DELETE FROM `##link` WHERE l_from=? AND l_file=?")->execute(array($gid, $ged_id));
1129
+        Database::prepare("DELETE FROM `##name` WHERE n_id=? AND n_file=?")->execute(array($gid, $ged_id));
1130
+        Database::prepare("DELETE FROM `##link` WHERE l_from=? AND l_file=?")->execute(array($gid, $ged_id));
1131 1131
 
1132
-		switch ($type) {
1133
-		case 'INDI':
1134
-			Database::prepare("DELETE FROM `##individuals` WHERE i_id=? AND i_file=?")->execute(array($gid, $ged_id));
1135
-			break;
1136
-		case 'FAM':
1137
-			Database::prepare("DELETE FROM `##families` WHERE f_id=? AND f_file=?")->execute(array($gid, $ged_id));
1138
-			break;
1139
-		case 'SOUR':
1140
-			Database::prepare("DELETE FROM `##sources` WHERE s_id=? AND s_file=?")->execute(array($gid, $ged_id));
1141
-			break;
1142
-		case 'OBJE':
1143
-			Database::prepare("DELETE FROM `##media` WHERE m_id=? AND m_file=?")->execute(array($gid, $ged_id));
1144
-			break;
1145
-		default:
1146
-			Database::prepare("DELETE FROM `##other` WHERE o_id=? AND o_file=?")->execute(array($gid, $ged_id));
1147
-			break;
1148
-		}
1132
+        switch ($type) {
1133
+        case 'INDI':
1134
+            Database::prepare("DELETE FROM `##individuals` WHERE i_id=? AND i_file=?")->execute(array($gid, $ged_id));
1135
+            break;
1136
+        case 'FAM':
1137
+            Database::prepare("DELETE FROM `##families` WHERE f_id=? AND f_file=?")->execute(array($gid, $ged_id));
1138
+            break;
1139
+        case 'SOUR':
1140
+            Database::prepare("DELETE FROM `##sources` WHERE s_id=? AND s_file=?")->execute(array($gid, $ged_id));
1141
+            break;
1142
+        case 'OBJE':
1143
+            Database::prepare("DELETE FROM `##media` WHERE m_id=? AND m_file=?")->execute(array($gid, $ged_id));
1144
+            break;
1145
+        default:
1146
+            Database::prepare("DELETE FROM `##other` WHERE o_id=? AND o_file=?")->execute(array($gid, $ged_id));
1147
+            break;
1148
+        }
1149 1149
 
1150
-		if (!$delete) {
1151
-			self::importRecord($gedrec, Tree::findById($ged_id), true);
1152
-		}
1153
-	}
1150
+        if (!$delete) {
1151
+            self::importRecord($gedrec, Tree::findById($ged_id), true);
1152
+        }
1153
+    }
1154 1154
 }
Please login to merge, or discard this patch.
Switch Indentation   +632 added lines, -632 removed lines patch added patch discarded remove patch
@@ -59,477 +59,477 @@  discard block
 block discarded – undo
59 59
 			$tag                               = strtoupper($tag); // Tags should always be upper case
60 60
 			switch ($tag) {
61 61
 				// Convert PhpGedView tags to WT
62
-			case '_PGVU':
63
-				$tag = '_WT_USER';
64
-				break;
65
-			case '_PGV_OBJS':
66
-				$tag = '_WT_OBJE_SORT';
67
-				break;
68
-				// Convert FTM-style "TAG_FORMAL_NAME" into "TAG".
69
-			case 'ABBREVIATION':
70
-				$tag = 'ABBR';
71
-				break;
72
-			case 'ADDRESS':
73
-				$tag = 'ADDR';
74
-				break;
75
-			case 'ADDRESS1':
76
-				$tag = 'ADR1';
77
-				break;
78
-			case 'ADDRESS2':
79
-				$tag = 'ADR2';
80
-				break;
81
-			case 'ADDRESS3':
82
-				$tag = 'ADR3';
83
-				break;
84
-			case 'ADOPTION':
85
-				$tag = 'ADOP';
86
-				break;
87
-			case 'ADULT_CHRISTENING':
88
-				$tag = 'CHRA';
89
-				break;
90
-			case 'AFN':
91
-				// AFN values are upper case
92
-				$data = strtoupper($data);
93
-				break;
94
-			case 'AGENCY':
95
-				$tag = 'AGNC';
96
-				break;
97
-			case 'ALIAS':
98
-				$tag = 'ALIA';
99
-				break;
100
-			case 'ANCESTORS':
101
-				$tag = 'ANCE';
102
-				break;
103
-			case 'ANCES_INTEREST':
104
-				$tag = 'ANCI';
105
-				break;
106
-			case 'ANNULMENT':
107
-				$tag = 'ANUL';
108
-				break;
109
-			case 'ASSOCIATES':
110
-				$tag = 'ASSO';
111
-				break;
112
-			case 'AUTHOR':
113
-				$tag = 'AUTH';
114
-				break;
115
-			case 'BAPTISM':
116
-				$tag = 'BAPM';
117
-				break;
118
-			case 'BAPTISM_LDS':
119
-				$tag = 'BAPL';
120
-				break;
121
-			case 'BAR_MITZVAH':
122
-				$tag = 'BARM';
123
-				break;
124
-			case 'BAS_MITZVAH':
125
-				$tag = 'BASM';
126
-				break;
127
-			case 'BIRTH':
128
-				$tag = 'BIRT';
129
-				break;
130
-			case 'BLESSING':
131
-				$tag = 'BLES';
132
-				break;
133
-			case 'BURIAL':
134
-				$tag = 'BURI';
135
-				break;
136
-			case 'CALL_NUMBER':
137
-				$tag = 'CALN';
138
-				break;
139
-			case 'CASTE':
140
-				$tag = 'CAST';
141
-				break;
142
-			case 'CAUSE':
143
-				$tag = 'CAUS';
144
-				break;
145
-			case 'CENSUS':
146
-				$tag = 'CENS';
147
-				break;
148
-			case 'CHANGE':
149
-				$tag = 'CHAN';
150
-				break;
151
-			case 'CHARACTER':
152
-				$tag = 'CHAR';
153
-				break;
154
-			case 'CHILD':
155
-				$tag = 'CHIL';
156
-				break;
157
-			case 'CHILDREN_COUNT':
158
-				$tag = 'NCHI';
159
-				break;
160
-			case 'CHRISTENING':
161
-				$tag = 'CHR';
162
-				break;
163
-			case 'CONCATENATION':
164
-				$tag = 'CONC';
165
-				break;
166
-			case 'CONFIRMATION':
167
-				$tag = 'CONF';
168
-				break;
169
-			case 'CONFIRMATION_LDS':
170
-				$tag = 'CONL';
171
-				break;
172
-			case 'CONTINUED':
173
-				$tag = 'CONT';
174
-				break;
175
-			case 'COPYRIGHT':
176
-				$tag = 'COPR';
177
-				break;
178
-			case 'CORPORATE':
179
-				$tag = 'CORP';
180
-				break;
181
-			case 'COUNTRY':
182
-				$tag = 'CTRY';
183
-				break;
184
-			case 'CREMATION':
185
-				$tag = 'CREM';
186
-				break;
187
-			case 'DATE':
188
-				// Preserve text from INT dates
189
-				if (strpos($data, '(') !== false) {
190
-					list($date, $text) = explode('(', $data, 2);
191
-					$text              = ' (' . $text;
192
-				} else {
193
-					$date = $data;
194
-					$text = '';
195
-				}
196
-				// Capitals
197
-				$date = strtoupper($date);
198
-				// Temporarily add leading/trailing spaces, to allow efficient matching below
199
-				$date = " {$date} ";
200
-				// Ensure space digits and letters
201
-				$date = preg_replace('/([A-Z])(\d)/', '$1 $2', $date);
202
-				$date = preg_replace('/(\d)([A-Z])/', '$1 $2', $date);
203
-				// Ensure space before/after calendar escapes
204
-				$date = preg_replace('/@#[^@]+@/', ' $0 ', $date);
205
-				// "BET." => "BET"
206
-				$date = preg_replace('/(\w\w)\./', '$1', $date);
207
-				// "CIR" => "ABT"
208
-				$date = str_replace(' CIR ', ' ABT ', $date);
209
-				$date = str_replace(' APX ', ' ABT ', $date);
210
-				// B.C. => BC (temporarily, to allow easier handling of ".")
211
-				$date = str_replace(' B.C. ', ' BC ', $date);
212
-				// "BET X - Y " => "BET X AND Y"
213
-				$date = preg_replace('/^(.* BET .+) - (.+)/', '$1 AND $2', $date);
214
-				$date = preg_replace('/^(.* FROM .+) - (.+)/', '$1 TO $2', $date);
215
-				// "@#ESC@ FROM X TO Y" => "FROM @#ESC@ X TO @#ESC@ Y"
216
-				$date = preg_replace('/^ +(@#[^@]+@) +FROM +(.+) +TO +(.+)/', ' FROM $1 $2 TO $1 $3', $date);
217
-				$date = preg_replace('/^ +(@#[^@]+@) +BET +(.+) +AND +(.+)/', ' BET $1 $2 AND $1 $3', $date);
218
-				// "@#ESC@ AFT X" => "AFT @#ESC@ X"
219
-				$date = preg_replace('/^ +(@#[^@]+@) +(FROM|BET|TO|AND|BEF|AFT|CAL|EST|INT|ABT) +(.+)/', ' $2 $1 $3', $date);
220
-				// Ignore any remaining punctuation, e.g. "14-MAY, 1900" => "14 MAY 1900"
221
-				// (don't change "/" - it is used in NS/OS dates)
222
-				$date = preg_replace('/[.,:;-]/', ' ', $date);
223
-				// BC => B.C.
224
-				$date = str_replace(' BC ', ' B.C. ', $date);
225
-				// Append the "INT" text
226
-				$data = $date . $text;
227
-				break;
228
-			case 'DEATH':
229
-				$tag = 'DEAT';
230
-				break;
231
-			case '_DEATH_OF_SPOUSE':
232
-				$tag = '_DETS';
233
-				break;
234
-			case '_DEGREE':
235
-				$tag = '_DEG';
236
-				break;
237
-			case 'DESCENDANTS':
238
-				$tag = 'DESC';
239
-				break;
240
-			case 'DESCENDANT_INT':
241
-				$tag = 'DESI';
242
-				break;
243
-			case 'DESTINATION':
244
-				$tag = 'DEST';
245
-				break;
246
-			case 'DIVORCE':
247
-				$tag = 'DIV';
248
-				break;
249
-			case 'DIVORCE_FILED':
250
-				$tag = 'DIVF';
251
-				break;
252
-			case 'EDUCATION':
253
-				$tag = 'EDUC';
254
-				break;
255
-			case 'EMIGRATION':
256
-				$tag = 'EMIG';
257
-				break;
258
-			case 'ENDOWMENT':
259
-				$tag = 'ENDL';
260
-				break;
261
-			case 'ENGAGEMENT':
262
-				$tag = 'ENGA';
263
-				break;
264
-			case 'EVENT':
265
-				$tag = 'EVEN';
266
-				break;
267
-			case 'FACSIMILE':
268
-				$tag = 'FAX';
269
-				break;
270
-			case 'FAMILY':
271
-				$tag = 'FAM';
272
-				break;
273
-			case 'FAMILY_CHILD':
274
-				$tag = 'FAMC';
275
-				break;
276
-			case 'FAMILY_FILE':
277
-				$tag = 'FAMF';
278
-				break;
279
-			case 'FAMILY_SPOUSE':
280
-				$tag = 'FAMS';
281
-				break;
282
-			case 'FIRST_COMMUNION':
283
-				$tag = 'FCOM';
284
-				break;
285
-			case '_FILE':
286
-				$tag = 'FILE';
287
-				break;
288
-			case 'FORMAT':
289
-				$tag = 'FORM';
290
-			case 'FORM':
291
-				// Consistent commas
292
-				$data = preg_replace('/ *, */', ', ', $data);
293
-				break;
294
-			case 'GEDCOM':
295
-				$tag = 'GEDC';
296
-				break;
297
-			case 'GIVEN_NAME':
298
-				$tag = 'GIVN';
299
-				break;
300
-			case 'GRADUATION':
301
-				$tag = 'GRAD';
302
-				break;
303
-			case 'HEADER':
304
-				$tag = 'HEAD';
305
-			case 'HEAD':
306
-				// HEAD records don't have an XREF or DATA
307
-				if ($level == '0') {
308
-					$xref = '';
309
-					$data = '';
310
-				}
311
-				break;
312
-			case 'HUSBAND':
313
-				$tag = 'HUSB';
314
-				break;
315
-			case 'IDENT_NUMBER':
316
-				$tag = 'IDNO';
317
-				break;
318
-			case 'IMMIGRATION':
319
-				$tag = 'IMMI';
320
-				break;
321
-			case 'INDIVIDUAL':
322
-				$tag = 'INDI';
323
-				break;
324
-			case 'LANGUAGE':
325
-				$tag = 'LANG';
326
-				break;
327
-			case 'LATITUDE':
328
-				$tag = 'LATI';
329
-				break;
330
-			case 'LONGITUDE':
331
-				$tag = 'LONG';
332
-				break;
333
-			case 'MARRIAGE':
334
-				$tag = 'MARR';
335
-				break;
336
-			case 'MARRIAGE_BANN':
337
-				$tag = 'MARB';
338
-				break;
339
-			case 'MARRIAGE_COUNT':
340
-				$tag = 'NMR';
341
-				break;
342
-			case 'MARRIAGE_CONTRACT':
343
-				$tag = 'MARC';
344
-				break;
345
-			case 'MARRIAGE_LICENSE':
346
-				$tag = 'MARL';
347
-				break;
348
-			case 'MARRIAGE_SETTLEMENT':
349
-				$tag = 'MARS';
350
-				break;
351
-			case 'MEDIA':
352
-				$tag = 'MEDI';
353
-				break;
354
-			case '_MEDICAL':
355
-				$tag = '_MDCL';
356
-				break;
357
-			case '_MILITARY_SERVICE':
358
-				$tag = '_MILT';
359
-				break;
360
-			case 'NAME':
361
-				// Tidy up whitespace
362
-				$data = preg_replace('/  +/', ' ', trim($data));
363
-				break;
364
-			case 'NAME_PREFIX':
365
-				$tag = 'NPFX';
366
-				break;
367
-			case 'NAME_SUFFIX':
368
-				$tag = 'NSFX';
369
-				break;
370
-			case 'NATIONALITY':
371
-				$tag = 'NATI';
372
-				break;
373
-			case 'NATURALIZATION':
374
-				$tag = 'NATU';
375
-				break;
376
-			case 'NICKNAME':
377
-				$tag = 'NICK';
378
-				break;
379
-			case 'OBJECT':
380
-				$tag = 'OBJE';
381
-				break;
382
-			case 'OCCUPATION':
383
-				$tag = 'OCCU';
384
-				break;
385
-			case 'ORDINANCE':
386
-				$tag = 'ORDI';
387
-				break;
388
-			case 'ORDINATION':
389
-				$tag = 'ORDN';
390
-				break;
391
-			case 'PEDIGREE':
392
-				$tag = 'PEDI';
393
-			case 'PEDI':
394
-				// PEDI values are lower case
395
-				$data = strtolower($data);
396
-				break;
397
-			case 'PHONE':
398
-				$tag = 'PHON';
399
-				break;
400
-			case 'PHONETIC':
401
-				$tag = 'FONE';
402
-				break;
403
-			case 'PHY_DESCRIPTION':
404
-				$tag = 'DSCR';
405
-				break;
406
-			case 'PLACE':
407
-				$tag = 'PLAC';
408
-			case 'PLAC':
409
-				// Consistent commas
410
-				$data = preg_replace('/ *(،|,) */', ', ', $data);
411
-				// The Master Genealogist stores LAT/LONG data in the PLAC field, e.g. Pennsylvania, USA, 395945N0751013W
412
-				if (preg_match('/(.*), (\d\d)(\d\d)(\d\d)([NS])(\d\d\d)(\d\d)(\d\d)([EW])$/', $data, $match)) {
413
-					$data =
414
-						$match[1] . "\n" .
415
-						($level + 1) . " MAP\n" .
416
-						($level + 2) . " LATI " . ($match[5] . (round($match[2] + ($match[3] / 60) + ($match[4] / 3600), 4))) . "\n" .
417
-						($level + 2) . " LONG " . ($match[9] . (round($match[6] + ($match[7] / 60) + ($match[8] / 3600), 4)));
418
-				}
419
-				break;
420
-			case 'POSTAL_CODE':
421
-				$tag = 'POST';
422
-				break;
423
-			case 'PROBATE':
424
-				$tag = 'PROB';
425
-				break;
426
-			case 'PROPERTY':
427
-				$tag = 'PROP';
428
-				break;
429
-			case 'PUBLICATION':
430
-				$tag = 'PUBL';
431
-				break;
432
-			case 'QUALITY_OF_DATA':
433
-				$tag = 'QUAL';
434
-				break;
435
-			case 'REC_FILE_NUMBER':
436
-				$tag = 'RFN';
437
-				break;
438
-			case 'REC_ID_NUMBER':
439
-				$tag = 'RIN';
440
-				break;
441
-			case 'REFERENCE':
442
-				$tag = 'REFN';
443
-				break;
444
-			case 'RELATIONSHIP':
445
-				$tag = 'RELA';
446
-				break;
447
-			case 'RELIGION':
448
-				$tag = 'RELI';
449
-				break;
450
-			case 'REPOSITORY':
451
-				$tag = 'REPO';
452
-				break;
453
-			case 'RESIDENCE':
454
-				$tag = 'RESI';
455
-				break;
456
-			case 'RESTRICTION':
457
-				$tag = 'RESN';
458
-			case 'RESN':
459
-				// RESN values are lower case (confidential, privacy, locked, none)
460
-				$data = strtolower($data);
461
-				if ($data == 'invisible') {
462
-					$data = 'confidential'; // From old versions of Legacy.
463
-				}
464
-				break;
465
-			case 'RETIREMENT':
466
-				$tag = 'RETI';
467
-				break;
468
-			case 'ROMANIZED':
469
-				$tag = 'ROMN';
470
-				break;
471
-			case 'SEALING_CHILD':
472
-				$tag = 'SLGC';
473
-				break;
474
-			case 'SEALING_SPOUSE':
475
-				$tag = 'SLGS';
476
-				break;
477
-			case 'SOC_SEC_NUMBER':
478
-				$tag = 'SSN';
479
-				break;
480
-			case 'SEX':
481
-				$data = strtoupper($data);
482
-				break;
483
-			case 'SOURCE':
484
-				$tag = 'SOUR';
485
-				break;
486
-			case 'STATE':
487
-				$tag = 'STAE';
488
-				break;
489
-			case 'STATUS':
490
-				$tag = 'STAT';
491
-			case 'STAT':
492
-				if ($data == 'CANCELLED') {
493
-					// PhpGedView mis-spells this tag - correct it.
494
-					$data = 'CANCELED';
495
-				}
496
-				break;
497
-			case 'SUBMISSION':
498
-				$tag = 'SUBN';
499
-				break;
500
-			case 'SUBMITTER':
501
-				$tag = 'SUBM';
502
-				break;
503
-			case 'SURNAME':
504
-				$tag = 'SURN';
505
-				break;
506
-			case 'SURN_PREFIX':
507
-				$tag = 'SPFX';
508
-				break;
509
-			case 'TEMPLE':
510
-				$tag = 'TEMP';
511
-			case 'TEMP':
512
-				// Temple codes are upper case
513
-				$data = strtoupper($data);
514
-				break;
515
-			case 'TITLE':
516
-				$tag = 'TITL';
517
-				break;
518
-			case 'TRAILER':
519
-				$tag = 'TRLR';
520
-			case 'TRLR':
521
-				// TRLR records don't have an XREF or DATA
522
-				if ($level == '0') {
523
-					$xref = '';
524
-					$data = '';
525
-				}
526
-				break;
527
-			case 'VERSION':
528
-				$tag = 'VERS';
529
-				break;
530
-			case 'WEB':
531
-				$tag = 'WWW';
532
-				break;
62
+			    case '_PGVU':
63
+				    $tag = '_WT_USER';
64
+				    break;
65
+			    case '_PGV_OBJS':
66
+				    $tag = '_WT_OBJE_SORT';
67
+				    break;
68
+				    // Convert FTM-style "TAG_FORMAL_NAME" into "TAG".
69
+			    case 'ABBREVIATION':
70
+				    $tag = 'ABBR';
71
+				    break;
72
+			    case 'ADDRESS':
73
+				    $tag = 'ADDR';
74
+				    break;
75
+			    case 'ADDRESS1':
76
+				    $tag = 'ADR1';
77
+				    break;
78
+			    case 'ADDRESS2':
79
+				    $tag = 'ADR2';
80
+				    break;
81
+			    case 'ADDRESS3':
82
+				    $tag = 'ADR3';
83
+				    break;
84
+			    case 'ADOPTION':
85
+				    $tag = 'ADOP';
86
+				    break;
87
+			    case 'ADULT_CHRISTENING':
88
+				    $tag = 'CHRA';
89
+				    break;
90
+			    case 'AFN':
91
+				    // AFN values are upper case
92
+				    $data = strtoupper($data);
93
+				    break;
94
+			    case 'AGENCY':
95
+				    $tag = 'AGNC';
96
+				    break;
97
+			    case 'ALIAS':
98
+				    $tag = 'ALIA';
99
+				    break;
100
+			    case 'ANCESTORS':
101
+				    $tag = 'ANCE';
102
+				    break;
103
+			    case 'ANCES_INTEREST':
104
+				    $tag = 'ANCI';
105
+				    break;
106
+			    case 'ANNULMENT':
107
+				    $tag = 'ANUL';
108
+				    break;
109
+			    case 'ASSOCIATES':
110
+				    $tag = 'ASSO';
111
+				    break;
112
+			    case 'AUTHOR':
113
+				    $tag = 'AUTH';
114
+				    break;
115
+			    case 'BAPTISM':
116
+				    $tag = 'BAPM';
117
+				    break;
118
+			    case 'BAPTISM_LDS':
119
+				    $tag = 'BAPL';
120
+				    break;
121
+			    case 'BAR_MITZVAH':
122
+				    $tag = 'BARM';
123
+				    break;
124
+			    case 'BAS_MITZVAH':
125
+				    $tag = 'BASM';
126
+				    break;
127
+			    case 'BIRTH':
128
+				    $tag = 'BIRT';
129
+				    break;
130
+			    case 'BLESSING':
131
+				    $tag = 'BLES';
132
+				    break;
133
+			    case 'BURIAL':
134
+				    $tag = 'BURI';
135
+				    break;
136
+			    case 'CALL_NUMBER':
137
+				    $tag = 'CALN';
138
+				    break;
139
+			    case 'CASTE':
140
+				    $tag = 'CAST';
141
+				    break;
142
+			    case 'CAUSE':
143
+				    $tag = 'CAUS';
144
+				    break;
145
+			    case 'CENSUS':
146
+				    $tag = 'CENS';
147
+				    break;
148
+			    case 'CHANGE':
149
+				    $tag = 'CHAN';
150
+				    break;
151
+			    case 'CHARACTER':
152
+				    $tag = 'CHAR';
153
+				    break;
154
+			    case 'CHILD':
155
+				    $tag = 'CHIL';
156
+				    break;
157
+			    case 'CHILDREN_COUNT':
158
+				    $tag = 'NCHI';
159
+				    break;
160
+			    case 'CHRISTENING':
161
+				    $tag = 'CHR';
162
+				    break;
163
+			    case 'CONCATENATION':
164
+				    $tag = 'CONC';
165
+				    break;
166
+			    case 'CONFIRMATION':
167
+				    $tag = 'CONF';
168
+				    break;
169
+			    case 'CONFIRMATION_LDS':
170
+				    $tag = 'CONL';
171
+				    break;
172
+			    case 'CONTINUED':
173
+				    $tag = 'CONT';
174
+				    break;
175
+			    case 'COPYRIGHT':
176
+				    $tag = 'COPR';
177
+				    break;
178
+			    case 'CORPORATE':
179
+				    $tag = 'CORP';
180
+				    break;
181
+			    case 'COUNTRY':
182
+				    $tag = 'CTRY';
183
+				    break;
184
+			    case 'CREMATION':
185
+				    $tag = 'CREM';
186
+				    break;
187
+			    case 'DATE':
188
+				    // Preserve text from INT dates
189
+				    if (strpos($data, '(') !== false) {
190
+					    list($date, $text) = explode('(', $data, 2);
191
+					    $text              = ' (' . $text;
192
+				    } else {
193
+					    $date = $data;
194
+					    $text = '';
195
+				    }
196
+				    // Capitals
197
+				    $date = strtoupper($date);
198
+				    // Temporarily add leading/trailing spaces, to allow efficient matching below
199
+				    $date = " {$date} ";
200
+				    // Ensure space digits and letters
201
+				    $date = preg_replace('/([A-Z])(\d)/', '$1 $2', $date);
202
+				    $date = preg_replace('/(\d)([A-Z])/', '$1 $2', $date);
203
+				    // Ensure space before/after calendar escapes
204
+				    $date = preg_replace('/@#[^@]+@/', ' $0 ', $date);
205
+				    // "BET." => "BET"
206
+				    $date = preg_replace('/(\w\w)\./', '$1', $date);
207
+				    // "CIR" => "ABT"
208
+				    $date = str_replace(' CIR ', ' ABT ', $date);
209
+				    $date = str_replace(' APX ', ' ABT ', $date);
210
+				    // B.C. => BC (temporarily, to allow easier handling of ".")
211
+				    $date = str_replace(' B.C. ', ' BC ', $date);
212
+				    // "BET X - Y " => "BET X AND Y"
213
+				    $date = preg_replace('/^(.* BET .+) - (.+)/', '$1 AND $2', $date);
214
+				    $date = preg_replace('/^(.* FROM .+) - (.+)/', '$1 TO $2', $date);
215
+				    // "@#ESC@ FROM X TO Y" => "FROM @#ESC@ X TO @#ESC@ Y"
216
+				    $date = preg_replace('/^ +(@#[^@]+@) +FROM +(.+) +TO +(.+)/', ' FROM $1 $2 TO $1 $3', $date);
217
+				    $date = preg_replace('/^ +(@#[^@]+@) +BET +(.+) +AND +(.+)/', ' BET $1 $2 AND $1 $3', $date);
218
+				    // "@#ESC@ AFT X" => "AFT @#ESC@ X"
219
+				    $date = preg_replace('/^ +(@#[^@]+@) +(FROM|BET|TO|AND|BEF|AFT|CAL|EST|INT|ABT) +(.+)/', ' $2 $1 $3', $date);
220
+				    // Ignore any remaining punctuation, e.g. "14-MAY, 1900" => "14 MAY 1900"
221
+				    // (don't change "/" - it is used in NS/OS dates)
222
+				    $date = preg_replace('/[.,:;-]/', ' ', $date);
223
+				    // BC => B.C.
224
+				    $date = str_replace(' BC ', ' B.C. ', $date);
225
+				    // Append the "INT" text
226
+				    $data = $date . $text;
227
+				    break;
228
+			    case 'DEATH':
229
+				    $tag = 'DEAT';
230
+				    break;
231
+			    case '_DEATH_OF_SPOUSE':
232
+				    $tag = '_DETS';
233
+				    break;
234
+			    case '_DEGREE':
235
+				    $tag = '_DEG';
236
+				    break;
237
+			    case 'DESCENDANTS':
238
+				    $tag = 'DESC';
239
+				    break;
240
+			    case 'DESCENDANT_INT':
241
+				    $tag = 'DESI';
242
+				    break;
243
+			    case 'DESTINATION':
244
+				    $tag = 'DEST';
245
+				    break;
246
+			    case 'DIVORCE':
247
+				    $tag = 'DIV';
248
+				    break;
249
+			    case 'DIVORCE_FILED':
250
+				    $tag = 'DIVF';
251
+				    break;
252
+			    case 'EDUCATION':
253
+				    $tag = 'EDUC';
254
+				    break;
255
+			    case 'EMIGRATION':
256
+				    $tag = 'EMIG';
257
+				    break;
258
+			    case 'ENDOWMENT':
259
+				    $tag = 'ENDL';
260
+				    break;
261
+			    case 'ENGAGEMENT':
262
+				    $tag = 'ENGA';
263
+				    break;
264
+			    case 'EVENT':
265
+				    $tag = 'EVEN';
266
+				    break;
267
+			    case 'FACSIMILE':
268
+				    $tag = 'FAX';
269
+				    break;
270
+			    case 'FAMILY':
271
+				    $tag = 'FAM';
272
+				    break;
273
+			    case 'FAMILY_CHILD':
274
+				    $tag = 'FAMC';
275
+				    break;
276
+			    case 'FAMILY_FILE':
277
+				    $tag = 'FAMF';
278
+				    break;
279
+			    case 'FAMILY_SPOUSE':
280
+				    $tag = 'FAMS';
281
+				    break;
282
+			    case 'FIRST_COMMUNION':
283
+				    $tag = 'FCOM';
284
+				    break;
285
+			    case '_FILE':
286
+				    $tag = 'FILE';
287
+				    break;
288
+			    case 'FORMAT':
289
+				    $tag = 'FORM';
290
+			    case 'FORM':
291
+				    // Consistent commas
292
+				    $data = preg_replace('/ *, */', ', ', $data);
293
+				    break;
294
+			    case 'GEDCOM':
295
+				    $tag = 'GEDC';
296
+				    break;
297
+			    case 'GIVEN_NAME':
298
+				    $tag = 'GIVN';
299
+				    break;
300
+			    case 'GRADUATION':
301
+				    $tag = 'GRAD';
302
+				    break;
303
+			    case 'HEADER':
304
+				    $tag = 'HEAD';
305
+			    case 'HEAD':
306
+				    // HEAD records don't have an XREF or DATA
307
+				    if ($level == '0') {
308
+					    $xref = '';
309
+					    $data = '';
310
+				    }
311
+				    break;
312
+			    case 'HUSBAND':
313
+				    $tag = 'HUSB';
314
+				    break;
315
+			    case 'IDENT_NUMBER':
316
+				    $tag = 'IDNO';
317
+				    break;
318
+			    case 'IMMIGRATION':
319
+				    $tag = 'IMMI';
320
+				    break;
321
+			    case 'INDIVIDUAL':
322
+				    $tag = 'INDI';
323
+				    break;
324
+			    case 'LANGUAGE':
325
+				    $tag = 'LANG';
326
+				    break;
327
+			    case 'LATITUDE':
328
+				    $tag = 'LATI';
329
+				    break;
330
+			    case 'LONGITUDE':
331
+				    $tag = 'LONG';
332
+				    break;
333
+			    case 'MARRIAGE':
334
+				    $tag = 'MARR';
335
+				    break;
336
+			    case 'MARRIAGE_BANN':
337
+				    $tag = 'MARB';
338
+				    break;
339
+			    case 'MARRIAGE_COUNT':
340
+				    $tag = 'NMR';
341
+				    break;
342
+			    case 'MARRIAGE_CONTRACT':
343
+				    $tag = 'MARC';
344
+				    break;
345
+			    case 'MARRIAGE_LICENSE':
346
+				    $tag = 'MARL';
347
+				    break;
348
+			    case 'MARRIAGE_SETTLEMENT':
349
+				    $tag = 'MARS';
350
+				    break;
351
+			    case 'MEDIA':
352
+				    $tag = 'MEDI';
353
+				    break;
354
+			    case '_MEDICAL':
355
+				    $tag = '_MDCL';
356
+				    break;
357
+			    case '_MILITARY_SERVICE':
358
+				    $tag = '_MILT';
359
+				    break;
360
+			    case 'NAME':
361
+				    // Tidy up whitespace
362
+				    $data = preg_replace('/  +/', ' ', trim($data));
363
+				    break;
364
+			    case 'NAME_PREFIX':
365
+				    $tag = 'NPFX';
366
+				    break;
367
+			    case 'NAME_SUFFIX':
368
+				    $tag = 'NSFX';
369
+				    break;
370
+			    case 'NATIONALITY':
371
+				    $tag = 'NATI';
372
+				    break;
373
+			    case 'NATURALIZATION':
374
+				    $tag = 'NATU';
375
+				    break;
376
+			    case 'NICKNAME':
377
+				    $tag = 'NICK';
378
+				    break;
379
+			    case 'OBJECT':
380
+				    $tag = 'OBJE';
381
+				    break;
382
+			    case 'OCCUPATION':
383
+				    $tag = 'OCCU';
384
+				    break;
385
+			    case 'ORDINANCE':
386
+				    $tag = 'ORDI';
387
+				    break;
388
+			    case 'ORDINATION':
389
+				    $tag = 'ORDN';
390
+				    break;
391
+			    case 'PEDIGREE':
392
+				    $tag = 'PEDI';
393
+			    case 'PEDI':
394
+				    // PEDI values are lower case
395
+				    $data = strtolower($data);
396
+				    break;
397
+			    case 'PHONE':
398
+				    $tag = 'PHON';
399
+				    break;
400
+			    case 'PHONETIC':
401
+				    $tag = 'FONE';
402
+				    break;
403
+			    case 'PHY_DESCRIPTION':
404
+				    $tag = 'DSCR';
405
+				    break;
406
+			    case 'PLACE':
407
+				    $tag = 'PLAC';
408
+			    case 'PLAC':
409
+				    // Consistent commas
410
+				    $data = preg_replace('/ *(،|,) */', ', ', $data);
411
+				    // The Master Genealogist stores LAT/LONG data in the PLAC field, e.g. Pennsylvania, USA, 395945N0751013W
412
+				    if (preg_match('/(.*), (\d\d)(\d\d)(\d\d)([NS])(\d\d\d)(\d\d)(\d\d)([EW])$/', $data, $match)) {
413
+					    $data =
414
+						    $match[1] . "\n" .
415
+						    ($level + 1) . " MAP\n" .
416
+						    ($level + 2) . " LATI " . ($match[5] . (round($match[2] + ($match[3] / 60) + ($match[4] / 3600), 4))) . "\n" .
417
+						    ($level + 2) . " LONG " . ($match[9] . (round($match[6] + ($match[7] / 60) + ($match[8] / 3600), 4)));
418
+				    }
419
+				    break;
420
+			    case 'POSTAL_CODE':
421
+				    $tag = 'POST';
422
+				    break;
423
+			    case 'PROBATE':
424
+				    $tag = 'PROB';
425
+				    break;
426
+			    case 'PROPERTY':
427
+				    $tag = 'PROP';
428
+				    break;
429
+			    case 'PUBLICATION':
430
+				    $tag = 'PUBL';
431
+				    break;
432
+			    case 'QUALITY_OF_DATA':
433
+				    $tag = 'QUAL';
434
+				    break;
435
+			    case 'REC_FILE_NUMBER':
436
+				    $tag = 'RFN';
437
+				    break;
438
+			    case 'REC_ID_NUMBER':
439
+				    $tag = 'RIN';
440
+				    break;
441
+			    case 'REFERENCE':
442
+				    $tag = 'REFN';
443
+				    break;
444
+			    case 'RELATIONSHIP':
445
+				    $tag = 'RELA';
446
+				    break;
447
+			    case 'RELIGION':
448
+				    $tag = 'RELI';
449
+				    break;
450
+			    case 'REPOSITORY':
451
+				    $tag = 'REPO';
452
+				    break;
453
+			    case 'RESIDENCE':
454
+				    $tag = 'RESI';
455
+				    break;
456
+			    case 'RESTRICTION':
457
+				    $tag = 'RESN';
458
+			    case 'RESN':
459
+				    // RESN values are lower case (confidential, privacy, locked, none)
460
+				    $data = strtolower($data);
461
+				    if ($data == 'invisible') {
462
+					    $data = 'confidential'; // From old versions of Legacy.
463
+				    }
464
+				    break;
465
+			    case 'RETIREMENT':
466
+				    $tag = 'RETI';
467
+				    break;
468
+			    case 'ROMANIZED':
469
+				    $tag = 'ROMN';
470
+				    break;
471
+			    case 'SEALING_CHILD':
472
+				    $tag = 'SLGC';
473
+				    break;
474
+			    case 'SEALING_SPOUSE':
475
+				    $tag = 'SLGS';
476
+				    break;
477
+			    case 'SOC_SEC_NUMBER':
478
+				    $tag = 'SSN';
479
+				    break;
480
+			    case 'SEX':
481
+				    $data = strtoupper($data);
482
+				    break;
483
+			    case 'SOURCE':
484
+				    $tag = 'SOUR';
485
+				    break;
486
+			    case 'STATE':
487
+				    $tag = 'STAE';
488
+				    break;
489
+			    case 'STATUS':
490
+				    $tag = 'STAT';
491
+			    case 'STAT':
492
+				    if ($data == 'CANCELLED') {
493
+					    // PhpGedView mis-spells this tag - correct it.
494
+					    $data = 'CANCELED';
495
+				    }
496
+				    break;
497
+			    case 'SUBMISSION':
498
+				    $tag = 'SUBN';
499
+				    break;
500
+			    case 'SUBMITTER':
501
+				    $tag = 'SUBM';
502
+				    break;
503
+			    case 'SURNAME':
504
+				    $tag = 'SURN';
505
+				    break;
506
+			    case 'SURN_PREFIX':
507
+				    $tag = 'SPFX';
508
+				    break;
509
+			    case 'TEMPLE':
510
+				    $tag = 'TEMP';
511
+			    case 'TEMP':
512
+				    // Temple codes are upper case
513
+				    $data = strtoupper($data);
514
+				    break;
515
+			    case 'TITLE':
516
+				    $tag = 'TITL';
517
+				    break;
518
+			    case 'TRAILER':
519
+				    $tag = 'TRLR';
520
+			    case 'TRLR':
521
+				    // TRLR records don't have an XREF or DATA
522
+				    if ($level == '0') {
523
+					    $xref = '';
524
+					    $data = '';
525
+				    }
526
+				    break;
527
+			    case 'VERSION':
528
+				    $tag = 'VERS';
529
+				    break;
530
+			    case 'WEB':
531
+				    $tag = 'WWW';
532
+				    break;
533 533
 			}
534 534
 			// Suppress "Y", for facts/events with a DATE or PLAC
535 535
 			if ($data == 'y') {
@@ -545,40 +545,40 @@  discard block
 block discarded – undo
545 545
 			}
546 546
 			// Reassemble components back into a single line
547 547
 			switch ($tag) {
548
-			default:
549
-				// Remove tabs and multiple/leading/trailing spaces
550
-				if (strpos($data, "\t") !== false) {
551
-					$data = str_replace("\t", ' ', $data);
552
-				}
553
-				if (substr($data, 0, 1) == ' ' || substr($data, -1, 1) == ' ') {
554
-					$data = trim($data);
555
-				}
556
-				while (strpos($data, '  ')) {
557
-					$data = str_replace('  ', ' ', $data);
558
-				}
559
-				$newrec .= ($newrec ? "\n" : '') . $level . ' ' . ($level == '0' && $xref ? $xref . ' ' : '') . $tag . ($data === '' && $tag != "NOTE" ? '' : ' ' . $data);
560
-				break;
561
-			case 'NOTE':
562
-			case 'TEXT':
563
-			case 'DATA':
564
-			case 'CONT':
565
-				$newrec .= ($newrec ? "\n" : '') . $level . ' ' . ($level == '0' && $xref ? $xref . ' ' : '') . $tag . ($data === '' && $tag != "NOTE" ? '' : ' ' . $data);
566
-				break;
567
-			case 'FILE':
568
-				// Strip off the user-defined path prefix
569
-				$GEDCOM_MEDIA_PATH = $tree->getPreference('GEDCOM_MEDIA_PATH');
570
-				if ($GEDCOM_MEDIA_PATH && strpos($data, $GEDCOM_MEDIA_PATH) === 0) {
571
-					$data = substr($data, strlen($GEDCOM_MEDIA_PATH));
572
-				}
573
-				// convert backslashes in filenames to forward slashes
574
-				$data = preg_replace("/\\\/", "/", $data);
548
+			    default:
549
+				    // Remove tabs and multiple/leading/trailing spaces
550
+				    if (strpos($data, "\t") !== false) {
551
+					    $data = str_replace("\t", ' ', $data);
552
+				    }
553
+				    if (substr($data, 0, 1) == ' ' || substr($data, -1, 1) == ' ') {
554
+					    $data = trim($data);
555
+				    }
556
+				    while (strpos($data, '  ')) {
557
+					    $data = str_replace('  ', ' ', $data);
558
+				    }
559
+				    $newrec .= ($newrec ? "\n" : '') . $level . ' ' . ($level == '0' && $xref ? $xref . ' ' : '') . $tag . ($data === '' && $tag != "NOTE" ? '' : ' ' . $data);
560
+				    break;
561
+			    case 'NOTE':
562
+			    case 'TEXT':
563
+			    case 'DATA':
564
+			    case 'CONT':
565
+				    $newrec .= ($newrec ? "\n" : '') . $level . ' ' . ($level == '0' && $xref ? $xref . ' ' : '') . $tag . ($data === '' && $tag != "NOTE" ? '' : ' ' . $data);
566
+				    break;
567
+			    case 'FILE':
568
+				    // Strip off the user-defined path prefix
569
+				    $GEDCOM_MEDIA_PATH = $tree->getPreference('GEDCOM_MEDIA_PATH');
570
+				    if ($GEDCOM_MEDIA_PATH && strpos($data, $GEDCOM_MEDIA_PATH) === 0) {
571
+					    $data = substr($data, strlen($GEDCOM_MEDIA_PATH));
572
+				    }
573
+				    // convert backslashes in filenames to forward slashes
574
+				    $data = preg_replace("/\\\/", "/", $data);
575 575
 
576
-				$newrec .= ($newrec ? "\n" : '') . $level . ' ' . ($level == '0' && $xref ? $xref . ' ' : '') . $tag . ($data === '' && $tag != "NOTE" ? '' : ' ' . $data);
577
-				break;
578
-			case 'CONC':
579
-				// Merge CONC lines, to simplify access later on.
580
-				$newrec .= ($tree->getPreference('WORD_WRAPPED_NOTES') ? ' ' : '') . $data;
581
-				break;
576
+				    $newrec .= ($newrec ? "\n" : '') . $level . ' ' . ($level == '0' && $xref ? $xref . ' ' : '') . $tag . ($data === '' && $tag != "NOTE" ? '' : ' ' . $data);
577
+				    break;
578
+			    case 'CONC':
579
+				    // Merge CONC lines, to simplify access later on.
580
+				    $newrec .= ($tree->getPreference('WORD_WRAPPED_NOTES') ? ' ' : '') . $data;
581
+				    break;
582 582
 			}
583 583
 		}
584 584
 
@@ -635,123 +635,123 @@  discard block
 block discarded – undo
635 635
 		}
636 636
 
637 637
 		switch ($type) {
638
-		case 'INDI':
639
-			// Convert inline media into media objects
640
-			$gedrec = self::convertInlineMedia($tree, $gedrec);
638
+		    case 'INDI':
639
+			    // Convert inline media into media objects
640
+			    $gedrec = self::convertInlineMedia($tree, $gedrec);
641 641
 
642
-			$record = new Individual($xref, $gedrec, null, $tree);
643
-			if (preg_match('/\n1 RIN (.+)/', $gedrec, $match)) {
644
-				$rin = $match[1];
645
-			} else {
646
-				$rin = $xref;
647
-			}
648
-			Database::prepare(
649
-				"INSERT INTO `##individuals` (i_id, i_file, i_rin, i_sex, i_gedcom) VALUES (?, ?, ?, ?, ?)"
650
-			)->execute(array(
651
-				$xref, $tree_id, $rin, $record->getSex(), $gedrec,
652
-			));
653
-			// Update the cross-reference/index tables.
654
-			self::updatePlaces($xref, $tree_id, $gedrec);
655
-			self::updateDates($xref, $tree_id, $gedrec);
656
-			self::updateLinks($xref, $tree_id, $gedrec);
657
-			self::updateNames($xref, $tree_id, $record);
658
-			break;
659
-		case 'FAM':
660
-			// Convert inline media into media objects
661
-			$gedrec = self::convertInlineMedia($tree, $gedrec);
642
+			    $record = new Individual($xref, $gedrec, null, $tree);
643
+			    if (preg_match('/\n1 RIN (.+)/', $gedrec, $match)) {
644
+				    $rin = $match[1];
645
+			    } else {
646
+				    $rin = $xref;
647
+			    }
648
+			    Database::prepare(
649
+				    "INSERT INTO `##individuals` (i_id, i_file, i_rin, i_sex, i_gedcom) VALUES (?, ?, ?, ?, ?)"
650
+			    )->execute(array(
651
+				    $xref, $tree_id, $rin, $record->getSex(), $gedrec,
652
+			    ));
653
+			    // Update the cross-reference/index tables.
654
+			    self::updatePlaces($xref, $tree_id, $gedrec);
655
+			    self::updateDates($xref, $tree_id, $gedrec);
656
+			    self::updateLinks($xref, $tree_id, $gedrec);
657
+			    self::updateNames($xref, $tree_id, $record);
658
+			    break;
659
+		    case 'FAM':
660
+			    // Convert inline media into media objects
661
+			    $gedrec = self::convertInlineMedia($tree, $gedrec);
662 662
 
663
-			if (preg_match('/\n1 HUSB @(' . WT_REGEX_XREF . ')@/', $gedrec, $match)) {
664
-				$husb = $match[1];
665
-			} else {
666
-				$husb = '';
667
-			}
668
-			if (preg_match('/\n1 WIFE @(' . WT_REGEX_XREF . ')@/', $gedrec, $match)) {
669
-				$wife = $match[1];
670
-			} else {
671
-				$wife = '';
672
-			}
673
-			$nchi = preg_match_all('/\n1 CHIL @(' . WT_REGEX_XREF . ')@/', $gedrec, $match);
674
-			if (preg_match('/\n1 NCHI (\d+)/', $gedrec, $match)) {
675
-				$nchi = max($nchi, $match[1]);
676
-			}
677
-			Database::prepare(
678
-				"INSERT INTO `##families` (f_id, f_file, f_husb, f_wife, f_gedcom, f_numchil) VALUES (?, ?, ?, ?, ?, ?)"
679
-			)->execute(array(
680
-				$xref, $tree_id, $husb, $wife, $gedrec, $nchi,
681
-			));
682
-			// Update the cross-reference/index tables.
683
-			self::updatePlaces($xref, $tree_id, $gedrec);
684
-			self::updateDates($xref, $tree_id, $gedrec);
685
-			self::updateLinks($xref, $tree_id, $gedrec);
686
-			break;
687
-		case 'SOUR':
688
-			// Convert inline media into media objects
689
-			$gedrec = self::convertInlineMedia($tree, $gedrec);
663
+			    if (preg_match('/\n1 HUSB @(' . WT_REGEX_XREF . ')@/', $gedrec, $match)) {
664
+				    $husb = $match[1];
665
+			    } else {
666
+				    $husb = '';
667
+			    }
668
+			    if (preg_match('/\n1 WIFE @(' . WT_REGEX_XREF . ')@/', $gedrec, $match)) {
669
+				    $wife = $match[1];
670
+			    } else {
671
+				    $wife = '';
672
+			    }
673
+			    $nchi = preg_match_all('/\n1 CHIL @(' . WT_REGEX_XREF . ')@/', $gedrec, $match);
674
+			    if (preg_match('/\n1 NCHI (\d+)/', $gedrec, $match)) {
675
+				    $nchi = max($nchi, $match[1]);
676
+			    }
677
+			    Database::prepare(
678
+				    "INSERT INTO `##families` (f_id, f_file, f_husb, f_wife, f_gedcom, f_numchil) VALUES (?, ?, ?, ?, ?, ?)"
679
+			    )->execute(array(
680
+				    $xref, $tree_id, $husb, $wife, $gedrec, $nchi,
681
+			    ));
682
+			    // Update the cross-reference/index tables.
683
+			    self::updatePlaces($xref, $tree_id, $gedrec);
684
+			    self::updateDates($xref, $tree_id, $gedrec);
685
+			    self::updateLinks($xref, $tree_id, $gedrec);
686
+			    break;
687
+		    case 'SOUR':
688
+			    // Convert inline media into media objects
689
+			    $gedrec = self::convertInlineMedia($tree, $gedrec);
690 690
 
691
-			$record = new Source($xref, $gedrec, null, $tree);
692
-			if (preg_match('/\n1 TITL (.+)/', $gedrec, $match)) {
693
-				$name = $match[1];
694
-			} elseif (preg_match('/\n1 ABBR (.+)/', $gedrec, $match)) {
695
-				$name = $match[1];
696
-			} else {
697
-				$name = $xref;
698
-			}
699
-			Database::prepare(
700
-				"INSERT INTO `##sources` (s_id, s_file, s_name, s_gedcom) VALUES (?, ?, LEFT(?, 255), ?)"
701
-			)->execute(array(
702
-				$xref, $tree_id, $name, $gedrec,
703
-			));
704
-			// Update the cross-reference/index tables.
705
-			self::updateLinks($xref, $tree_id, $gedrec);
706
-			self::updateNames($xref, $tree_id, $record);
707
-			break;
708
-		case 'REPO':
709
-			// Convert inline media into media objects
710
-			$gedrec = self::convertInlineMedia($tree, $gedrec);
691
+			    $record = new Source($xref, $gedrec, null, $tree);
692
+			    if (preg_match('/\n1 TITL (.+)/', $gedrec, $match)) {
693
+				    $name = $match[1];
694
+			    } elseif (preg_match('/\n1 ABBR (.+)/', $gedrec, $match)) {
695
+				    $name = $match[1];
696
+			    } else {
697
+				    $name = $xref;
698
+			    }
699
+			    Database::prepare(
700
+				    "INSERT INTO `##sources` (s_id, s_file, s_name, s_gedcom) VALUES (?, ?, LEFT(?, 255), ?)"
701
+			    )->execute(array(
702
+				    $xref, $tree_id, $name, $gedrec,
703
+			    ));
704
+			    // Update the cross-reference/index tables.
705
+			    self::updateLinks($xref, $tree_id, $gedrec);
706
+			    self::updateNames($xref, $tree_id, $record);
707
+			    break;
708
+		    case 'REPO':
709
+			    // Convert inline media into media objects
710
+			    $gedrec = self::convertInlineMedia($tree, $gedrec);
711 711
 
712
-			$record = new Repository($xref, $gedrec, null, $tree);
713
-			Database::prepare(
714
-				"INSERT INTO `##other` (o_id, o_file, o_type, o_gedcom) VALUES (?, ?, 'REPO', ?)"
715
-			)->execute(array(
716
-				$xref, $tree_id, $gedrec,
717
-			));
718
-			// Update the cross-reference/index tables.
719
-			self::updateLinks($xref, $tree_id, $gedrec);
720
-			self::updateNames($xref, $tree_id, $record);
721
-			break;
722
-		case 'NOTE':
723
-			$record = new Note($xref, $gedrec, null, $tree);
724
-			Database::prepare(
725
-				"INSERT INTO `##other` (o_id, o_file, o_type, o_gedcom) VALUES (?, ?, 'NOTE', ?)"
726
-			)->execute(array(
727
-				$xref, $tree_id, $gedrec,
728
-			));
729
-			// Update the cross-reference/index tables.
730
-			self::updateLinks($xref, $tree_id, $gedrec);
731
-			self::updateNames($xref, $tree_id, $record);
732
-			break;
733
-		case 'OBJE':
734
-			$record = new Media($xref, $gedrec, null, $tree);
735
-			Database::prepare(
736
-				"INSERT INTO `##media` (m_id, m_ext, m_type, m_titl, m_filename, m_file, m_gedcom) VALUES (?, LEFT(?, 6), LEFT(?, 60), LEFT(?, 255), left(?, 512), ?, ?)"
737
-			)->execute(array(
738
-				$xref, $record->extension(), $record->getMediaType(), $record->getTitle(), $record->getFilename(), $tree_id, $gedrec,
739
-			));
740
-			// Update the cross-reference/index tables.
741
-			self::updateLinks($xref, $tree_id, $gedrec);
742
-			self::updateNames($xref, $tree_id, $record);
743
-			break;
744
-		default: // HEAD, TRLR, SUBM, SUBN, and custom record types.
745
-			// Force HEAD records to have a creation date.
746
-			if ($type === 'HEAD' && strpos($gedrec, "\n1 DATE ") === false) {
747
-				$gedrec .= "\n1 DATE " . date('j M Y');
748
-			}
749
-			Database::prepare(
750
-				"INSERT INTO `##other` (o_id, o_file, o_type, o_gedcom) VALUES (?, ?, LEFT(?, 15), ?)"
751
-			)->execute(array($xref, $tree_id, $type, $gedrec));
752
-			// Update the cross-reference/index tables.
753
-			self::updateLinks($xref, $tree_id, $gedrec);
754
-			break;
712
+			    $record = new Repository($xref, $gedrec, null, $tree);
713
+			    Database::prepare(
714
+				    "INSERT INTO `##other` (o_id, o_file, o_type, o_gedcom) VALUES (?, ?, 'REPO', ?)"
715
+			    )->execute(array(
716
+				    $xref, $tree_id, $gedrec,
717
+			    ));
718
+			    // Update the cross-reference/index tables.
719
+			    self::updateLinks($xref, $tree_id, $gedrec);
720
+			    self::updateNames($xref, $tree_id, $record);
721
+			    break;
722
+		    case 'NOTE':
723
+			    $record = new Note($xref, $gedrec, null, $tree);
724
+			    Database::prepare(
725
+				    "INSERT INTO `##other` (o_id, o_file, o_type, o_gedcom) VALUES (?, ?, 'NOTE', ?)"
726
+			    )->execute(array(
727
+				    $xref, $tree_id, $gedrec,
728
+			    ));
729
+			    // Update the cross-reference/index tables.
730
+			    self::updateLinks($xref, $tree_id, $gedrec);
731
+			    self::updateNames($xref, $tree_id, $record);
732
+			    break;
733
+		    case 'OBJE':
734
+			    $record = new Media($xref, $gedrec, null, $tree);
735
+			    Database::prepare(
736
+				    "INSERT INTO `##media` (m_id, m_ext, m_type, m_titl, m_filename, m_file, m_gedcom) VALUES (?, LEFT(?, 6), LEFT(?, 60), LEFT(?, 255), left(?, 512), ?, ?)"
737
+			    )->execute(array(
738
+				    $xref, $record->extension(), $record->getMediaType(), $record->getTitle(), $record->getFilename(), $tree_id, $gedrec,
739
+			    ));
740
+			    // Update the cross-reference/index tables.
741
+			    self::updateLinks($xref, $tree_id, $gedrec);
742
+			    self::updateNames($xref, $tree_id, $record);
743
+			    break;
744
+		    default: // HEAD, TRLR, SUBM, SUBN, and custom record types.
745
+			    // Force HEAD records to have a creation date.
746
+			    if ($type === 'HEAD' && strpos($gedrec, "\n1 DATE ") === false) {
747
+				    $gedrec .= "\n1 DATE " . date('j M Y');
748
+			    }
749
+			    Database::prepare(
750
+				    "INSERT INTO `##other` (o_id, o_file, o_type, o_gedcom) VALUES (?, ?, LEFT(?, 15), ?)"
751
+			    )->execute(array($xref, $tree_id, $type, $gedrec));
752
+			    // Update the cross-reference/index tables.
753
+			    self::updateLinks($xref, $tree_id, $gedrec);
754
+			    break;
755 755
 		}
756 756
 	}
757 757
 
@@ -1130,21 +1130,21 @@  discard block
 block discarded – undo
1130 1130
 		Database::prepare("DELETE FROM `##link` WHERE l_from=? AND l_file=?")->execute(array($gid, $ged_id));
1131 1131
 
1132 1132
 		switch ($type) {
1133
-		case 'INDI':
1134
-			Database::prepare("DELETE FROM `##individuals` WHERE i_id=? AND i_file=?")->execute(array($gid, $ged_id));
1135
-			break;
1136
-		case 'FAM':
1137
-			Database::prepare("DELETE FROM `##families` WHERE f_id=? AND f_file=?")->execute(array($gid, $ged_id));
1138
-			break;
1139
-		case 'SOUR':
1140
-			Database::prepare("DELETE FROM `##sources` WHERE s_id=? AND s_file=?")->execute(array($gid, $ged_id));
1141
-			break;
1142
-		case 'OBJE':
1143
-			Database::prepare("DELETE FROM `##media` WHERE m_id=? AND m_file=?")->execute(array($gid, $ged_id));
1144
-			break;
1145
-		default:
1146
-			Database::prepare("DELETE FROM `##other` WHERE o_id=? AND o_file=?")->execute(array($gid, $ged_id));
1147
-			break;
1133
+		    case 'INDI':
1134
+			    Database::prepare("DELETE FROM `##individuals` WHERE i_id=? AND i_file=?")->execute(array($gid, $ged_id));
1135
+			    break;
1136
+		    case 'FAM':
1137
+			    Database::prepare("DELETE FROM `##families` WHERE f_id=? AND f_file=?")->execute(array($gid, $ged_id));
1138
+			    break;
1139
+		    case 'SOUR':
1140
+			    Database::prepare("DELETE FROM `##sources` WHERE s_id=? AND s_file=?")->execute(array($gid, $ged_id));
1141
+			    break;
1142
+		    case 'OBJE':
1143
+			    Database::prepare("DELETE FROM `##media` WHERE m_id=? AND m_file=?")->execute(array($gid, $ged_id));
1144
+			    break;
1145
+		    default:
1146
+			    Database::prepare("DELETE FROM `##other` WHERE o_id=? AND o_file=?")->execute(array($gid, $ged_id));
1147
+			    break;
1148 1148
 		}
1149 1149
 
1150 1150
 		if (!$delete) {
Please login to merge, or discard this patch.
Braces   +24 added lines, -12 removed lines patch added patch discarded remove patch
@@ -33,7 +33,8 @@  discard block
 block discarded – undo
33 33
 /**
34 34
  * Class FunctionsImport - common functions
35 35
  */
36
-class FunctionsImport {
36
+class FunctionsImport
37
+{
37 38
 	/**
38 39
 	 * Tidy up a gedcom record on import, so that we can access it consistently/efficiently.
39 40
 	 *
@@ -42,7 +43,8 @@  discard block
 block discarded – undo
42 43
 	 *
43 44
 	 * @return string
44 45
 	 */
45
-	public static function reformatRecord($rec, Tree $tree) {
46
+	public static function reformatRecord($rec, Tree $tree)
47
+	{
46 48
 		// Strip out UTF8 formatting characters
47 49
 		$rec = str_replace(array(WT_UTF8_BOM, WT_UTF8_LRM, WT_UTF8_RLM), '', $rec);
48 50
 
@@ -594,7 +596,8 @@  discard block
 block discarded – undo
594 596
 	 * @param Tree   $tree   import the record into this tree
595 597
 	 * @param bool   $update whether or not this is an updated record that has been accepted
596 598
 	 */
597
-	public static function importRecord($gedrec, Tree $tree, $update) {
599
+	public static function importRecord($gedrec, Tree $tree, $update)
600
+	{
598 601
 		$tree_id = $tree->getTreeId();
599 602
 
600 603
 		// Escaped @ signs (only if importing from file)
@@ -762,7 +765,8 @@  discard block
 block discarded – undo
762 765
 	 * @param int    $ged_id
763 766
 	 * @param string $gedrec
764 767
 	 */
765
-	public static function updatePlaces($gid, $ged_id, $gedrec) {
768
+	public static function updatePlaces($gid, $ged_id, $gedrec)
769
+	{
766 770
 		global $placecache;
767 771
 
768 772
 		if (!isset($placecache)) {
@@ -854,7 +858,8 @@  discard block
 block discarded – undo
854 858
 	 * @param int    $ged_id
855 859
 	 * @param string $gedrec
856 860
 	 */
857
-	public static function updateDates($xref, $ged_id, $gedrec) {
861
+	public static function updateDates($xref, $ged_id, $gedrec)
862
+	{
858 863
 		if (strpos($gedrec, '2 DATE ') && preg_match_all("/\n1 (\w+).*(?:\n[2-9].*)*(?:\n2 DATE (.+))(?:\n[2-9].*)*/", $gedrec, $matches, PREG_SET_ORDER)) {
859 864
 			foreach ($matches as $match) {
860 865
 				$fact = $match[1];
@@ -903,7 +908,8 @@  discard block
 block discarded – undo
903 908
 	 * @param int    $ged_id
904 909
 	 * @param string $gedrec
905 910
 	 */
906
-	public static function updateLinks($xref, $ged_id, $gedrec) {
911
+	public static function updateLinks($xref, $ged_id, $gedrec)
912
+	{
907 913
 		if (preg_match_all('/^\d+ (' . WT_REGEX_TAG . ') @(' . WT_REGEX_XREF . ')@/m', $gedrec, $matches, PREG_SET_ORDER)) {
908 914
 			$data = array();
909 915
 			foreach ($matches as $match) {
@@ -932,7 +938,8 @@  discard block
 block discarded – undo
932 938
 	 * @param int          $ged_id
933 939
 	 * @param GedcomRecord $record
934 940
 	 */
935
-	public static function updateNames($xref, $ged_id, GedcomRecord $record) {
941
+	public static function updateNames($xref, $ged_id, GedcomRecord $record)
942
+	{
936 943
 		foreach ($record->getAllNames() as $n => $name) {
937 944
 			if ($record instanceof Individual) {
938 945
 				if ($name['givn'] === '@P.N.') {
@@ -972,7 +979,8 @@  discard block
 block discarded – undo
972 979
 	 *
973 980
 	 * @return string
974 981
 	 */
975
-	public static function convertInlineMedia(Tree $tree, $gedrec) {
982
+	public static function convertInlineMedia(Tree $tree, $gedrec)
983
+	{
976 984
 		while (preg_match('/\n1 OBJE(?:\n[2-9].+)+/', $gedrec, $match)) {
977 985
 			$gedrec = str_replace($match[0], self::createMediaObject(1, $match[0], $tree), $gedrec);
978 986
 		}
@@ -995,7 +1003,8 @@  discard block
 block discarded – undo
995 1003
 	 *
996 1004
 	 * @return string
997 1005
 	 */
998
-	public static function createMediaObject($level, $gedrec, Tree $tree) {
1006
+	public static function createMediaObject($level, $gedrec, Tree $tree)
1007
+	{
999 1008
 		if (preg_match('/\n\d FILE (.+)/', $gedrec, $file_match)) {
1000 1009
 			$file = $file_match[1];
1001 1010
 		} else {
@@ -1045,7 +1054,8 @@  discard block
 block discarded – undo
1045 1054
 	 * @param string $xref
1046 1055
 	 * @param int    $ged_id
1047 1056
 	 */
1048
-	public static function acceptAllChanges($xref, $ged_id) {
1057
+	public static function acceptAllChanges($xref, $ged_id)
1058
+	{
1049 1059
 		$changes = Database::prepare(
1050 1060
 			"SELECT change_id, gedcom_name, old_gedcom, new_gedcom" .
1051 1061
 			" FROM `##change` c" .
@@ -1075,7 +1085,8 @@  discard block
 block discarded – undo
1075 1085
 	 *
1076 1086
 	 * @param GedcomRecord $record
1077 1087
 	 */
1078
-	public static function rejectAllChanges(GedcomRecord $record) {
1088
+	public static function rejectAllChanges(GedcomRecord $record)
1089
+	{
1079 1090
 		Database::prepare(
1080 1091
 			"UPDATE `##change`" .
1081 1092
 			" SET status = 'rejected'" .
@@ -1093,7 +1104,8 @@  discard block
 block discarded – undo
1093 1104
 	 * @param int    $ged_id
1094 1105
 	 * @param bool   $delete
1095 1106
 	 */
1096
-	public static function updateRecord($gedrec, $ged_id, $delete) {
1107
+	public static function updateRecord($gedrec, $ged_id, $delete)
1108
+	{
1097 1109
 		if (preg_match('/^0 @(' . WT_REGEX_XREF . ')@ (' . WT_REGEX_TAG . ')/', $gedrec, $match)) {
1098 1110
 			list(, $gid, $type) = $match;
1099 1111
 		} elseif (preg_match('/^0 (HEAD)(?:\n|$)/', $gedrec, $match)) {
Please login to merge, or discard this patch.
app/Functions/FunctionsPrintFacts.php 3 patches
Indentation   +1150 added lines, -1150 removed lines patch added patch discarded remove patch
@@ -41,136 +41,136 @@  discard block
 block discarded – undo
41 41
  * Class FunctionsPrintFacts - common functions
42 42
  */
43 43
 class FunctionsPrintFacts {
44
-	/**
45
-	 * Print a fact record, for the individual/family/source/repository/etc. pages.
46
-	 *
47
-	 * Although a Fact has a parent object, we also need to know
48
-	 * the GedcomRecord for which we are printing it. For example,
49
-	 * we can show the death of X on the page of Y, or the marriage
50
-	 * of X+Y on the page of Z. We need to know both records to
51
-	 * calculate ages, relationships, etc.
52
-	 *
53
-	 * @param Fact $fact
54
-	 * @param GedcomRecord $record
55
-	 */
56
-	public static function printFact(Fact $fact, GedcomRecord $record) {
57
-		static $n_chil = 0, $n_gchi = 0;
58
-
59
-		$parent = $fact->getParent();
60
-
61
-		// Some facts don't get printed here ...
62
-		switch ($fact->getTag()) {
63
-		case 'NOTE':
64
-			self::printMainNotes($fact, 1);
65
-
66
-			return;
67
-		case 'SOUR':
68
-			self::printMainSources($fact, 1);
69
-
70
-			return;
71
-		case 'OBJE':
72
-			self::printMainMedia($fact, 1);
73
-
74
-			return;
75
-		case 'FAMC':
76
-		case 'FAMS':
77
-		case 'CHIL':
78
-		case 'HUSB':
79
-		case 'WIFE':
80
-			// These are internal links, not facts
81
-			return;
82
-		case '_WT_OBJE_SORT':
83
-			// These links are used internally to record the sort order.
84
-			return;
85
-		default:
86
-			// Hide unrecognized/custom tags?
87
-			if ($fact->getParent()->getTree()->getPreference('HIDE_GEDCOM_ERRORS') === '0' && !GedcomTag::isTag($fact->getTag())) {
88
-				return;
89
-			}
90
-			break;
91
-		}
92
-
93
-		// Who is this fact about? Need it to translate fact label correctly
94
-		if ($parent instanceof Family && $record instanceof Individual) {
95
-			// Family event
96
-			$label_person = $fact->getParent()->getSpouse($record);
97
-		} else {
98
-			// Individual event
99
-			$label_person = $parent;
100
-		}
101
-
102
-		// New or deleted facts need different styling
103
-		$styleadd = '';
104
-		if ($fact->isPendingAddition()) {
105
-			$styleadd = 'new';
106
-		}
107
-		if ($fact->isPendingDeletion()) {
108
-			$styleadd = 'old';
109
-		}
110
-
111
-		// Event of close relative
112
-		if (preg_match('/^_[A-Z_]{3,5}_[A-Z0-9]{4}$/', $fact->getTag())) {
113
-			$styleadd = trim($styleadd . ' rela');
114
-		}
115
-
116
-		// Event of close associates
117
-		if ($fact->getFactId() == 'asso') {
118
-			$styleadd = trim($styleadd . ' rela');
119
-		}
120
-
121
-		// historical facts
122
-		if ($fact->getFactId() == 'histo') {
123
-			$styleadd = trim($styleadd . ' histo');
124
-		}
125
-
126
-		// Does this fact have a type?
127
-		if (preg_match('/\n2 TYPE (.+)/', $fact->getGedcom(), $match)) {
128
-			$type = $match[1];
129
-		} else {
130
-			$type = '';
131
-		}
132
-
133
-		switch ($fact->getTag()) {
134
-		case 'EVEN':
135
-		case 'FACT':
136
-			if (GedcomTag::isTag($type)) {
137
-				// Some users (just Meliza?) use "1 EVEN/2 TYPE BIRT". Translate the TYPE.
138
-				$label = GedcomTag::getLabel($type, $label_person);
139
-				$type  = ''; // Do not print this again
140
-			} elseif ($type) {
141
-				// We don't have a translation for $type - but a custom translation might exist.
142
-				$label = I18N::translate(Filter::escapeHtml($type));
143
-				$type  = ''; // Do not print this again
144
-			} else {
145
-				// An unspecified fact/event
146
-				$label = $fact->getLabel();
147
-			}
148
-			break;
149
-		case 'MARR':
150
-			// This is a hack for a proprietory extension. Is it still used/needed?
151
-			$utype = strtoupper($type);
152
-			if ($utype == 'CIVIL' || $utype == 'PARTNERS' || $utype == 'RELIGIOUS') {
153
-				$label = GedcomTag::getLabel('MARR_' . $utype, $label_person);
154
-				$type  = ''; // Do not print this again
155
-			} else {
156
-				$label = $fact->getLabel();
157
-			}
158
-			break;
159
-		default:
160
-			// Normal fact/event
161
-			$label = $fact->getLabel();
162
-			break;
163
-		}
164
-
165
-		echo '<tr class="', $styleadd, '">';
166
-		echo '<td class="descriptionbox width20">';
167
-
168
-		if ($fact->getParent()->getTree()->getPreference('SHOW_FACT_ICONS')) {
169
-			echo Theme::theme()->icon($fact), ' ';
170
-		}
171
-
172
-		if ($fact->getFactId() != 'histo' && $fact->canEdit()) {
173
-			?>
44
+    /**
45
+     * Print a fact record, for the individual/family/source/repository/etc. pages.
46
+     *
47
+     * Although a Fact has a parent object, we also need to know
48
+     * the GedcomRecord for which we are printing it. For example,
49
+     * we can show the death of X on the page of Y, or the marriage
50
+     * of X+Y on the page of Z. We need to know both records to
51
+     * calculate ages, relationships, etc.
52
+     *
53
+     * @param Fact $fact
54
+     * @param GedcomRecord $record
55
+     */
56
+    public static function printFact(Fact $fact, GedcomRecord $record) {
57
+        static $n_chil = 0, $n_gchi = 0;
58
+
59
+        $parent = $fact->getParent();
60
+
61
+        // Some facts don't get printed here ...
62
+        switch ($fact->getTag()) {
63
+        case 'NOTE':
64
+            self::printMainNotes($fact, 1);
65
+
66
+            return;
67
+        case 'SOUR':
68
+            self::printMainSources($fact, 1);
69
+
70
+            return;
71
+        case 'OBJE':
72
+            self::printMainMedia($fact, 1);
73
+
74
+            return;
75
+        case 'FAMC':
76
+        case 'FAMS':
77
+        case 'CHIL':
78
+        case 'HUSB':
79
+        case 'WIFE':
80
+            // These are internal links, not facts
81
+            return;
82
+        case '_WT_OBJE_SORT':
83
+            // These links are used internally to record the sort order.
84
+            return;
85
+        default:
86
+            // Hide unrecognized/custom tags?
87
+            if ($fact->getParent()->getTree()->getPreference('HIDE_GEDCOM_ERRORS') === '0' && !GedcomTag::isTag($fact->getTag())) {
88
+                return;
89
+            }
90
+            break;
91
+        }
92
+
93
+        // Who is this fact about? Need it to translate fact label correctly
94
+        if ($parent instanceof Family && $record instanceof Individual) {
95
+            // Family event
96
+            $label_person = $fact->getParent()->getSpouse($record);
97
+        } else {
98
+            // Individual event
99
+            $label_person = $parent;
100
+        }
101
+
102
+        // New or deleted facts need different styling
103
+        $styleadd = '';
104
+        if ($fact->isPendingAddition()) {
105
+            $styleadd = 'new';
106
+        }
107
+        if ($fact->isPendingDeletion()) {
108
+            $styleadd = 'old';
109
+        }
110
+
111
+        // Event of close relative
112
+        if (preg_match('/^_[A-Z_]{3,5}_[A-Z0-9]{4}$/', $fact->getTag())) {
113
+            $styleadd = trim($styleadd . ' rela');
114
+        }
115
+
116
+        // Event of close associates
117
+        if ($fact->getFactId() == 'asso') {
118
+            $styleadd = trim($styleadd . ' rela');
119
+        }
120
+
121
+        // historical facts
122
+        if ($fact->getFactId() == 'histo') {
123
+            $styleadd = trim($styleadd . ' histo');
124
+        }
125
+
126
+        // Does this fact have a type?
127
+        if (preg_match('/\n2 TYPE (.+)/', $fact->getGedcom(), $match)) {
128
+            $type = $match[1];
129
+        } else {
130
+            $type = '';
131
+        }
132
+
133
+        switch ($fact->getTag()) {
134
+        case 'EVEN':
135
+        case 'FACT':
136
+            if (GedcomTag::isTag($type)) {
137
+                // Some users (just Meliza?) use "1 EVEN/2 TYPE BIRT". Translate the TYPE.
138
+                $label = GedcomTag::getLabel($type, $label_person);
139
+                $type  = ''; // Do not print this again
140
+            } elseif ($type) {
141
+                // We don't have a translation for $type - but a custom translation might exist.
142
+                $label = I18N::translate(Filter::escapeHtml($type));
143
+                $type  = ''; // Do not print this again
144
+            } else {
145
+                // An unspecified fact/event
146
+                $label = $fact->getLabel();
147
+            }
148
+            break;
149
+        case 'MARR':
150
+            // This is a hack for a proprietory extension. Is it still used/needed?
151
+            $utype = strtoupper($type);
152
+            if ($utype == 'CIVIL' || $utype == 'PARTNERS' || $utype == 'RELIGIOUS') {
153
+                $label = GedcomTag::getLabel('MARR_' . $utype, $label_person);
154
+                $type  = ''; // Do not print this again
155
+            } else {
156
+                $label = $fact->getLabel();
157
+            }
158
+            break;
159
+        default:
160
+            // Normal fact/event
161
+            $label = $fact->getLabel();
162
+            break;
163
+        }
164
+
165
+        echo '<tr class="', $styleadd, '">';
166
+        echo '<td class="descriptionbox width20">';
167
+
168
+        if ($fact->getParent()->getTree()->getPreference('SHOW_FACT_ICONS')) {
169
+            echo Theme::theme()->icon($fact), ' ';
170
+        }
171
+
172
+        if ($fact->getFactId() != 'histo' && $fact->canEdit()) {
173
+            ?>
174 174
 			<a
175 175
 				href="#"
176 176
 				title="<?php echo I18N::translate('Edit'); ?>"
@@ -203,1024 +203,1024 @@  discard block
 block discarded – undo
203 203
 				</div>
204 204
 			</div>
205 205
 		<?php
206
-		} else {
207
-			echo $label;
208
-		}
209
-
210
-		switch ($fact->getTag()) {
211
-		case '_BIRT_CHIL':
212
-			echo '<br>', /* I18N: Abbreviation for "number %s" */
213
-			I18N::translate('#%s', ++$n_chil);
214
-			break;
215
-		case '_BIRT_GCHI':
216
-		case '_BIRT_GCH1':
217
-		case '_BIRT_GCH2':
218
-			echo '<br>', I18N::translate('#%s', ++$n_gchi);
219
-			break;
220
-		}
221
-
222
-		echo '</td><td class="optionbox ', $styleadd, ' wrap">';
223
-
224
-		// Event from another record?
225
-		if ($parent !== $record) {
226
-			if ($parent instanceof Family) {
227
-				foreach ($parent->getSpouses() as $spouse) {
228
-					if ($record !== $spouse) {
229
-						echo '<a href="', $spouse->getHtmlUrl(), '">', $spouse->getFullName(), '</a> — ';
230
-					}
231
-				}
232
-				echo '<a href="', $parent->getHtmlUrl(), '">', I18N::translate('View this family'), '</a><br>';
233
-			} elseif ($parent instanceof Individual) {
234
-				echo '<a href="', $parent->getHtmlUrl(), '">', $parent->getFullName(), '</a><br>';
235
-			}
236
-		}
237
-
238
-		// Print the value of this fact/event
239
-		switch ($fact->getTag()) {
240
-		case 'ADDR':
241
-			echo $fact->getValue();
242
-			break;
243
-		case 'AFN':
244
-			echo '<div class="field"><a href="https://familysearch.org/search/tree/results#count=20&query=afn:', Filter::escapeUrl($fact->getValue()), '">', Filter::escapeHtml($fact->getValue()), '</a></div>';
245
-			break;
246
-		case 'ASSO':
247
-			// we handle this later, in format_asso_rela_record()
248
-			break;
249
-		case 'EMAIL':
250
-		case 'EMAI':
251
-		case '_EMAIL':
252
-			echo '<div class="field"><a href="mailto:', Filter::escapeHtml($fact->getValue()), '">', Filter::escapeHtml($fact->getValue()), '</a></div>';
253
-			break;
254
-		case 'FILE':
255
-			if (Auth::isEditor($fact->getParent()->getTree())) {
256
-				echo '<div class="field">', Filter::escapeHtml($fact->getValue()), '</div>';
257
-			}
258
-			break;
259
-		case 'RESN':
260
-			echo '<div class="field">';
261
-			switch ($fact->getValue()) {
262
-			case 'none':
263
-			// Note: "1 RESN none" is not valid gedcom.
264
-			// However, webtrees privacy rules will interpret it as "show an otherwise private record to public".
265
-			echo '<i class="icon-resn-none"></i> ', I18N::translate('Show to visitors');
266
-			break;
267
-			case 'privacy':
268
-			echo '<i class="icon-class-none"></i> ', I18N::translate('Show to members');
269
-			break;
270
-			case 'confidential':
271
-			echo '<i class="icon-confidential-none"></i> ', I18N::translate('Show to managers');
272
-			break;
273
-			case 'locked':
274
-			echo '<i class="icon-locked-none"></i> ', I18N::translate('Only managers can edit');
275
-			break;
276
-			default:
277
-			echo Filter::escapeHtml($fact->getValue());
278
-			break;
279
-			}
280
-				echo '</div>';
281
-				break;
282
-		case 'PUBL': // Publication details might contain URLs.
283
-			echo '<div class="field">', Filter::expandUrls($fact->getValue()), '</div>';
284
-			break;
285
-		case 'REPO':
286
-			if (preg_match('/^@(' . WT_REGEX_XREF . ')@$/', $fact->getValue(), $match)) {
287
-				self::printRepositoryRecord($match[1]);
288
-			} else {
289
-				echo '<div class="error">', Filter::escapeHtml($fact->getValue()), '</div>';
290
-			}
291
-			break;
292
-		case 'URL':
293
-		case '_URL':
294
-		case 'WWW':
295
-			echo '<div class="field"><a href="', Filter::escapeHtml($fact->getValue()), '">', Filter::escapeHtml($fact->getValue()), '</a></div>';
296
-			break;
297
-		case 'TEXT': // 0 SOUR / 1 TEXT
298
-			echo '<div class="field">', nl2br(Filter::escapeHtml($fact->getValue()), false), '</div>';
299
-			break;
300
-		default:
301
-			// Display the value for all other facts/events
302
-			switch ($fact->getValue()) {
303
-			case '':
304
-			// Nothing to display
305
-			break;
306
-			case 'N':
307
-			// Not valid GEDCOM
308
-			echo '<div class="field">', I18N::translate('No'), '</div>';
309
-			break;
310
-			case 'Y':
311
-			// Do not display "Yes".
312
-			break;
313
-			default:
314
-			if (preg_match('/^@(' . WT_REGEX_XREF . ')@$/', $fact->getValue(), $match)) {
315
-			$target = GedcomRecord::getInstance($match[1], $fact->getParent()->getTree());
316
-			if ($target) {
317
-				echo '<div><a href="', $target->getHtmlUrl(), '">', $target->getFullName(), '</a></div>';
318
-			} else {
319
-				echo '<div class="error">', Filter::escapeHtml($fact->getValue()), '</div>';
320
-			}
321
-			} else {
322
-			echo '<div class="field"><span dir="auto">', Filter::escapeHtml($fact->getValue()), '</span></div>';
323
-			}
324
-			break;
325
-			}
326
-				break;
327
-		}
328
-
329
-		// Print the type of this fact/event
330
-		if ($type) {
331
-			$utype = strtoupper($type);
332
-			// Events of close relatives, e.g. _MARR_CHIL
333
-			if (substr($fact->getTag(), 0, 6) == '_MARR_' && ($utype == 'CIVIL' || $utype == 'PARTNERS' || $utype == 'RELIGIOUS')) {
334
-				// Translate MARR/TYPE using the code that supports MARR_CIVIL, etc. tags
335
-				$type = GedcomTag::getLabel('MARR_' . $utype);
336
-			} else {
337
-				// Allow (custom) translations for other types
338
-				$type = I18N::translate($type);
339
-			}
340
-			echo GedcomTag::getLabelValue('TYPE', Filter::escapeHtml($type));
341
-		}
342
-
343
-		// Print the date of this fact/event
344
-		echo FunctionsPrint::formatFactDate($fact, $record, true, true);
345
-
346
-		// Print the place of this fact/event
347
-		echo '<div class="place">', FunctionsPrint::formatFactPlace($fact, true, true, true), '</div>';
348
-		// A blank line between the primary attributes (value, date, place) and the secondary ones
349
-		echo '<br>';
350
-
351
-		$addr = $fact->getAttribute('ADDR');
352
-		if ($addr) {
353
-			echo GedcomTag::getLabelValue('ADDR', $addr);
354
-		}
355
-
356
-		// Print the associates of this fact/event
357
-		if ($fact->getFactId() !== 'asso') {
358
-			echo self::formatAssociateRelationship($fact);
359
-		}
360
-
361
-		// Print any other "2 XXXX" attributes, in the order in which they appear.
362
-		preg_match_all('/\n2 (' . WT_REGEX_TAG . ') (.+)/', $fact->getGedcom(), $matches, PREG_SET_ORDER);
363
-		foreach ($matches as $match) {
364
-			switch ($match[1]) {
365
-			case 'DATE':
366
-			case 'TIME':
367
-			case 'AGE':
368
-			case 'PLAC':
369
-			case 'ADDR':
370
-			case 'ALIA':
371
-			case 'ASSO':
372
-			case '_ASSO':
373
-			case 'DESC':
374
-			case 'RELA':
375
-			case 'STAT':
376
-			case 'TEMP':
377
-			case 'TYPE':
378
-			case 'FAMS':
379
-			case 'CONT':
380
-				// These were already shown at the beginning
381
-				break;
382
-			case 'NOTE':
383
-			case 'OBJE':
384
-			case 'SOUR':
385
-				// These will be shown at the end
386
-				break;
387
-			case '_UID':
388
-			case 'RIN':
389
-				// These don't belong at level 2, so do not display them.
390
-				// They are only shown when editing.
391
-				break;
392
-			case 'EVEN': // 0 SOUR / 1 DATA / 2 EVEN / 3 DATE / 3 PLAC
393
-				$events = array();
394
-				foreach (preg_split('/ *, */', $match[2]) as $event) {
395
-					$events[] = GedcomTag::getLabel($event);
396
-				}
397
-				if (count($events) == 1) {
398
-					echo GedcomTag::getLabelValue('EVEN', $event);
399
-				} else {
400
-					echo GedcomTag::getLabelValue('EVEN', implode(I18N::$list_separator, $events));
401
-				}
402
-				if (preg_match('/\n3 DATE (.+)/', $fact->getGedcom(), $date_match)) {
403
-					$date = new Date($date_match[1]);
404
-					echo GedcomTag::getLabelValue('DATE', $date->display());
405
-				}
406
-				if (preg_match('/\n3 PLAC (.+)/', $fact->getGedcom(), $plac_match)) {
407
-					echo GedcomTag::getLabelValue('PLAC', $plac_match[1]);
408
-				}
409
-				break;
410
-			case 'FAMC': // 0 INDI / 1 ADOP / 2 FAMC / 3 ADOP
411
-				$family = Family::getInstance(str_replace('@', '', $match[2]), $fact->getParent()->getTree());
412
-				if ($family) {
413
-					echo GedcomTag::getLabelValue('FAM', '<a href="' . $family->getHtmlUrl() . '">' . $family->getFullName() . '</a>');
414
-					if (preg_match('/\n3 ADOP (HUSB|WIFE|BOTH)/', $fact->getGedcom(), $match)) {
415
-						echo GedcomTag::getLabelValue('ADOP', GedcomCodeAdop::getValue($match[1], $label_person));
416
-					}
417
-				} else {
418
-					echo GedcomTag::getLabelValue('FAM', '<span class="error">' . $match[2] . '</span>');
419
-				}
420
-				break;
421
-			case '_WT_USER':
422
-				$user = User::findByIdentifier($match[2]); // may not exist
423
-				if ($user) {
424
-					echo GedcomTag::getLabelValue('_WT_USER', $user->getRealNameHtml());
425
-				} else {
426
-					echo GedcomTag::getLabelValue('_WT_USER', Filter::escapeHtml($match[2]));
427
-				}
428
-				break;
429
-			case 'RESN':
430
-				switch ($match[2]) {
431
-				case 'none':
432
-				// Note: "2 RESN none" is not valid gedcom.
433
-				// However, webtrees privacy rules will interpret it as "show an otherwise private fact to public".
434
-				echo GedcomTag::getLabelValue('RESN', '<i class="icon-resn-none"></i> ' . I18N::translate('Show to visitors'));
435
-				break;
436
-				case 'privacy':
437
-				echo GedcomTag::getLabelValue('RESN', '<i class="icon-resn-privacy"></i> ' . I18N::translate('Show to members'));
438
-				break;
439
-				case 'confidential':
440
-				echo GedcomTag::getLabelValue('RESN', '<i class="icon-resn-confidential"></i> ' . I18N::translate('Show to managers'));
441
-				break;
442
-				case 'locked':
443
-				echo GedcomTag::getLabelValue('RESN', '<i class="icon-resn-locked"></i> ' . I18N::translate('Only managers can edit'));
444
-				break;
445
-				default:
446
-				echo GedcomTag::getLabelValue('RESN', Filter::escapeHtml($match[2]));
447
-				break;
448
-				}
449
-					break;
450
-			case 'CALN':
451
-				echo GedcomTag::getLabelValue('CALN', Filter::expandUrls($match[2]));
452
-				break;
453
-			case 'FORM': // 0 OBJE / 1 FILE / 2 FORM / 3 TYPE
454
-				echo GedcomTag::getLabelValue('FORM', $match[2]);
455
-				if (preg_match('/\n3 TYPE (.+)/', $fact->getGedcom(), $type_match)) {
456
-					echo GedcomTag::getLabelValue('TYPE', GedcomTag::getFileFormTypeValue($type_match[1]));
457
-				}
458
-				break;
459
-			case 'URL':
460
-			case '_URL':
461
-			case 'WWW':
462
-				$link = '<a href="' . Filter::escapeHtml($match[2]) . '">' . Filter::escapeHtml($match[2]) . '</a>';
463
-				echo GedcomTag::getLabelValue($fact->getTag() . ':' . $match[1], $link);
464
-				break;
465
-			default:
466
-				if ($fact->getParent()->getTree()->getPreference('HIDE_GEDCOM_ERRORS') === '1' || GedcomTag::isTag($match[1])) {
467
-					if (preg_match('/^@(' . WT_REGEX_XREF . ')@$/', $match[2], $xmatch)) {
468
-						// Links
469
-						$linked_record = GedcomRecord::getInstance($xmatch[1], $fact->getParent()->getTree());
470
-						if ($linked_record) {
471
-							$link = '<a href="' . $linked_record->getHtmlUrl() . '">' . $linked_record->getFullName() . '</a>';
472
-							echo GedcomTag::getLabelValue($fact->getTag() . ':' . $match[1], $link);
473
-						} else {
474
-							echo GedcomTag::getLabelValue($fact->getTag() . ':' . $match[1], Filter::escapeHtml($match[2]));
475
-						}
476
-					} else {
477
-						// Non links
478
-						echo GedcomTag::getLabelValue($fact->getTag() . ':' . $match[1], Filter::escapeHtml($match[2]));
479
-					}
480
-				}
481
-				break;
482
-			}
483
-		}
484
-		echo self::printFactSources($fact->getGedcom(), 2);
485
-		echo FunctionsPrint::printFactNotes($fact->getGedcom(), 2);
486
-		self::printMediaLinks($fact->getGedcom(), 2);
487
-		echo '</td></tr>';
488
-	}
489
-
490
-	/**
491
-	 * Print the associations from the associated individuals in $event to the individuals in $record
492
-	 *
493
-	 * @param Fact $event
494
-	 *
495
-	 * @return string
496
-	 */
497
-	private static function formatAssociateRelationship(Fact $event) {
498
-		$parent = $event->getParent();
499
-		// To whom is this record an assocate?
500
-		if ($parent instanceof Individual) {
501
-			// On an individual page, we just show links to the person
502
-			$associates = array($parent);
503
-		} elseif ($parent instanceof Family) {
504
-			// On a family page, we show links to both spouses
505
-			$associates = $parent->getSpouses();
506
-		} else {
507
-			// On other pages, it does not make sense to show associates
508
-			return '';
509
-		}
510
-
511
-		preg_match_all('/^1 ASSO @(' . WT_REGEX_XREF . ')@((\n[2-9].*)*)/', $event->getGedcom(), $amatches1, PREG_SET_ORDER);
512
-		preg_match_all('/\n2 _?ASSO @(' . WT_REGEX_XREF . ')@((\n[3-9].*)*)/', $event->getGedcom(), $amatches2, PREG_SET_ORDER);
513
-
514
-		$html = '';
515
-		// For each ASSO record
516
-		foreach (array_merge($amatches1, $amatches2) as $amatch) {
517
-			$person = Individual::getInstance($amatch[1], $event->getParent()->getTree());
518
-			if ($person && $person->canShowName()) {
519
-				// Is there a "RELA" tag
520
-				if (preg_match('/\n[23] RELA (.+)/', $amatch[2], $rmatch)) {
521
-					// Use the supplied relationship as a label
522
-					$label = GedcomCodeRela::getValue($rmatch[1], $person);
523
-				} else {
524
-					// Use a default label
525
-					$label = GedcomTag::getLabel('ASSO', $person);
526
-				}
527
-
528
-				$values = array('<a href="' . $person->getHtmlUrl() . '">' . $person->getFullName() . '</a>');
529
-				foreach ($associates as $associate) {
530
-					$relationship_name = Functions::getAssociateRelationshipName($associate, $person);
531
-					if (!$relationship_name) {
532
-						$relationship_name = GedcomTag::getLabel('RELA');
533
-					}
534
-
535
-					if ($parent instanceof Family) {
536
-						// For family ASSO records (e.g. MARR), identify the spouse with a sex icon
537
-						$relationship_name .= $associate->getSexImage();
538
-					}
539
-
540
-					$values[] = '<a href="relationship.php?pid1=' . $associate->getXref() . '&amp;pid2=' . $person->getXref() . '&amp;ged=' . $associate->getTree()->getNameUrl() . '" rel="nofollow">' . $relationship_name . '</a>';
541
-				}
542
-				$value = implode(' — ', $values);
543
-
544
-				// Use same markup as GedcomTag::getLabelValue()
545
-				$asso = I18N::translate('<span class="label">%1$s:</span> <span class="field" dir="auto">%2$s</span>', $label, $value);
546
-			} elseif (!$person && Auth::isEditor($event->getParent()->getTree())) {
547
-				$asso = GedcomTag::getLabelValue('ASSO', '<span class="error">' . $amatch[1] . '</span>');
548
-			} else {
549
-				$asso = '';
550
-			}
551
-			$html .= '<div class="fact_ASSO">' . $asso . '</div>';
552
-		}
553
-
554
-		return $html;
555
-	}
556
-
557
-	/**
558
-	 * print a repository record
559
-	 *
560
-	 * find and print repository information attached to a source
561
-	 *
562
-	 * @param string $xref the Gedcom Xref ID of the repository to print
563
-	 */
564
-	public static function printRepositoryRecord($xref) {
565
-		global $WT_TREE;
566
-
567
-		$repository = Repository::getInstance($xref, $WT_TREE);
568
-		if ($repository && $repository->canShow()) {
569
-			echo '<a class="field" href="', $repository->getHtmlUrl(), '">', $repository->getFullName(), '</a><br>';
570
-			echo '<br>';
571
-			echo FunctionsPrint::printFactNotes($repository->getGedcom(), 1);
572
-		}
573
-	}
574
-
575
-	/**
576
-	 * print a source linked to a fact (2 SOUR)
577
-	 *
578
-	 * this function is called by the FunctionsPrintFacts::print_fact function and other functions to
579
-	 * print any source information attached to the fact
580
-	 *
581
-	 * @param string $factrec The fact record to look for sources in
582
-	 * @param int $level The level to look for sources at
583
-	 *
584
-	 * @return string HTML text
585
-	 */
586
-	public static function printFactSources($factrec, $level) {
587
-		global $WT_TREE;
588
-
589
-		$data   = '';
590
-		$nlevel = $level + 1;
591
-
592
-		// Systems not using source records
593
-		// The old style is not supported when entering or editing sources, but may be found in imported trees.
594
-		// Also, the old style sources allow histo.* files to use tree independent source citations, which
595
-		// will display nicely when markdown is used.
596
-		$ct = preg_match_all('/' . $level . ' SOUR (.*)((?:\n\d CONT.*)*)/', $factrec, $match, PREG_SET_ORDER);
597
-		for ($j = 0; $j < $ct; $j++) {
598
-			if (strpos($match[$j][1], '@') === false) {
599
-				$source = Filter::escapeHtml($match[$j][1] . preg_replace('/\n\d CONT ?/', "\n", $match[$j][2]));
600
-				$data .= '<div class="fact_SOUR"><span class="label">' . I18N::translate('Source') . ':</span> <span class="field" dir="auto">' . Filter::formatText($source, $WT_TREE) . '</span></div>';
601
-			}
602
-		}
603
-		// Find source for each fact
604
-		$ct    = preg_match_all("/$level SOUR @(.*)@/", $factrec, $match, PREG_SET_ORDER);
605
-		$spos2 = 0;
606
-		for ($j = 0; $j < $ct; $j++) {
607
-			$sid    = $match[$j][1];
608
-			$source = Source::getInstance($sid, $WT_TREE);
609
-			if ($source) {
610
-				if ($source->canShow()) {
611
-					$spos1 = strpos($factrec, "$level SOUR @" . $sid . "@", $spos2);
612
-					$spos2 = strpos($factrec, "\n$level", $spos1);
613
-					if (!$spos2) {
614
-						$spos2 = strlen($factrec);
615
-					}
616
-					$srec = substr($factrec, $spos1, $spos2 - $spos1);
617
-					$lt   = preg_match_all("/$nlevel \w+/", $srec, $matches);
618
-					$data .= '<div class="fact_SOUR">';
619
-					$elementID = Uuid::uuid4();
620
-					if ($WT_TREE->getPreference('EXPAND_SOURCES')) {
621
-						$plusminus = 'icon-minus';
622
-					} else {
623
-						$plusminus = 'icon-plus';
624
-					}
625
-					if ($lt > 0) {
626
-						$data .= '<a href="#" onclick="return expand_layer(\'' . $elementID . '\');"><i id="' . $elementID . '_img" class="' . $plusminus . '"></i></a> ';
627
-					}
628
-					$data .= GedcomTag::getLabelValue('SOUR', '<a href="' . $source->getHtmlUrl() . '">' . $source->getFullName() . '</a>', null, 'span');
629
-					$data .= '</div>';
630
-
631
-					$data .= "<div id=\"$elementID\"";
632
-					if ($WT_TREE->getPreference('EXPAND_SOURCES')) {
633
-						$data .= ' style="display:block"';
634
-					}
635
-					$data .= ' class="source_citations">';
636
-					// PUBL
637
-					$publ = $source->getFirstFact('PUBL');
638
-					if ($publ) {
639
-						$data .= GedcomTag::getLabelValue('PUBL', $publ->getValue());
640
-					}
641
-					$data .= self::printSourceStructure(self::getSourceStructure($srec));
642
-					$data .= '<div class="indent">';
643
-					ob_start();
644
-					self::printMediaLinks($srec, $nlevel);
645
-					$data .= ob_get_clean();
646
-					$data .= FunctionsPrint::printFactNotes($srec, $nlevel, false);
647
-					$data .= '</div>';
648
-					$data .= '</div>';
649
-				} else {
650
-					// Here we could show that we do actually have sources for this data,
651
-					// but not the details. For example “Sources: ”.
652
-					// But not by default, based on user feedback.
653
-					// https://webtrees.net/index.php/en/forum/3-help-for-beta-and-svn-versions/27002-source-media-privacy-issue
654
-				}
655
-			} else {
656
-				$data .= GedcomTag::getLabelValue('SOUR', '<span class="error">' . $sid . '</span>');
657
-			}
658
-		}
659
-
660
-		return $data;
661
-	}
662
-
663
-	/**
664
-	 * Print the links to media objects
665
-	 *
666
-	 * @param string $factrec
667
-	 * @param int $level
668
-	 */
669
-	public static function printMediaLinks($factrec, $level) {
670
-		global $WT_TREE;
671
-
672
-		$nlevel = $level + 1;
673
-		if (preg_match_all("/$level OBJE @(.*)@/", $factrec, $omatch, PREG_SET_ORDER) == 0) {
674
-			return;
675
-		}
676
-		$objectNum = 0;
677
-		while ($objectNum < count($omatch)) {
678
-			$media_id = $omatch[$objectNum][1];
679
-			$media    = Media::getInstance($media_id, $WT_TREE);
680
-			if ($media) {
681
-				if ($media->canShow()) {
682
-					if ($objectNum > 0) {
683
-						echo '<br class="media-separator" style="clear:both;">';
684
-					}
685
-					echo '<div class="media-display"><div class="media-display-image">';
686
-					echo $media->displayImage();
687
-					echo '</div>';
688
-					echo '<div class="media-display-title">';
689
-					echo '<a href="', $media->getHtmlUrl(), '">', $media->getFullName(), '</a>';
690
-					// NOTE: echo the notes of the media
691
-					echo '<p>';
692
-					echo FunctionsPrint::printFactNotes($media->getGedcom(), 1);
693
-					$ttype = preg_match("/" . ($nlevel + 1) . " TYPE (.*)/", $media->getGedcom(), $match);
694
-					if ($ttype > 0) {
695
-						$mediaType = GedcomTag::getFileFormTypeValue($match[1]);
696
-						echo '<p class="label">', I18N::translate('Type'), ': </span> <span class="field">', $mediaType, '</p>';
697
-					}
698
-					echo '</p>';
699
-					//-- print spouse name for marriage events
700
-					$ct = preg_match("/WT_SPOUSE: (.*)/", $factrec, $match);
701
-					if ($ct > 0) {
702
-						$spouse = Individual::getInstance($match[1], $media->getTree());
703
-						if ($spouse) {
704
-							echo '<a href="', $spouse->getHtmlUrl(), '">';
705
-							echo $spouse->getFullName();
706
-							echo '</a>';
707
-						}
708
-						$ct = preg_match("/WT_FAMILY_ID: (.*)/", $factrec, $match);
709
-						if ($ct > 0) {
710
-							$famid  = trim($match[1]);
711
-							$family = Family::getInstance($famid, $spouse->getTree());
712
-							if ($family) {
713
-								if ($spouse) {
714
-									echo " - ";
715
-								}
716
-								echo '<a href="', $family->getHtmlUrl(), '">', I18N::translate('View this family'), '</a>';
717
-							}
718
-						}
719
-					}
720
-					echo FunctionsPrint::printFactNotes($media->getGedcom(), $nlevel);
721
-					echo self::printFactSources($media->getGedcom(), $nlevel);
722
-					echo '</div>'; //close div "media-display-title"
723
-					echo '</div>'; //close div "media-display"
724
-				}
725
-			} elseif ($WT_TREE->getPreference('HIDE_GEDCOM_ERRORS') === '1') {
726
-				echo '<p class="ui-state-error">', $media_id, '</p>';
727
-			}
728
-			$objectNum++;
729
-		}
730
-	}
731
-
732
-	/**
733
-	 * Print a row for the sources tab on the individual page.
734
-	 *
735
-	 * @param Fact $fact
736
-	 * @param int $level
737
-	 */
738
-	public static function printMainSources(Fact $fact, $level) {
739
-		$factrec = $fact->getGedcom();
740
-		$fact_id = $fact->getFactId();
741
-		$parent  = $fact->getParent();
742
-		$pid     = $parent->getXref();
743
-
744
-		$nlevel = $level + 1;
745
-		if ($fact->isPendingAddition()) {
746
-			$styleadd = 'new';
747
-			$can_edit = $level == 1 && $fact->canEdit();
748
-		} elseif ($fact->isPendingDeletion()) {
749
-			$styleadd = 'old';
750
-			$can_edit = false;
751
-		} else {
752
-			$styleadd = '';
753
-			$can_edit = $level == 1 && $fact->canEdit();
754
-		}
755
-
756
-		// -- find source for each fact
757
-		$ct    = preg_match_all("/($level SOUR (.+))/", $factrec, $match, PREG_SET_ORDER);
758
-		$spos2 = 0;
759
-		for ($j = 0; $j < $ct; $j++) {
760
-			$sid   = trim($match[$j][2], '@');
761
-			$spos1 = strpos($factrec, $match[$j][1], $spos2);
762
-			$spos2 = strpos($factrec, "\n$level", $spos1);
763
-			if (!$spos2) {
764
-				$spos2 = strlen($factrec);
765
-			}
766
-			$srec   = substr($factrec, $spos1, $spos2 - $spos1);
767
-			$source = Source::getInstance($sid, $fact->getParent()->getTree());
768
-			// Allow access to "1 SOUR @non_existent_source@", so it can be corrected/deleted
769
-			if (!$source || $source->canShow()) {
770
-				if ($level > 1) {
771
-					echo '<tr class="row_sour2">';
772
-				} else {
773
-					echo '<tr>';
774
-				}
775
-				echo '<td class="descriptionbox';
776
-				if ($level > 1) {
777
-					echo ' rela';
778
-				}
779
-				echo ' ', $styleadd, ' width20">';
780
-				$factlines = explode("\n", $factrec); // 1 BIRT Y\n2 SOUR ...
781
-				$factwords = explode(" ", $factlines[0]); // 1 BIRT Y
782
-				$factname  = $factwords[1]; // BIRT
783
-				if ($factname == 'EVEN' || $factname == 'FACT') {
784
-					// Add ' EVEN' to provide sensible output for an event with an empty TYPE record
785
-					$ct = preg_match("/2 TYPE (.*)/", $factrec, $ematch);
786
-					if ($ct > 0) {
787
-						$factname = trim($ematch[1]);
788
-						echo $factname;
789
-					} else {
790
-						echo GedcomTag::getLabel($factname, $parent);
791
-					}
792
-				} elseif ($can_edit) {
793
-					echo "<a onclick=\"return edit_record('$pid', '$fact_id');\" href=\"#\" title=\"", I18N::translate('Edit'), '">';
794
-					if ($fact->getParent()->getTree()->getPreference('SHOW_FACT_ICONS')) {
795
-						if ($level == 1) {
796
-							echo '<i class="icon-source"></i> ';
797
-						}
798
-					}
799
-					echo GedcomTag::getLabel($factname, $parent), '</a>';
800
-					echo '<div class="editfacts">';
801
-					if (preg_match('/^@.+@$/', $match[$j][2])) {
802
-						// Inline sources can't be edited. Attempting to save one will convert it
803
-						// into a link, and delete it.
804
-						// e.g. "1 SOUR my source" becomes "1 SOUR @my source@" which does not exist.
805
-						echo "<div class=\"editlink\"><a class=\"editicon\" onclick=\"return edit_record('$pid', '$fact_id');\" href=\"#\" title=\"" . I18N::translate('Edit') . "\"><span class=\"link_text\">" . I18N::translate('Edit') . "</span></a></div>";
806
-						echo '<div class="copylink"><a class="copyicon" href="#" onclick="return copy_fact(\'', $pid, '\', \'', $fact_id, '\');" title="' . I18N::translate('Copy') . '"><span class="link_text">' . I18N::translate('Copy') . '</span></a></div>';
807
-					}
808
-					echo "<div class=\"deletelink\"><a class=\"deleteicon\" onclick=\"return delete_fact('" . I18N::translate('Are you sure you want to delete this fact?') . "', '$pid', '$fact_id');\" href=\"#\" title=\"" . I18N::translate('Delete') . "\"><span class=\"link_text\">" . I18N::translate('Delete') . "</span></a></div>";
809
-					echo '</div>';
810
-				} else {
811
-					echo GedcomTag::getLabel($factname, $parent);
812
-				}
813
-				echo '</td>';
814
-				echo '<td class="optionbox ', $styleadd, ' wrap">';
815
-				if ($source) {
816
-					echo '<a href="', $source->getHtmlUrl(), '">', $source->getFullName(), '</a>';
817
-					// PUBL
818
-					$publ = $source->getFirstFact('PUBL');
819
-					if ($publ) {
820
-						echo GedcomTag::getLabelValue('PUBL', $publ->getValue());
821
-					}
822
-					// 2 RESN tags. Note, there can be more than one, such as "privacy" and "locked"
823
-					if (preg_match_all("/\n2 RESN (.+)/", $factrec, $rmatches)) {
824
-						foreach ($rmatches[1] as $rmatch) {
825
-							echo '<br><span class="label">', GedcomTag::getLabel('RESN'), ':</span> <span class="field">';
826
-							switch ($rmatch) {
827
-							case 'none':
828
-								// Note: "2 RESN none" is not valid gedcom, and the GUI will not let you add it.
829
-								// However, webtrees privacy rules will interpret it as "show an otherwise private fact to public".
830
-								echo '<i class="icon-resn-none"></i> ', I18N::translate('Show to visitors');
831
-								break;
832
-							case 'privacy':
833
-								echo '<i class="icon-resn-privacy"></i> ', I18N::translate('Show to members');
834
-								break;
835
-							case 'confidential':
836
-								echo '<i class="icon-resn-confidential"></i> ', I18N::translate('Show to managers');
837
-								break;
838
-							case 'locked':
839
-								echo '<i class="icon-resn-locked"></i> ', I18N::translate('Only managers can edit');
840
-								break;
841
-							default:
842
-								echo $rmatch;
843
-								break;
844
-							}
845
-							echo '</span>';
846
-						}
847
-					}
848
-					$cs = preg_match("/$nlevel EVEN (.*)/", $srec, $cmatch);
849
-					if ($cs > 0) {
850
-						echo '<br><span class="label">', GedcomTag::getLabel('EVEN'), ' </span><span class="field">', $cmatch[1], '</span>';
851
-						$cs = preg_match("/" . ($nlevel + 1) . " ROLE (.*)/", $srec, $cmatch);
852
-						if ($cs > 0) {
853
-							echo '<br>&nbsp;&nbsp;&nbsp;&nbsp;<span class="label">', GedcomTag::getLabel('ROLE'), ' </span><span class="field">', $cmatch[1], '</span>';
854
-						}
855
-					}
856
-					echo self::printSourceStructure(self::getSourceStructure($srec));
857
-					echo '<div class="indent">';
858
-					self::printMediaLinks($srec, $nlevel);
859
-					if ($nlevel == 2) {
860
-						self::printMediaLinks($source->getGedcom(), 1);
861
-					}
862
-					echo FunctionsPrint::printFactNotes($srec, $nlevel);
863
-					if ($nlevel == 2) {
864
-						echo FunctionsPrint::printFactNotes($source->getGedcom(), 1);
865
-					}
866
-					echo '</div>';
867
-				} else {
868
-					echo $sid;
869
-				}
870
-				echo '</td></tr>';
871
-			}
872
-		}
873
-	}
874
-
875
-	/**
876
-	 * Print SOUR structure
877
-	 *  This function prints the input array of SOUR sub-records built by the
878
-	 *  getSourceStructure() function.
879
-	 *
880
-	 * @param string[] $textSOUR
881
-	 *
882
-	 * @return string
883
-	 */
884
-	public static function printSourceStructure($textSOUR) {
885
-		global $WT_TREE;
886
-		$html = '';
887
-
888
-		if ($textSOUR['PAGE']) {
889
-			$html .= GedcomTag::getLabelValue('PAGE', Filter::expandUrls($textSOUR['PAGE']));
890
-		}
891
-
892
-		if ($textSOUR['EVEN']) {
893
-			$html .= GedcomTag::getLabelValue('EVEN', Filter::escapeHtml($textSOUR['EVEN']));
894
-			if ($textSOUR['ROLE']) {
895
-				$html .= GedcomTag::getLabelValue('ROLE', Filter::escapeHtml($textSOUR['ROLE']));
896
-			}
897
-		}
898
-
899
-		if ($textSOUR['DATE'] || count($textSOUR['TEXT'])) {
900
-			if ($textSOUR['DATE']) {
901
-				$date = new Date($textSOUR['DATE']);
902
-				$html .= GedcomTag::getLabelValue('DATA:DATE', $date->display());
903
-			}
904
-			foreach ($textSOUR['TEXT'] as $text) {
905
-				$html .= GedcomTag::getLabelValue('TEXT', Filter::formatText($text, $WT_TREE));
906
-			}
907
-		}
908
-
909
-		if ($textSOUR['QUAY'] != '') {
910
-			$html .= GedcomTag::getLabelValue('QUAY', GedcomCodeQuay::getValue($textSOUR['QUAY']));
911
-		}
912
-
913
-		return '<div class="indent">' . $html . '</div>';
914
-	}
915
-
916
-	/**
917
-	 * Extract SOUR structure from the incoming Source sub-record
918
-	 * The output array is defined as follows:
919
-	 *  $textSOUR['PAGE'] = Source citation
920
-	 *  $textSOUR['EVEN'] = Event type
921
-	 *  $textSOUR['ROLE'] = Role in event
922
-	 *  $textSOUR['DATA'] = place holder (no text in this sub-record)
923
-	 *  $textSOUR['DATE'] = Entry recording date
924
-	 *  $textSOUR['TEXT'] = (array) Text from source
925
-	 *  $textSOUR['QUAY'] = Certainty assessment
926
-	 *
927
-	 * @param string $srec
928
-	 *
929
-	 * @return string[]
930
-	 */
931
-	public static function getSourceStructure($srec) {
932
-		// Set up the output array
933
-		$textSOUR = array(
934
-			'PAGE' => '',
935
-			'EVEN' => '',
936
-			'ROLE' => '',
937
-			'DATA' => '',
938
-			'DATE' => '',
939
-			'TEXT' => array(),
940
-			'QUAY' => '',
941
-		);
942
-
943
-		if ($srec) {
944
-			$subrecords = explode("\n", $srec);
945
-			for ($i = 0; $i < count($subrecords); $i++) {
946
-				$tag  = substr($subrecords[$i], 2, 4);
947
-				$text = substr($subrecords[$i], 7);
948
-				$i++;
949
-				for (; $i < count($subrecords); $i++) {
950
-					$nextTag = substr($subrecords[$i], 2, 4);
951
-					if ($nextTag != 'CONT') {
952
-						$i--;
953
-						break;
954
-					}
955
-					if ($nextTag == 'CONT') {
956
-						$text .= "\n";
957
-					}
958
-					$text .= rtrim(substr($subrecords[$i], 7));
959
-				}
960
-				if ($tag == 'TEXT') {
961
-					$textSOUR[$tag][] = $text;
962
-				} else {
963
-					$textSOUR[$tag] = $text;
964
-				}
965
-			}
966
-		}
967
-
968
-		return $textSOUR;
969
-	}
970
-
971
-	/**
972
-	 * Print a row for the notes tab on the individual page.
973
-	 *
974
-	 * @param Fact $fact
975
-	 * @param int $level
976
-	 */
977
-	public static function printMainNotes(Fact $fact, $level) {
978
-		$factrec = $fact->getGedcom();
979
-		$fact_id = $fact->getFactId();
980
-		$parent  = $fact->getParent();
981
-		$pid     = $parent->getXref();
982
-
983
-		if ($fact->isPendingAddition()) {
984
-			$styleadd = ' new';
985
-			$can_edit = $level == 1 && $fact->canEdit();
986
-		} elseif ($fact->isPendingDeletion()) {
987
-			$styleadd = ' old';
988
-			$can_edit = false;
989
-		} else {
990
-			$styleadd = '';
991
-			$can_edit = $level == 1 && $fact->canEdit();
992
-		}
993
-
994
-		$ct = preg_match_all("/$level NOTE (.*)/", $factrec, $match, PREG_SET_ORDER);
995
-		for ($j = 0; $j < $ct; $j++) {
996
-			// Note object, or inline note?
997
-			if (preg_match("/$level NOTE @(.*)@/", $match[$j][0], $nmatch)) {
998
-				$note = Note::getInstance($nmatch[1], $fact->getParent()->getTree());
999
-				if ($note && !$note->canShow()) {
1000
-					continue;
1001
-				}
1002
-			} else {
1003
-				$note = null;
1004
-			}
1005
-
1006
-			if ($level >= 2) {
1007
-				echo '<tr class="row_note2"><td class="descriptionbox rela ', $styleadd, ' width20">';
1008
-			} else {
1009
-				echo '<tr><td class="descriptionbox ', $styleadd, ' width20">';
1010
-			}
1011
-			if ($can_edit) {
1012
-				echo '<a onclick="return edit_record(\'', $pid, '\', \'', $fact_id, '\');" href="#" title="', I18N::translate('Edit'), '">';
1013
-				if ($level < 2) {
1014
-					if ($fact->getParent()->getTree()->getPreference('SHOW_FACT_ICONS')) {
1015
-						echo '<i class="icon-note"></i> ';
1016
-					}
1017
-					if ($note) {
1018
-						echo GedcomTag::getLabel('SHARED_NOTE');
1019
-					} else {
1020
-						echo GedcomTag::getLabel('NOTE');
1021
-					}
1022
-					echo '</a>';
1023
-					echo '<div class="editfacts">';
1024
-					echo "<div class=\"editlink\"><a class=\"editicon\" onclick=\"return edit_record('$pid', '$fact_id');\" href=\"#\" title=\"" . I18N::translate('Edit') . "\"><span class=\"link_text\">" . I18N::translate('Edit') . "</span></a></div>";
1025
-					echo '<div class="copylink"><a class="copyicon" href="#" onclick="return copy_fact(\'', $pid, '\', \'', $fact_id, '\');" title="' . I18N::translate('Copy') . '"><span class="link_text">' . I18N::translate('Copy') . '</span></a></div>';
1026
-					echo "<div class=\"deletelink\"><a class=\"deleteicon\" onclick=\"return delete_fact('" . I18N::translate('Are you sure you want to delete this fact?') . "', '$pid', '$fact_id');\" href=\"#\" title=\"" . I18N::translate('Delete') . "\"><span class=\"link_text\">" . I18N::translate('Delete') . "</span></a></div>";
1027
-					if ($note) {
1028
-						echo '<a class="icon-note" href="', $note->getHtmlUrl(), '" title="' . I18N::translate('View') . '"><span class="link_text">' . I18N::translate('View') . '</span></a>';
1029
-					}
1030
-					echo '</div>';
1031
-				}
1032
-			} else {
1033
-				if ($level < 2) {
1034
-					if ($fact->getParent()->getTree()->getPreference('SHOW_FACT_ICONS')) {
1035
-						echo '<i class="icon-note"></i> ';
1036
-					}
1037
-					if ($note) {
1038
-						echo GedcomTag::getLabel('SHARED_NOTE');
1039
-					} else {
1040
-						echo GedcomTag::getLabel('NOTE');
1041
-					}
1042
-				}
1043
-				$factlines = explode("\n", $factrec); // 1 BIRT Y\n2 NOTE ...
1044
-				$factwords = explode(" ", $factlines[0]); // 1 BIRT Y
1045
-				$factname  = $factwords[1]; // BIRT
1046
-				$parent    = GedcomRecord::getInstance($pid, $fact->getParent()->getTree());
1047
-				if ($factname == 'EVEN' || $factname == 'FACT') {
1048
-					// Add ' EVEN' to provide sensible output for an event with an empty TYPE record
1049
-					$ct = preg_match("/2 TYPE (.*)/", $factrec, $ematch);
1050
-					if ($ct > 0) {
1051
-						$factname = trim($ematch[1]);
1052
-						echo $factname;
1053
-					} else {
1054
-						echo GedcomTag::getLabel($factname, $parent);
1055
-					}
1056
-				} elseif ($factname != 'NOTE') {
1057
-					// Note is already printed
1058
-					echo GedcomTag::getLabel($factname, $parent);
1059
-					if ($note) {
1060
-						echo '<div class="editfacts"><a class="icon-note" href="', $note->getHtmlUrl(), '" title="' . I18N::translate('View') . '"><span class="link_text">' . I18N::translate('View') . '</span></a></div>';
1061
-
1062
-					}
1063
-				}
1064
-			}
1065
-			echo '</td>';
1066
-			if ($note) {
1067
-				// Note objects
1068
-				if (Module::getModuleByName('GEDFact_assistant')) {
1069
-					// If Census assistant installed, allow it to format the note
1070
-					$text = CensusAssistantModule::formatCensusNote($note);
1071
-				} else {
1072
-					$text = Filter::formatText($note->getNote(), $fact->getParent()->getTree());
1073
-				}
1074
-			} else {
1075
-				// Inline notes
1076
-				$nrec = Functions::getSubRecord($level, "$level NOTE", $factrec, $j + 1);
1077
-				$text = $match[$j][1] . Functions::getCont($level + 1, $nrec);
1078
-				$text = Filter::formatText($text, $fact->getParent()->getTree());
1079
-			}
1080
-
1081
-			echo '<td class="optionbox', $styleadd, ' wrap">';
1082
-			echo $text;
1083
-
1084
-			if (!empty($noterec)) {
1085
-				echo self::printFactSources($noterec, 1);
1086
-			}
1087
-
1088
-			// 2 RESN tags. Note, there can be more than one, such as "privacy" and "locked"
1089
-			if (preg_match_all("/\n2 RESN (.+)/", $factrec, $matches)) {
1090
-				foreach ($matches[1] as $match) {
1091
-					echo '<br><span class="label">', GedcomTag::getLabel('RESN'), ':</span> <span class="field">';
1092
-					switch ($match) {
1093
-					case 'none':
1094
-						// Note: "2 RESN none" is not valid gedcom, and the GUI will not let you add it.
1095
-						// However, webtrees privacy rules will interpret it as "show an otherwise private fact to public".
1096
-						echo '<i class="icon-resn-none"></i> ', I18N::translate('Show to visitors');
1097
-						break;
1098
-					case 'privacy':
1099
-						echo '<i class="icon-resn-privacy"></i> ', I18N::translate('Show to members');
1100
-						break;
1101
-					case 'confidential':
1102
-						echo '<i class="icon-resn-confidential"></i> ', I18N::translate('Show to managers');
1103
-						break;
1104
-					case 'locked':
1105
-						echo '<i class="icon-resn-locked"></i> ', I18N::translate('Only managers can edit');
1106
-						break;
1107
-					default:
1108
-						echo $match;
1109
-						break;
1110
-					}
1111
-					echo '</span>';
1112
-				}
1113
-			}
1114
-			echo '</td></tr>';
1115
-		}
1116
-	}
1117
-
1118
-	/**
1119
-	 * Print a row for the media tab on the individual page.
1120
-	 *
1121
-	 * @param Fact $fact
1122
-	 * @param int $level
1123
-	 */
1124
-	public static function printMainMedia(Fact $fact, $level) {
1125
-		$factrec = $fact->getGedcom();
1126
-		$parent  = $fact->getParent();
1127
-
1128
-		if ($fact->isPendingAddition()) {
1129
-			$styleadd = 'new';
1130
-			$can_edit = $level == 1 && $fact->canEdit();
1131
-		} elseif ($fact->isPendingDeletion()) {
1132
-			$styleadd = 'old';
1133
-			$can_edit = false;
1134
-		} else {
1135
-			$styleadd = '';
1136
-			$can_edit = $level == 1 && $fact->canEdit();
1137
-		}
1138
-
1139
-		// -- find source for each fact
1140
-		preg_match_all('/(?:^|\n)' . $level . ' OBJE @(.*)@/', $factrec, $matches);
1141
-		foreach ($matches[1] as $xref) {
1142
-			$media = Media::getInstance($xref, $fact->getParent()->getTree());
1143
-			// Allow access to "1 OBJE @non_existent_source@", so it can be corrected/deleted
1144
-			if (!$media || $media->canShow()) {
1145
-				if ($level > 1) {
1146
-					echo '<tr class="row_obje2">';
1147
-				} else {
1148
-					echo '<tr>';
1149
-				}
1150
-				echo '<td class="descriptionbox';
1151
-				if ($level > 1) {
1152
-					echo ' rela';
1153
-				}
1154
-				echo ' ', $styleadd, ' width20">';
1155
-				preg_match("/^\d (\w*)/", $factrec, $factname);
1156
-				$factlines = explode("\n", $factrec); // 1 BIRT Y\n2 SOUR ...
1157
-				$factwords = explode(" ", $factlines[0]); // 1 BIRT Y
1158
-				$factname  = $factwords[1]; // BIRT
1159
-				if ($factname == 'EVEN' || $factname == 'FACT') {
1160
-					// Add ' EVEN' to provide sensible output for an event with an empty TYPE record
1161
-					$ct = preg_match("/2 TYPE (.*)/", $factrec, $ematch);
1162
-					if ($ct > 0) {
1163
-						$factname = $ematch[1];
1164
-						echo $factname;
1165
-					} else {
1166
-						echo GedcomTag::getLabel($factname, $parent);
1167
-					}
1168
-				} elseif ($can_edit) {
1169
-					echo '<a onclick="window.open(\'addmedia.php?action=editmedia&amp;pid=', $media->getXref(), '\', \'_blank\', edit_window_specs); return false;" href="#" title="', I18N::translate('Edit'), '">';
1170
-					echo GedcomTag::getLabel($factname, $parent), '</a>';
1171
-					echo '<div class="editfacts">';
1172
-					echo '<div class="editlink"><a class="editicon" onclick="window.open(\'addmedia.php?action=editmedia&amp;pid=', $media->getXref(), '\', \'_blank\', edit_window_specs); return false;" href="#" title="', I18N::translate('Edit'), '"><span class="link_text">', I18N::translate('Edit'), '</span></a></div>';
1173
-					echo '<div class="copylink"><a class="copyicon" href="#" onclick="jQuery.post(\'action.php\',{action:\'copy-fact\', type:\'\', factgedcom:\'' . rawurlencode($factrec) . '\'},function(){location.reload();})" title="' . I18N::translate('Copy') . '"><span class="link_text">' . I18N::translate('Copy') . '</span></a></div>';
1174
-					echo '<div class="deletelink"><a class="deleteicon" onclick="return delete_fact(\'', I18N::translate('Are you sure you want to delete this fact?'), '\', \'', $parent->getXref(), '\', \'', $fact->getFactId(), '\');" href="#" title="', I18N::translate('Delete'), '"><span class="link_text">', I18N::translate('Delete'), '</span></a></div>';
1175
-					echo '</div>';
1176
-				} else {
1177
-					echo GedcomTag::getLabel($factname, $parent);
1178
-				}
1179
-				echo '</td>';
1180
-				echo '<td class="optionbox ', $styleadd, ' wrap">';
1181
-				if ($media) {
1182
-					echo '<span class="field">';
1183
-					echo $media->displayImage();
1184
-					echo '<a href="' . $media->getHtmlUrl() . '">';
1185
-					echo '<em>';
1186
-					foreach ($media->getAllNames() as $name) {
1187
-						if ($name['type'] != 'TITL') {
1188
-							echo '<br>';
1189
-						}
1190
-						echo $name['full'];
1191
-					}
1192
-					echo '</em>';
1193
-					echo '</a>';
1194
-					echo '</span>';
1195
-
1196
-					echo GedcomTag::getLabelValue('FORM', $media->mimeType());
1197
-					$imgsize = $media->getImageAttributes('main');
1198
-					if (!empty($imgsize['WxH'])) {
1199
-						echo GedcomTag::getLabelValue('__IMAGE_SIZE__', $imgsize['WxH']);
1200
-					}
1201
-					if ($media->getFilesizeraw() > 0) {
1202
-						echo GedcomTag::getLabelValue('__FILE_SIZE__', $media->getFilesize());
1203
-					}
1204
-					$mediatype = $media->getMediaType();
1205
-					if ($mediatype) {
1206
-						echo GedcomTag::getLabelValue('TYPE', GedcomTag::getFileFormTypeValue($mediatype));
1207
-					}
1208
-
1209
-					switch ($media->isPrimary()) {
1210
-					case 'Y':
1211
-						echo GedcomTag::getLabelValue('_PRIM', I18N::translate('yes'));
1212
-						break;
1213
-					case 'N':
1214
-						echo GedcomTag::getLabelValue('_PRIM', I18N::translate('no'));
1215
-						break;
1216
-					}
1217
-					echo FunctionsPrint::printFactNotes($media->getGedcom(), 1);
1218
-					echo self::printFactSources($media->getGedcom(), 1);
1219
-				} else {
1220
-					echo $xref;
1221
-				}
1222
-				echo '</td></tr>';
1223
-			}
1224
-		}
1225
-	}
206
+        } else {
207
+            echo $label;
208
+        }
209
+
210
+        switch ($fact->getTag()) {
211
+        case '_BIRT_CHIL':
212
+            echo '<br>', /* I18N: Abbreviation for "number %s" */
213
+            I18N::translate('#%s', ++$n_chil);
214
+            break;
215
+        case '_BIRT_GCHI':
216
+        case '_BIRT_GCH1':
217
+        case '_BIRT_GCH2':
218
+            echo '<br>', I18N::translate('#%s', ++$n_gchi);
219
+            break;
220
+        }
221
+
222
+        echo '</td><td class="optionbox ', $styleadd, ' wrap">';
223
+
224
+        // Event from another record?
225
+        if ($parent !== $record) {
226
+            if ($parent instanceof Family) {
227
+                foreach ($parent->getSpouses() as $spouse) {
228
+                    if ($record !== $spouse) {
229
+                        echo '<a href="', $spouse->getHtmlUrl(), '">', $spouse->getFullName(), '</a> — ';
230
+                    }
231
+                }
232
+                echo '<a href="', $parent->getHtmlUrl(), '">', I18N::translate('View this family'), '</a><br>';
233
+            } elseif ($parent instanceof Individual) {
234
+                echo '<a href="', $parent->getHtmlUrl(), '">', $parent->getFullName(), '</a><br>';
235
+            }
236
+        }
237
+
238
+        // Print the value of this fact/event
239
+        switch ($fact->getTag()) {
240
+        case 'ADDR':
241
+            echo $fact->getValue();
242
+            break;
243
+        case 'AFN':
244
+            echo '<div class="field"><a href="https://familysearch.org/search/tree/results#count=20&query=afn:', Filter::escapeUrl($fact->getValue()), '">', Filter::escapeHtml($fact->getValue()), '</a></div>';
245
+            break;
246
+        case 'ASSO':
247
+            // we handle this later, in format_asso_rela_record()
248
+            break;
249
+        case 'EMAIL':
250
+        case 'EMAI':
251
+        case '_EMAIL':
252
+            echo '<div class="field"><a href="mailto:', Filter::escapeHtml($fact->getValue()), '">', Filter::escapeHtml($fact->getValue()), '</a></div>';
253
+            break;
254
+        case 'FILE':
255
+            if (Auth::isEditor($fact->getParent()->getTree())) {
256
+                echo '<div class="field">', Filter::escapeHtml($fact->getValue()), '</div>';
257
+            }
258
+            break;
259
+        case 'RESN':
260
+            echo '<div class="field">';
261
+            switch ($fact->getValue()) {
262
+            case 'none':
263
+            // Note: "1 RESN none" is not valid gedcom.
264
+            // However, webtrees privacy rules will interpret it as "show an otherwise private record to public".
265
+            echo '<i class="icon-resn-none"></i> ', I18N::translate('Show to visitors');
266
+            break;
267
+            case 'privacy':
268
+            echo '<i class="icon-class-none"></i> ', I18N::translate('Show to members');
269
+            break;
270
+            case 'confidential':
271
+            echo '<i class="icon-confidential-none"></i> ', I18N::translate('Show to managers');
272
+            break;
273
+            case 'locked':
274
+            echo '<i class="icon-locked-none"></i> ', I18N::translate('Only managers can edit');
275
+            break;
276
+            default:
277
+            echo Filter::escapeHtml($fact->getValue());
278
+            break;
279
+            }
280
+                echo '</div>';
281
+                break;
282
+        case 'PUBL': // Publication details might contain URLs.
283
+            echo '<div class="field">', Filter::expandUrls($fact->getValue()), '</div>';
284
+            break;
285
+        case 'REPO':
286
+            if (preg_match('/^@(' . WT_REGEX_XREF . ')@$/', $fact->getValue(), $match)) {
287
+                self::printRepositoryRecord($match[1]);
288
+            } else {
289
+                echo '<div class="error">', Filter::escapeHtml($fact->getValue()), '</div>';
290
+            }
291
+            break;
292
+        case 'URL':
293
+        case '_URL':
294
+        case 'WWW':
295
+            echo '<div class="field"><a href="', Filter::escapeHtml($fact->getValue()), '">', Filter::escapeHtml($fact->getValue()), '</a></div>';
296
+            break;
297
+        case 'TEXT': // 0 SOUR / 1 TEXT
298
+            echo '<div class="field">', nl2br(Filter::escapeHtml($fact->getValue()), false), '</div>';
299
+            break;
300
+        default:
301
+            // Display the value for all other facts/events
302
+            switch ($fact->getValue()) {
303
+            case '':
304
+            // Nothing to display
305
+            break;
306
+            case 'N':
307
+            // Not valid GEDCOM
308
+            echo '<div class="field">', I18N::translate('No'), '</div>';
309
+            break;
310
+            case 'Y':
311
+            // Do not display "Yes".
312
+            break;
313
+            default:
314
+            if (preg_match('/^@(' . WT_REGEX_XREF . ')@$/', $fact->getValue(), $match)) {
315
+            $target = GedcomRecord::getInstance($match[1], $fact->getParent()->getTree());
316
+            if ($target) {
317
+                echo '<div><a href="', $target->getHtmlUrl(), '">', $target->getFullName(), '</a></div>';
318
+            } else {
319
+                echo '<div class="error">', Filter::escapeHtml($fact->getValue()), '</div>';
320
+            }
321
+            } else {
322
+            echo '<div class="field"><span dir="auto">', Filter::escapeHtml($fact->getValue()), '</span></div>';
323
+            }
324
+            break;
325
+            }
326
+                break;
327
+        }
328
+
329
+        // Print the type of this fact/event
330
+        if ($type) {
331
+            $utype = strtoupper($type);
332
+            // Events of close relatives, e.g. _MARR_CHIL
333
+            if (substr($fact->getTag(), 0, 6) == '_MARR_' && ($utype == 'CIVIL' || $utype == 'PARTNERS' || $utype == 'RELIGIOUS')) {
334
+                // Translate MARR/TYPE using the code that supports MARR_CIVIL, etc. tags
335
+                $type = GedcomTag::getLabel('MARR_' . $utype);
336
+            } else {
337
+                // Allow (custom) translations for other types
338
+                $type = I18N::translate($type);
339
+            }
340
+            echo GedcomTag::getLabelValue('TYPE', Filter::escapeHtml($type));
341
+        }
342
+
343
+        // Print the date of this fact/event
344
+        echo FunctionsPrint::formatFactDate($fact, $record, true, true);
345
+
346
+        // Print the place of this fact/event
347
+        echo '<div class="place">', FunctionsPrint::formatFactPlace($fact, true, true, true), '</div>';
348
+        // A blank line between the primary attributes (value, date, place) and the secondary ones
349
+        echo '<br>';
350
+
351
+        $addr = $fact->getAttribute('ADDR');
352
+        if ($addr) {
353
+            echo GedcomTag::getLabelValue('ADDR', $addr);
354
+        }
355
+
356
+        // Print the associates of this fact/event
357
+        if ($fact->getFactId() !== 'asso') {
358
+            echo self::formatAssociateRelationship($fact);
359
+        }
360
+
361
+        // Print any other "2 XXXX" attributes, in the order in which they appear.
362
+        preg_match_all('/\n2 (' . WT_REGEX_TAG . ') (.+)/', $fact->getGedcom(), $matches, PREG_SET_ORDER);
363
+        foreach ($matches as $match) {
364
+            switch ($match[1]) {
365
+            case 'DATE':
366
+            case 'TIME':
367
+            case 'AGE':
368
+            case 'PLAC':
369
+            case 'ADDR':
370
+            case 'ALIA':
371
+            case 'ASSO':
372
+            case '_ASSO':
373
+            case 'DESC':
374
+            case 'RELA':
375
+            case 'STAT':
376
+            case 'TEMP':
377
+            case 'TYPE':
378
+            case 'FAMS':
379
+            case 'CONT':
380
+                // These were already shown at the beginning
381
+                break;
382
+            case 'NOTE':
383
+            case 'OBJE':
384
+            case 'SOUR':
385
+                // These will be shown at the end
386
+                break;
387
+            case '_UID':
388
+            case 'RIN':
389
+                // These don't belong at level 2, so do not display them.
390
+                // They are only shown when editing.
391
+                break;
392
+            case 'EVEN': // 0 SOUR / 1 DATA / 2 EVEN / 3 DATE / 3 PLAC
393
+                $events = array();
394
+                foreach (preg_split('/ *, */', $match[2]) as $event) {
395
+                    $events[] = GedcomTag::getLabel($event);
396
+                }
397
+                if (count($events) == 1) {
398
+                    echo GedcomTag::getLabelValue('EVEN', $event);
399
+                } else {
400
+                    echo GedcomTag::getLabelValue('EVEN', implode(I18N::$list_separator, $events));
401
+                }
402
+                if (preg_match('/\n3 DATE (.+)/', $fact->getGedcom(), $date_match)) {
403
+                    $date = new Date($date_match[1]);
404
+                    echo GedcomTag::getLabelValue('DATE', $date->display());
405
+                }
406
+                if (preg_match('/\n3 PLAC (.+)/', $fact->getGedcom(), $plac_match)) {
407
+                    echo GedcomTag::getLabelValue('PLAC', $plac_match[1]);
408
+                }
409
+                break;
410
+            case 'FAMC': // 0 INDI / 1 ADOP / 2 FAMC / 3 ADOP
411
+                $family = Family::getInstance(str_replace('@', '', $match[2]), $fact->getParent()->getTree());
412
+                if ($family) {
413
+                    echo GedcomTag::getLabelValue('FAM', '<a href="' . $family->getHtmlUrl() . '">' . $family->getFullName() . '</a>');
414
+                    if (preg_match('/\n3 ADOP (HUSB|WIFE|BOTH)/', $fact->getGedcom(), $match)) {
415
+                        echo GedcomTag::getLabelValue('ADOP', GedcomCodeAdop::getValue($match[1], $label_person));
416
+                    }
417
+                } else {
418
+                    echo GedcomTag::getLabelValue('FAM', '<span class="error">' . $match[2] . '</span>');
419
+                }
420
+                break;
421
+            case '_WT_USER':
422
+                $user = User::findByIdentifier($match[2]); // may not exist
423
+                if ($user) {
424
+                    echo GedcomTag::getLabelValue('_WT_USER', $user->getRealNameHtml());
425
+                } else {
426
+                    echo GedcomTag::getLabelValue('_WT_USER', Filter::escapeHtml($match[2]));
427
+                }
428
+                break;
429
+            case 'RESN':
430
+                switch ($match[2]) {
431
+                case 'none':
432
+                // Note: "2 RESN none" is not valid gedcom.
433
+                // However, webtrees privacy rules will interpret it as "show an otherwise private fact to public".
434
+                echo GedcomTag::getLabelValue('RESN', '<i class="icon-resn-none"></i> ' . I18N::translate('Show to visitors'));
435
+                break;
436
+                case 'privacy':
437
+                echo GedcomTag::getLabelValue('RESN', '<i class="icon-resn-privacy"></i> ' . I18N::translate('Show to members'));
438
+                break;
439
+                case 'confidential':
440
+                echo GedcomTag::getLabelValue('RESN', '<i class="icon-resn-confidential"></i> ' . I18N::translate('Show to managers'));
441
+                break;
442
+                case 'locked':
443
+                echo GedcomTag::getLabelValue('RESN', '<i class="icon-resn-locked"></i> ' . I18N::translate('Only managers can edit'));
444
+                break;
445
+                default:
446
+                echo GedcomTag::getLabelValue('RESN', Filter::escapeHtml($match[2]));
447
+                break;
448
+                }
449
+                    break;
450
+            case 'CALN':
451
+                echo GedcomTag::getLabelValue('CALN', Filter::expandUrls($match[2]));
452
+                break;
453
+            case 'FORM': // 0 OBJE / 1 FILE / 2 FORM / 3 TYPE
454
+                echo GedcomTag::getLabelValue('FORM', $match[2]);
455
+                if (preg_match('/\n3 TYPE (.+)/', $fact->getGedcom(), $type_match)) {
456
+                    echo GedcomTag::getLabelValue('TYPE', GedcomTag::getFileFormTypeValue($type_match[1]));
457
+                }
458
+                break;
459
+            case 'URL':
460
+            case '_URL':
461
+            case 'WWW':
462
+                $link = '<a href="' . Filter::escapeHtml($match[2]) . '">' . Filter::escapeHtml($match[2]) . '</a>';
463
+                echo GedcomTag::getLabelValue($fact->getTag() . ':' . $match[1], $link);
464
+                break;
465
+            default:
466
+                if ($fact->getParent()->getTree()->getPreference('HIDE_GEDCOM_ERRORS') === '1' || GedcomTag::isTag($match[1])) {
467
+                    if (preg_match('/^@(' . WT_REGEX_XREF . ')@$/', $match[2], $xmatch)) {
468
+                        // Links
469
+                        $linked_record = GedcomRecord::getInstance($xmatch[1], $fact->getParent()->getTree());
470
+                        if ($linked_record) {
471
+                            $link = '<a href="' . $linked_record->getHtmlUrl() . '">' . $linked_record->getFullName() . '</a>';
472
+                            echo GedcomTag::getLabelValue($fact->getTag() . ':' . $match[1], $link);
473
+                        } else {
474
+                            echo GedcomTag::getLabelValue($fact->getTag() . ':' . $match[1], Filter::escapeHtml($match[2]));
475
+                        }
476
+                    } else {
477
+                        // Non links
478
+                        echo GedcomTag::getLabelValue($fact->getTag() . ':' . $match[1], Filter::escapeHtml($match[2]));
479
+                    }
480
+                }
481
+                break;
482
+            }
483
+        }
484
+        echo self::printFactSources($fact->getGedcom(), 2);
485
+        echo FunctionsPrint::printFactNotes($fact->getGedcom(), 2);
486
+        self::printMediaLinks($fact->getGedcom(), 2);
487
+        echo '</td></tr>';
488
+    }
489
+
490
+    /**
491
+     * Print the associations from the associated individuals in $event to the individuals in $record
492
+     *
493
+     * @param Fact $event
494
+     *
495
+     * @return string
496
+     */
497
+    private static function formatAssociateRelationship(Fact $event) {
498
+        $parent = $event->getParent();
499
+        // To whom is this record an assocate?
500
+        if ($parent instanceof Individual) {
501
+            // On an individual page, we just show links to the person
502
+            $associates = array($parent);
503
+        } elseif ($parent instanceof Family) {
504
+            // On a family page, we show links to both spouses
505
+            $associates = $parent->getSpouses();
506
+        } else {
507
+            // On other pages, it does not make sense to show associates
508
+            return '';
509
+        }
510
+
511
+        preg_match_all('/^1 ASSO @(' . WT_REGEX_XREF . ')@((\n[2-9].*)*)/', $event->getGedcom(), $amatches1, PREG_SET_ORDER);
512
+        preg_match_all('/\n2 _?ASSO @(' . WT_REGEX_XREF . ')@((\n[3-9].*)*)/', $event->getGedcom(), $amatches2, PREG_SET_ORDER);
513
+
514
+        $html = '';
515
+        // For each ASSO record
516
+        foreach (array_merge($amatches1, $amatches2) as $amatch) {
517
+            $person = Individual::getInstance($amatch[1], $event->getParent()->getTree());
518
+            if ($person && $person->canShowName()) {
519
+                // Is there a "RELA" tag
520
+                if (preg_match('/\n[23] RELA (.+)/', $amatch[2], $rmatch)) {
521
+                    // Use the supplied relationship as a label
522
+                    $label = GedcomCodeRela::getValue($rmatch[1], $person);
523
+                } else {
524
+                    // Use a default label
525
+                    $label = GedcomTag::getLabel('ASSO', $person);
526
+                }
527
+
528
+                $values = array('<a href="' . $person->getHtmlUrl() . '">' . $person->getFullName() . '</a>');
529
+                foreach ($associates as $associate) {
530
+                    $relationship_name = Functions::getAssociateRelationshipName($associate, $person);
531
+                    if (!$relationship_name) {
532
+                        $relationship_name = GedcomTag::getLabel('RELA');
533
+                    }
534
+
535
+                    if ($parent instanceof Family) {
536
+                        // For family ASSO records (e.g. MARR), identify the spouse with a sex icon
537
+                        $relationship_name .= $associate->getSexImage();
538
+                    }
539
+
540
+                    $values[] = '<a href="relationship.php?pid1=' . $associate->getXref() . '&amp;pid2=' . $person->getXref() . '&amp;ged=' . $associate->getTree()->getNameUrl() . '" rel="nofollow">' . $relationship_name . '</a>';
541
+                }
542
+                $value = implode(' — ', $values);
543
+
544
+                // Use same markup as GedcomTag::getLabelValue()
545
+                $asso = I18N::translate('<span class="label">%1$s:</span> <span class="field" dir="auto">%2$s</span>', $label, $value);
546
+            } elseif (!$person && Auth::isEditor($event->getParent()->getTree())) {
547
+                $asso = GedcomTag::getLabelValue('ASSO', '<span class="error">' . $amatch[1] . '</span>');
548
+            } else {
549
+                $asso = '';
550
+            }
551
+            $html .= '<div class="fact_ASSO">' . $asso . '</div>';
552
+        }
553
+
554
+        return $html;
555
+    }
556
+
557
+    /**
558
+     * print a repository record
559
+     *
560
+     * find and print repository information attached to a source
561
+     *
562
+     * @param string $xref the Gedcom Xref ID of the repository to print
563
+     */
564
+    public static function printRepositoryRecord($xref) {
565
+        global $WT_TREE;
566
+
567
+        $repository = Repository::getInstance($xref, $WT_TREE);
568
+        if ($repository && $repository->canShow()) {
569
+            echo '<a class="field" href="', $repository->getHtmlUrl(), '">', $repository->getFullName(), '</a><br>';
570
+            echo '<br>';
571
+            echo FunctionsPrint::printFactNotes($repository->getGedcom(), 1);
572
+        }
573
+    }
574
+
575
+    /**
576
+     * print a source linked to a fact (2 SOUR)
577
+     *
578
+     * this function is called by the FunctionsPrintFacts::print_fact function and other functions to
579
+     * print any source information attached to the fact
580
+     *
581
+     * @param string $factrec The fact record to look for sources in
582
+     * @param int $level The level to look for sources at
583
+     *
584
+     * @return string HTML text
585
+     */
586
+    public static function printFactSources($factrec, $level) {
587
+        global $WT_TREE;
588
+
589
+        $data   = '';
590
+        $nlevel = $level + 1;
591
+
592
+        // Systems not using source records
593
+        // The old style is not supported when entering or editing sources, but may be found in imported trees.
594
+        // Also, the old style sources allow histo.* files to use tree independent source citations, which
595
+        // will display nicely when markdown is used.
596
+        $ct = preg_match_all('/' . $level . ' SOUR (.*)((?:\n\d CONT.*)*)/', $factrec, $match, PREG_SET_ORDER);
597
+        for ($j = 0; $j < $ct; $j++) {
598
+            if (strpos($match[$j][1], '@') === false) {
599
+                $source = Filter::escapeHtml($match[$j][1] . preg_replace('/\n\d CONT ?/', "\n", $match[$j][2]));
600
+                $data .= '<div class="fact_SOUR"><span class="label">' . I18N::translate('Source') . ':</span> <span class="field" dir="auto">' . Filter::formatText($source, $WT_TREE) . '</span></div>';
601
+            }
602
+        }
603
+        // Find source for each fact
604
+        $ct    = preg_match_all("/$level SOUR @(.*)@/", $factrec, $match, PREG_SET_ORDER);
605
+        $spos2 = 0;
606
+        for ($j = 0; $j < $ct; $j++) {
607
+            $sid    = $match[$j][1];
608
+            $source = Source::getInstance($sid, $WT_TREE);
609
+            if ($source) {
610
+                if ($source->canShow()) {
611
+                    $spos1 = strpos($factrec, "$level SOUR @" . $sid . "@", $spos2);
612
+                    $spos2 = strpos($factrec, "\n$level", $spos1);
613
+                    if (!$spos2) {
614
+                        $spos2 = strlen($factrec);
615
+                    }
616
+                    $srec = substr($factrec, $spos1, $spos2 - $spos1);
617
+                    $lt   = preg_match_all("/$nlevel \w+/", $srec, $matches);
618
+                    $data .= '<div class="fact_SOUR">';
619
+                    $elementID = Uuid::uuid4();
620
+                    if ($WT_TREE->getPreference('EXPAND_SOURCES')) {
621
+                        $plusminus = 'icon-minus';
622
+                    } else {
623
+                        $plusminus = 'icon-plus';
624
+                    }
625
+                    if ($lt > 0) {
626
+                        $data .= '<a href="#" onclick="return expand_layer(\'' . $elementID . '\');"><i id="' . $elementID . '_img" class="' . $plusminus . '"></i></a> ';
627
+                    }
628
+                    $data .= GedcomTag::getLabelValue('SOUR', '<a href="' . $source->getHtmlUrl() . '">' . $source->getFullName() . '</a>', null, 'span');
629
+                    $data .= '</div>';
630
+
631
+                    $data .= "<div id=\"$elementID\"";
632
+                    if ($WT_TREE->getPreference('EXPAND_SOURCES')) {
633
+                        $data .= ' style="display:block"';
634
+                    }
635
+                    $data .= ' class="source_citations">';
636
+                    // PUBL
637
+                    $publ = $source->getFirstFact('PUBL');
638
+                    if ($publ) {
639
+                        $data .= GedcomTag::getLabelValue('PUBL', $publ->getValue());
640
+                    }
641
+                    $data .= self::printSourceStructure(self::getSourceStructure($srec));
642
+                    $data .= '<div class="indent">';
643
+                    ob_start();
644
+                    self::printMediaLinks($srec, $nlevel);
645
+                    $data .= ob_get_clean();
646
+                    $data .= FunctionsPrint::printFactNotes($srec, $nlevel, false);
647
+                    $data .= '</div>';
648
+                    $data .= '</div>';
649
+                } else {
650
+                    // Here we could show that we do actually have sources for this data,
651
+                    // but not the details. For example “Sources: ”.
652
+                    // But not by default, based on user feedback.
653
+                    // https://webtrees.net/index.php/en/forum/3-help-for-beta-and-svn-versions/27002-source-media-privacy-issue
654
+                }
655
+            } else {
656
+                $data .= GedcomTag::getLabelValue('SOUR', '<span class="error">' . $sid . '</span>');
657
+            }
658
+        }
659
+
660
+        return $data;
661
+    }
662
+
663
+    /**
664
+     * Print the links to media objects
665
+     *
666
+     * @param string $factrec
667
+     * @param int $level
668
+     */
669
+    public static function printMediaLinks($factrec, $level) {
670
+        global $WT_TREE;
671
+
672
+        $nlevel = $level + 1;
673
+        if (preg_match_all("/$level OBJE @(.*)@/", $factrec, $omatch, PREG_SET_ORDER) == 0) {
674
+            return;
675
+        }
676
+        $objectNum = 0;
677
+        while ($objectNum < count($omatch)) {
678
+            $media_id = $omatch[$objectNum][1];
679
+            $media    = Media::getInstance($media_id, $WT_TREE);
680
+            if ($media) {
681
+                if ($media->canShow()) {
682
+                    if ($objectNum > 0) {
683
+                        echo '<br class="media-separator" style="clear:both;">';
684
+                    }
685
+                    echo '<div class="media-display"><div class="media-display-image">';
686
+                    echo $media->displayImage();
687
+                    echo '</div>';
688
+                    echo '<div class="media-display-title">';
689
+                    echo '<a href="', $media->getHtmlUrl(), '">', $media->getFullName(), '</a>';
690
+                    // NOTE: echo the notes of the media
691
+                    echo '<p>';
692
+                    echo FunctionsPrint::printFactNotes($media->getGedcom(), 1);
693
+                    $ttype = preg_match("/" . ($nlevel + 1) . " TYPE (.*)/", $media->getGedcom(), $match);
694
+                    if ($ttype > 0) {
695
+                        $mediaType = GedcomTag::getFileFormTypeValue($match[1]);
696
+                        echo '<p class="label">', I18N::translate('Type'), ': </span> <span class="field">', $mediaType, '</p>';
697
+                    }
698
+                    echo '</p>';
699
+                    //-- print spouse name for marriage events
700
+                    $ct = preg_match("/WT_SPOUSE: (.*)/", $factrec, $match);
701
+                    if ($ct > 0) {
702
+                        $spouse = Individual::getInstance($match[1], $media->getTree());
703
+                        if ($spouse) {
704
+                            echo '<a href="', $spouse->getHtmlUrl(), '">';
705
+                            echo $spouse->getFullName();
706
+                            echo '</a>';
707
+                        }
708
+                        $ct = preg_match("/WT_FAMILY_ID: (.*)/", $factrec, $match);
709
+                        if ($ct > 0) {
710
+                            $famid  = trim($match[1]);
711
+                            $family = Family::getInstance($famid, $spouse->getTree());
712
+                            if ($family) {
713
+                                if ($spouse) {
714
+                                    echo " - ";
715
+                                }
716
+                                echo '<a href="', $family->getHtmlUrl(), '">', I18N::translate('View this family'), '</a>';
717
+                            }
718
+                        }
719
+                    }
720
+                    echo FunctionsPrint::printFactNotes($media->getGedcom(), $nlevel);
721
+                    echo self::printFactSources($media->getGedcom(), $nlevel);
722
+                    echo '</div>'; //close div "media-display-title"
723
+                    echo '</div>'; //close div "media-display"
724
+                }
725
+            } elseif ($WT_TREE->getPreference('HIDE_GEDCOM_ERRORS') === '1') {
726
+                echo '<p class="ui-state-error">', $media_id, '</p>';
727
+            }
728
+            $objectNum++;
729
+        }
730
+    }
731
+
732
+    /**
733
+     * Print a row for the sources tab on the individual page.
734
+     *
735
+     * @param Fact $fact
736
+     * @param int $level
737
+     */
738
+    public static function printMainSources(Fact $fact, $level) {
739
+        $factrec = $fact->getGedcom();
740
+        $fact_id = $fact->getFactId();
741
+        $parent  = $fact->getParent();
742
+        $pid     = $parent->getXref();
743
+
744
+        $nlevel = $level + 1;
745
+        if ($fact->isPendingAddition()) {
746
+            $styleadd = 'new';
747
+            $can_edit = $level == 1 && $fact->canEdit();
748
+        } elseif ($fact->isPendingDeletion()) {
749
+            $styleadd = 'old';
750
+            $can_edit = false;
751
+        } else {
752
+            $styleadd = '';
753
+            $can_edit = $level == 1 && $fact->canEdit();
754
+        }
755
+
756
+        // -- find source for each fact
757
+        $ct    = preg_match_all("/($level SOUR (.+))/", $factrec, $match, PREG_SET_ORDER);
758
+        $spos2 = 0;
759
+        for ($j = 0; $j < $ct; $j++) {
760
+            $sid   = trim($match[$j][2], '@');
761
+            $spos1 = strpos($factrec, $match[$j][1], $spos2);
762
+            $spos2 = strpos($factrec, "\n$level", $spos1);
763
+            if (!$spos2) {
764
+                $spos2 = strlen($factrec);
765
+            }
766
+            $srec   = substr($factrec, $spos1, $spos2 - $spos1);
767
+            $source = Source::getInstance($sid, $fact->getParent()->getTree());
768
+            // Allow access to "1 SOUR @non_existent_source@", so it can be corrected/deleted
769
+            if (!$source || $source->canShow()) {
770
+                if ($level > 1) {
771
+                    echo '<tr class="row_sour2">';
772
+                } else {
773
+                    echo '<tr>';
774
+                }
775
+                echo '<td class="descriptionbox';
776
+                if ($level > 1) {
777
+                    echo ' rela';
778
+                }
779
+                echo ' ', $styleadd, ' width20">';
780
+                $factlines = explode("\n", $factrec); // 1 BIRT Y\n2 SOUR ...
781
+                $factwords = explode(" ", $factlines[0]); // 1 BIRT Y
782
+                $factname  = $factwords[1]; // BIRT
783
+                if ($factname == 'EVEN' || $factname == 'FACT') {
784
+                    // Add ' EVEN' to provide sensible output for an event with an empty TYPE record
785
+                    $ct = preg_match("/2 TYPE (.*)/", $factrec, $ematch);
786
+                    if ($ct > 0) {
787
+                        $factname = trim($ematch[1]);
788
+                        echo $factname;
789
+                    } else {
790
+                        echo GedcomTag::getLabel($factname, $parent);
791
+                    }
792
+                } elseif ($can_edit) {
793
+                    echo "<a onclick=\"return edit_record('$pid', '$fact_id');\" href=\"#\" title=\"", I18N::translate('Edit'), '">';
794
+                    if ($fact->getParent()->getTree()->getPreference('SHOW_FACT_ICONS')) {
795
+                        if ($level == 1) {
796
+                            echo '<i class="icon-source"></i> ';
797
+                        }
798
+                    }
799
+                    echo GedcomTag::getLabel($factname, $parent), '</a>';
800
+                    echo '<div class="editfacts">';
801
+                    if (preg_match('/^@.+@$/', $match[$j][2])) {
802
+                        // Inline sources can't be edited. Attempting to save one will convert it
803
+                        // into a link, and delete it.
804
+                        // e.g. "1 SOUR my source" becomes "1 SOUR @my source@" which does not exist.
805
+                        echo "<div class=\"editlink\"><a class=\"editicon\" onclick=\"return edit_record('$pid', '$fact_id');\" href=\"#\" title=\"" . I18N::translate('Edit') . "\"><span class=\"link_text\">" . I18N::translate('Edit') . "</span></a></div>";
806
+                        echo '<div class="copylink"><a class="copyicon" href="#" onclick="return copy_fact(\'', $pid, '\', \'', $fact_id, '\');" title="' . I18N::translate('Copy') . '"><span class="link_text">' . I18N::translate('Copy') . '</span></a></div>';
807
+                    }
808
+                    echo "<div class=\"deletelink\"><a class=\"deleteicon\" onclick=\"return delete_fact('" . I18N::translate('Are you sure you want to delete this fact?') . "', '$pid', '$fact_id');\" href=\"#\" title=\"" . I18N::translate('Delete') . "\"><span class=\"link_text\">" . I18N::translate('Delete') . "</span></a></div>";
809
+                    echo '</div>';
810
+                } else {
811
+                    echo GedcomTag::getLabel($factname, $parent);
812
+                }
813
+                echo '</td>';
814
+                echo '<td class="optionbox ', $styleadd, ' wrap">';
815
+                if ($source) {
816
+                    echo '<a href="', $source->getHtmlUrl(), '">', $source->getFullName(), '</a>';
817
+                    // PUBL
818
+                    $publ = $source->getFirstFact('PUBL');
819
+                    if ($publ) {
820
+                        echo GedcomTag::getLabelValue('PUBL', $publ->getValue());
821
+                    }
822
+                    // 2 RESN tags. Note, there can be more than one, such as "privacy" and "locked"
823
+                    if (preg_match_all("/\n2 RESN (.+)/", $factrec, $rmatches)) {
824
+                        foreach ($rmatches[1] as $rmatch) {
825
+                            echo '<br><span class="label">', GedcomTag::getLabel('RESN'), ':</span> <span class="field">';
826
+                            switch ($rmatch) {
827
+                            case 'none':
828
+                                // Note: "2 RESN none" is not valid gedcom, and the GUI will not let you add it.
829
+                                // However, webtrees privacy rules will interpret it as "show an otherwise private fact to public".
830
+                                echo '<i class="icon-resn-none"></i> ', I18N::translate('Show to visitors');
831
+                                break;
832
+                            case 'privacy':
833
+                                echo '<i class="icon-resn-privacy"></i> ', I18N::translate('Show to members');
834
+                                break;
835
+                            case 'confidential':
836
+                                echo '<i class="icon-resn-confidential"></i> ', I18N::translate('Show to managers');
837
+                                break;
838
+                            case 'locked':
839
+                                echo '<i class="icon-resn-locked"></i> ', I18N::translate('Only managers can edit');
840
+                                break;
841
+                            default:
842
+                                echo $rmatch;
843
+                                break;
844
+                            }
845
+                            echo '</span>';
846
+                        }
847
+                    }
848
+                    $cs = preg_match("/$nlevel EVEN (.*)/", $srec, $cmatch);
849
+                    if ($cs > 0) {
850
+                        echo '<br><span class="label">', GedcomTag::getLabel('EVEN'), ' </span><span class="field">', $cmatch[1], '</span>';
851
+                        $cs = preg_match("/" . ($nlevel + 1) . " ROLE (.*)/", $srec, $cmatch);
852
+                        if ($cs > 0) {
853
+                            echo '<br>&nbsp;&nbsp;&nbsp;&nbsp;<span class="label">', GedcomTag::getLabel('ROLE'), ' </span><span class="field">', $cmatch[1], '</span>';
854
+                        }
855
+                    }
856
+                    echo self::printSourceStructure(self::getSourceStructure($srec));
857
+                    echo '<div class="indent">';
858
+                    self::printMediaLinks($srec, $nlevel);
859
+                    if ($nlevel == 2) {
860
+                        self::printMediaLinks($source->getGedcom(), 1);
861
+                    }
862
+                    echo FunctionsPrint::printFactNotes($srec, $nlevel);
863
+                    if ($nlevel == 2) {
864
+                        echo FunctionsPrint::printFactNotes($source->getGedcom(), 1);
865
+                    }
866
+                    echo '</div>';
867
+                } else {
868
+                    echo $sid;
869
+                }
870
+                echo '</td></tr>';
871
+            }
872
+        }
873
+    }
874
+
875
+    /**
876
+     * Print SOUR structure
877
+     *  This function prints the input array of SOUR sub-records built by the
878
+     *  getSourceStructure() function.
879
+     *
880
+     * @param string[] $textSOUR
881
+     *
882
+     * @return string
883
+     */
884
+    public static function printSourceStructure($textSOUR) {
885
+        global $WT_TREE;
886
+        $html = '';
887
+
888
+        if ($textSOUR['PAGE']) {
889
+            $html .= GedcomTag::getLabelValue('PAGE', Filter::expandUrls($textSOUR['PAGE']));
890
+        }
891
+
892
+        if ($textSOUR['EVEN']) {
893
+            $html .= GedcomTag::getLabelValue('EVEN', Filter::escapeHtml($textSOUR['EVEN']));
894
+            if ($textSOUR['ROLE']) {
895
+                $html .= GedcomTag::getLabelValue('ROLE', Filter::escapeHtml($textSOUR['ROLE']));
896
+            }
897
+        }
898
+
899
+        if ($textSOUR['DATE'] || count($textSOUR['TEXT'])) {
900
+            if ($textSOUR['DATE']) {
901
+                $date = new Date($textSOUR['DATE']);
902
+                $html .= GedcomTag::getLabelValue('DATA:DATE', $date->display());
903
+            }
904
+            foreach ($textSOUR['TEXT'] as $text) {
905
+                $html .= GedcomTag::getLabelValue('TEXT', Filter::formatText($text, $WT_TREE));
906
+            }
907
+        }
908
+
909
+        if ($textSOUR['QUAY'] != '') {
910
+            $html .= GedcomTag::getLabelValue('QUAY', GedcomCodeQuay::getValue($textSOUR['QUAY']));
911
+        }
912
+
913
+        return '<div class="indent">' . $html . '</div>';
914
+    }
915
+
916
+    /**
917
+     * Extract SOUR structure from the incoming Source sub-record
918
+     * The output array is defined as follows:
919
+     *  $textSOUR['PAGE'] = Source citation
920
+     *  $textSOUR['EVEN'] = Event type
921
+     *  $textSOUR['ROLE'] = Role in event
922
+     *  $textSOUR['DATA'] = place holder (no text in this sub-record)
923
+     *  $textSOUR['DATE'] = Entry recording date
924
+     *  $textSOUR['TEXT'] = (array) Text from source
925
+     *  $textSOUR['QUAY'] = Certainty assessment
926
+     *
927
+     * @param string $srec
928
+     *
929
+     * @return string[]
930
+     */
931
+    public static function getSourceStructure($srec) {
932
+        // Set up the output array
933
+        $textSOUR = array(
934
+            'PAGE' => '',
935
+            'EVEN' => '',
936
+            'ROLE' => '',
937
+            'DATA' => '',
938
+            'DATE' => '',
939
+            'TEXT' => array(),
940
+            'QUAY' => '',
941
+        );
942
+
943
+        if ($srec) {
944
+            $subrecords = explode("\n", $srec);
945
+            for ($i = 0; $i < count($subrecords); $i++) {
946
+                $tag  = substr($subrecords[$i], 2, 4);
947
+                $text = substr($subrecords[$i], 7);
948
+                $i++;
949
+                for (; $i < count($subrecords); $i++) {
950
+                    $nextTag = substr($subrecords[$i], 2, 4);
951
+                    if ($nextTag != 'CONT') {
952
+                        $i--;
953
+                        break;
954
+                    }
955
+                    if ($nextTag == 'CONT') {
956
+                        $text .= "\n";
957
+                    }
958
+                    $text .= rtrim(substr($subrecords[$i], 7));
959
+                }
960
+                if ($tag == 'TEXT') {
961
+                    $textSOUR[$tag][] = $text;
962
+                } else {
963
+                    $textSOUR[$tag] = $text;
964
+                }
965
+            }
966
+        }
967
+
968
+        return $textSOUR;
969
+    }
970
+
971
+    /**
972
+     * Print a row for the notes tab on the individual page.
973
+     *
974
+     * @param Fact $fact
975
+     * @param int $level
976
+     */
977
+    public static function printMainNotes(Fact $fact, $level) {
978
+        $factrec = $fact->getGedcom();
979
+        $fact_id = $fact->getFactId();
980
+        $parent  = $fact->getParent();
981
+        $pid     = $parent->getXref();
982
+
983
+        if ($fact->isPendingAddition()) {
984
+            $styleadd = ' new';
985
+            $can_edit = $level == 1 && $fact->canEdit();
986
+        } elseif ($fact->isPendingDeletion()) {
987
+            $styleadd = ' old';
988
+            $can_edit = false;
989
+        } else {
990
+            $styleadd = '';
991
+            $can_edit = $level == 1 && $fact->canEdit();
992
+        }
993
+
994
+        $ct = preg_match_all("/$level NOTE (.*)/", $factrec, $match, PREG_SET_ORDER);
995
+        for ($j = 0; $j < $ct; $j++) {
996
+            // Note object, or inline note?
997
+            if (preg_match("/$level NOTE @(.*)@/", $match[$j][0], $nmatch)) {
998
+                $note = Note::getInstance($nmatch[1], $fact->getParent()->getTree());
999
+                if ($note && !$note->canShow()) {
1000
+                    continue;
1001
+                }
1002
+            } else {
1003
+                $note = null;
1004
+            }
1005
+
1006
+            if ($level >= 2) {
1007
+                echo '<tr class="row_note2"><td class="descriptionbox rela ', $styleadd, ' width20">';
1008
+            } else {
1009
+                echo '<tr><td class="descriptionbox ', $styleadd, ' width20">';
1010
+            }
1011
+            if ($can_edit) {
1012
+                echo '<a onclick="return edit_record(\'', $pid, '\', \'', $fact_id, '\');" href="#" title="', I18N::translate('Edit'), '">';
1013
+                if ($level < 2) {
1014
+                    if ($fact->getParent()->getTree()->getPreference('SHOW_FACT_ICONS')) {
1015
+                        echo '<i class="icon-note"></i> ';
1016
+                    }
1017
+                    if ($note) {
1018
+                        echo GedcomTag::getLabel('SHARED_NOTE');
1019
+                    } else {
1020
+                        echo GedcomTag::getLabel('NOTE');
1021
+                    }
1022
+                    echo '</a>';
1023
+                    echo '<div class="editfacts">';
1024
+                    echo "<div class=\"editlink\"><a class=\"editicon\" onclick=\"return edit_record('$pid', '$fact_id');\" href=\"#\" title=\"" . I18N::translate('Edit') . "\"><span class=\"link_text\">" . I18N::translate('Edit') . "</span></a></div>";
1025
+                    echo '<div class="copylink"><a class="copyicon" href="#" onclick="return copy_fact(\'', $pid, '\', \'', $fact_id, '\');" title="' . I18N::translate('Copy') . '"><span class="link_text">' . I18N::translate('Copy') . '</span></a></div>';
1026
+                    echo "<div class=\"deletelink\"><a class=\"deleteicon\" onclick=\"return delete_fact('" . I18N::translate('Are you sure you want to delete this fact?') . "', '$pid', '$fact_id');\" href=\"#\" title=\"" . I18N::translate('Delete') . "\"><span class=\"link_text\">" . I18N::translate('Delete') . "</span></a></div>";
1027
+                    if ($note) {
1028
+                        echo '<a class="icon-note" href="', $note->getHtmlUrl(), '" title="' . I18N::translate('View') . '"><span class="link_text">' . I18N::translate('View') . '</span></a>';
1029
+                    }
1030
+                    echo '</div>';
1031
+                }
1032
+            } else {
1033
+                if ($level < 2) {
1034
+                    if ($fact->getParent()->getTree()->getPreference('SHOW_FACT_ICONS')) {
1035
+                        echo '<i class="icon-note"></i> ';
1036
+                    }
1037
+                    if ($note) {
1038
+                        echo GedcomTag::getLabel('SHARED_NOTE');
1039
+                    } else {
1040
+                        echo GedcomTag::getLabel('NOTE');
1041
+                    }
1042
+                }
1043
+                $factlines = explode("\n", $factrec); // 1 BIRT Y\n2 NOTE ...
1044
+                $factwords = explode(" ", $factlines[0]); // 1 BIRT Y
1045
+                $factname  = $factwords[1]; // BIRT
1046
+                $parent    = GedcomRecord::getInstance($pid, $fact->getParent()->getTree());
1047
+                if ($factname == 'EVEN' || $factname == 'FACT') {
1048
+                    // Add ' EVEN' to provide sensible output for an event with an empty TYPE record
1049
+                    $ct = preg_match("/2 TYPE (.*)/", $factrec, $ematch);
1050
+                    if ($ct > 0) {
1051
+                        $factname = trim($ematch[1]);
1052
+                        echo $factname;
1053
+                    } else {
1054
+                        echo GedcomTag::getLabel($factname, $parent);
1055
+                    }
1056
+                } elseif ($factname != 'NOTE') {
1057
+                    // Note is already printed
1058
+                    echo GedcomTag::getLabel($factname, $parent);
1059
+                    if ($note) {
1060
+                        echo '<div class="editfacts"><a class="icon-note" href="', $note->getHtmlUrl(), '" title="' . I18N::translate('View') . '"><span class="link_text">' . I18N::translate('View') . '</span></a></div>';
1061
+
1062
+                    }
1063
+                }
1064
+            }
1065
+            echo '</td>';
1066
+            if ($note) {
1067
+                // Note objects
1068
+                if (Module::getModuleByName('GEDFact_assistant')) {
1069
+                    // If Census assistant installed, allow it to format the note
1070
+                    $text = CensusAssistantModule::formatCensusNote($note);
1071
+                } else {
1072
+                    $text = Filter::formatText($note->getNote(), $fact->getParent()->getTree());
1073
+                }
1074
+            } else {
1075
+                // Inline notes
1076
+                $nrec = Functions::getSubRecord($level, "$level NOTE", $factrec, $j + 1);
1077
+                $text = $match[$j][1] . Functions::getCont($level + 1, $nrec);
1078
+                $text = Filter::formatText($text, $fact->getParent()->getTree());
1079
+            }
1080
+
1081
+            echo '<td class="optionbox', $styleadd, ' wrap">';
1082
+            echo $text;
1083
+
1084
+            if (!empty($noterec)) {
1085
+                echo self::printFactSources($noterec, 1);
1086
+            }
1087
+
1088
+            // 2 RESN tags. Note, there can be more than one, such as "privacy" and "locked"
1089
+            if (preg_match_all("/\n2 RESN (.+)/", $factrec, $matches)) {
1090
+                foreach ($matches[1] as $match) {
1091
+                    echo '<br><span class="label">', GedcomTag::getLabel('RESN'), ':</span> <span class="field">';
1092
+                    switch ($match) {
1093
+                    case 'none':
1094
+                        // Note: "2 RESN none" is not valid gedcom, and the GUI will not let you add it.
1095
+                        // However, webtrees privacy rules will interpret it as "show an otherwise private fact to public".
1096
+                        echo '<i class="icon-resn-none"></i> ', I18N::translate('Show to visitors');
1097
+                        break;
1098
+                    case 'privacy':
1099
+                        echo '<i class="icon-resn-privacy"></i> ', I18N::translate('Show to members');
1100
+                        break;
1101
+                    case 'confidential':
1102
+                        echo '<i class="icon-resn-confidential"></i> ', I18N::translate('Show to managers');
1103
+                        break;
1104
+                    case 'locked':
1105
+                        echo '<i class="icon-resn-locked"></i> ', I18N::translate('Only managers can edit');
1106
+                        break;
1107
+                    default:
1108
+                        echo $match;
1109
+                        break;
1110
+                    }
1111
+                    echo '</span>';
1112
+                }
1113
+            }
1114
+            echo '</td></tr>';
1115
+        }
1116
+    }
1117
+
1118
+    /**
1119
+     * Print a row for the media tab on the individual page.
1120
+     *
1121
+     * @param Fact $fact
1122
+     * @param int $level
1123
+     */
1124
+    public static function printMainMedia(Fact $fact, $level) {
1125
+        $factrec = $fact->getGedcom();
1126
+        $parent  = $fact->getParent();
1127
+
1128
+        if ($fact->isPendingAddition()) {
1129
+            $styleadd = 'new';
1130
+            $can_edit = $level == 1 && $fact->canEdit();
1131
+        } elseif ($fact->isPendingDeletion()) {
1132
+            $styleadd = 'old';
1133
+            $can_edit = false;
1134
+        } else {
1135
+            $styleadd = '';
1136
+            $can_edit = $level == 1 && $fact->canEdit();
1137
+        }
1138
+
1139
+        // -- find source for each fact
1140
+        preg_match_all('/(?:^|\n)' . $level . ' OBJE @(.*)@/', $factrec, $matches);
1141
+        foreach ($matches[1] as $xref) {
1142
+            $media = Media::getInstance($xref, $fact->getParent()->getTree());
1143
+            // Allow access to "1 OBJE @non_existent_source@", so it can be corrected/deleted
1144
+            if (!$media || $media->canShow()) {
1145
+                if ($level > 1) {
1146
+                    echo '<tr class="row_obje2">';
1147
+                } else {
1148
+                    echo '<tr>';
1149
+                }
1150
+                echo '<td class="descriptionbox';
1151
+                if ($level > 1) {
1152
+                    echo ' rela';
1153
+                }
1154
+                echo ' ', $styleadd, ' width20">';
1155
+                preg_match("/^\d (\w*)/", $factrec, $factname);
1156
+                $factlines = explode("\n", $factrec); // 1 BIRT Y\n2 SOUR ...
1157
+                $factwords = explode(" ", $factlines[0]); // 1 BIRT Y
1158
+                $factname  = $factwords[1]; // BIRT
1159
+                if ($factname == 'EVEN' || $factname == 'FACT') {
1160
+                    // Add ' EVEN' to provide sensible output for an event with an empty TYPE record
1161
+                    $ct = preg_match("/2 TYPE (.*)/", $factrec, $ematch);
1162
+                    if ($ct > 0) {
1163
+                        $factname = $ematch[1];
1164
+                        echo $factname;
1165
+                    } else {
1166
+                        echo GedcomTag::getLabel($factname, $parent);
1167
+                    }
1168
+                } elseif ($can_edit) {
1169
+                    echo '<a onclick="window.open(\'addmedia.php?action=editmedia&amp;pid=', $media->getXref(), '\', \'_blank\', edit_window_specs); return false;" href="#" title="', I18N::translate('Edit'), '">';
1170
+                    echo GedcomTag::getLabel($factname, $parent), '</a>';
1171
+                    echo '<div class="editfacts">';
1172
+                    echo '<div class="editlink"><a class="editicon" onclick="window.open(\'addmedia.php?action=editmedia&amp;pid=', $media->getXref(), '\', \'_blank\', edit_window_specs); return false;" href="#" title="', I18N::translate('Edit'), '"><span class="link_text">', I18N::translate('Edit'), '</span></a></div>';
1173
+                    echo '<div class="copylink"><a class="copyicon" href="#" onclick="jQuery.post(\'action.php\',{action:\'copy-fact\', type:\'\', factgedcom:\'' . rawurlencode($factrec) . '\'},function(){location.reload();})" title="' . I18N::translate('Copy') . '"><span class="link_text">' . I18N::translate('Copy') . '</span></a></div>';
1174
+                    echo '<div class="deletelink"><a class="deleteicon" onclick="return delete_fact(\'', I18N::translate('Are you sure you want to delete this fact?'), '\', \'', $parent->getXref(), '\', \'', $fact->getFactId(), '\');" href="#" title="', I18N::translate('Delete'), '"><span class="link_text">', I18N::translate('Delete'), '</span></a></div>';
1175
+                    echo '</div>';
1176
+                } else {
1177
+                    echo GedcomTag::getLabel($factname, $parent);
1178
+                }
1179
+                echo '</td>';
1180
+                echo '<td class="optionbox ', $styleadd, ' wrap">';
1181
+                if ($media) {
1182
+                    echo '<span class="field">';
1183
+                    echo $media->displayImage();
1184
+                    echo '<a href="' . $media->getHtmlUrl() . '">';
1185
+                    echo '<em>';
1186
+                    foreach ($media->getAllNames() as $name) {
1187
+                        if ($name['type'] != 'TITL') {
1188
+                            echo '<br>';
1189
+                        }
1190
+                        echo $name['full'];
1191
+                    }
1192
+                    echo '</em>';
1193
+                    echo '</a>';
1194
+                    echo '</span>';
1195
+
1196
+                    echo GedcomTag::getLabelValue('FORM', $media->mimeType());
1197
+                    $imgsize = $media->getImageAttributes('main');
1198
+                    if (!empty($imgsize['WxH'])) {
1199
+                        echo GedcomTag::getLabelValue('__IMAGE_SIZE__', $imgsize['WxH']);
1200
+                    }
1201
+                    if ($media->getFilesizeraw() > 0) {
1202
+                        echo GedcomTag::getLabelValue('__FILE_SIZE__', $media->getFilesize());
1203
+                    }
1204
+                    $mediatype = $media->getMediaType();
1205
+                    if ($mediatype) {
1206
+                        echo GedcomTag::getLabelValue('TYPE', GedcomTag::getFileFormTypeValue($mediatype));
1207
+                    }
1208
+
1209
+                    switch ($media->isPrimary()) {
1210
+                    case 'Y':
1211
+                        echo GedcomTag::getLabelValue('_PRIM', I18N::translate('yes'));
1212
+                        break;
1213
+                    case 'N':
1214
+                        echo GedcomTag::getLabelValue('_PRIM', I18N::translate('no'));
1215
+                        break;
1216
+                    }
1217
+                    echo FunctionsPrint::printFactNotes($media->getGedcom(), 1);
1218
+                    echo self::printFactSources($media->getGedcom(), 1);
1219
+                } else {
1220
+                    echo $xref;
1221
+                }
1222
+                echo '</td></tr>';
1223
+            }
1224
+        }
1225
+    }
1226 1226
 }
Please login to merge, or discard this patch.
Switch Indentation   +306 added lines, -306 removed lines patch added patch discarded remove patch
@@ -60,34 +60,34 @@  discard block
 block discarded – undo
60 60
 
61 61
 		// Some facts don't get printed here ...
62 62
 		switch ($fact->getTag()) {
63
-		case 'NOTE':
64
-			self::printMainNotes($fact, 1);
65
-
66
-			return;
67
-		case 'SOUR':
68
-			self::printMainSources($fact, 1);
69
-
70
-			return;
71
-		case 'OBJE':
72
-			self::printMainMedia($fact, 1);
73
-
74
-			return;
75
-		case 'FAMC':
76
-		case 'FAMS':
77
-		case 'CHIL':
78
-		case 'HUSB':
79
-		case 'WIFE':
80
-			// These are internal links, not facts
81
-			return;
82
-		case '_WT_OBJE_SORT':
83
-			// These links are used internally to record the sort order.
84
-			return;
85
-		default:
86
-			// Hide unrecognized/custom tags?
87
-			if ($fact->getParent()->getTree()->getPreference('HIDE_GEDCOM_ERRORS') === '0' && !GedcomTag::isTag($fact->getTag())) {
88
-				return;
89
-			}
90
-			break;
63
+		    case 'NOTE':
64
+			    self::printMainNotes($fact, 1);
65
+
66
+			    return;
67
+		    case 'SOUR':
68
+			    self::printMainSources($fact, 1);
69
+
70
+			    return;
71
+		    case 'OBJE':
72
+			    self::printMainMedia($fact, 1);
73
+
74
+			    return;
75
+		    case 'FAMC':
76
+		    case 'FAMS':
77
+		    case 'CHIL':
78
+		    case 'HUSB':
79
+		    case 'WIFE':
80
+			    // These are internal links, not facts
81
+			    return;
82
+		    case '_WT_OBJE_SORT':
83
+			    // These links are used internally to record the sort order.
84
+			    return;
85
+		    default:
86
+			    // Hide unrecognized/custom tags?
87
+			    if ($fact->getParent()->getTree()->getPreference('HIDE_GEDCOM_ERRORS') === '0' && !GedcomTag::isTag($fact->getTag())) {
88
+				    return;
89
+			    }
90
+			    break;
91 91
 		}
92 92
 
93 93
 		// Who is this fact about? Need it to translate fact label correctly
@@ -131,35 +131,35 @@  discard block
 block discarded – undo
131 131
 		}
132 132
 
133 133
 		switch ($fact->getTag()) {
134
-		case 'EVEN':
135
-		case 'FACT':
136
-			if (GedcomTag::isTag($type)) {
137
-				// Some users (just Meliza?) use "1 EVEN/2 TYPE BIRT". Translate the TYPE.
138
-				$label = GedcomTag::getLabel($type, $label_person);
139
-				$type  = ''; // Do not print this again
140
-			} elseif ($type) {
141
-				// We don't have a translation for $type - but a custom translation might exist.
142
-				$label = I18N::translate(Filter::escapeHtml($type));
143
-				$type  = ''; // Do not print this again
144
-			} else {
145
-				// An unspecified fact/event
146
-				$label = $fact->getLabel();
147
-			}
148
-			break;
149
-		case 'MARR':
150
-			// This is a hack for a proprietory extension. Is it still used/needed?
151
-			$utype = strtoupper($type);
152
-			if ($utype == 'CIVIL' || $utype == 'PARTNERS' || $utype == 'RELIGIOUS') {
153
-				$label = GedcomTag::getLabel('MARR_' . $utype, $label_person);
154
-				$type  = ''; // Do not print this again
155
-			} else {
156
-				$label = $fact->getLabel();
157
-			}
158
-			break;
159
-		default:
160
-			// Normal fact/event
161
-			$label = $fact->getLabel();
162
-			break;
134
+		    case 'EVEN':
135
+		    case 'FACT':
136
+			    if (GedcomTag::isTag($type)) {
137
+				    // Some users (just Meliza?) use "1 EVEN/2 TYPE BIRT". Translate the TYPE.
138
+				    $label = GedcomTag::getLabel($type, $label_person);
139
+				    $type  = ''; // Do not print this again
140
+			    } elseif ($type) {
141
+				    // We don't have a translation for $type - but a custom translation might exist.
142
+				    $label = I18N::translate(Filter::escapeHtml($type));
143
+				    $type  = ''; // Do not print this again
144
+			    } else {
145
+				    // An unspecified fact/event
146
+				    $label = $fact->getLabel();
147
+			    }
148
+			    break;
149
+		    case 'MARR':
150
+			    // This is a hack for a proprietory extension. Is it still used/needed?
151
+			    $utype = strtoupper($type);
152
+			    if ($utype == 'CIVIL' || $utype == 'PARTNERS' || $utype == 'RELIGIOUS') {
153
+				    $label = GedcomTag::getLabel('MARR_' . $utype, $label_person);
154
+				    $type  = ''; // Do not print this again
155
+			    } else {
156
+				    $label = $fact->getLabel();
157
+			    }
158
+			    break;
159
+		    default:
160
+			    // Normal fact/event
161
+			    $label = $fact->getLabel();
162
+			    break;
163 163
 		}
164 164
 
165 165
 		echo '<tr class="', $styleadd, '">';
@@ -208,15 +208,15 @@  discard block
 block discarded – undo
208 208
 		}
209 209
 
210 210
 		switch ($fact->getTag()) {
211
-		case '_BIRT_CHIL':
212
-			echo '<br>', /* I18N: Abbreviation for "number %s" */
213
-			I18N::translate('#%s', ++$n_chil);
214
-			break;
215
-		case '_BIRT_GCHI':
216
-		case '_BIRT_GCH1':
217
-		case '_BIRT_GCH2':
218
-			echo '<br>', I18N::translate('#%s', ++$n_gchi);
219
-			break;
211
+		    case '_BIRT_CHIL':
212
+			    echo '<br>', /* I18N: Abbreviation for "number %s" */
213
+			    I18N::translate('#%s', ++$n_chil);
214
+			    break;
215
+		    case '_BIRT_GCHI':
216
+		    case '_BIRT_GCH1':
217
+		    case '_BIRT_GCH2':
218
+			    echo '<br>', I18N::translate('#%s', ++$n_gchi);
219
+			    break;
220 220
 		}
221 221
 
222 222
 		echo '</td><td class="optionbox ', $styleadd, ' wrap">';
@@ -237,92 +237,92 @@  discard block
 block discarded – undo
237 237
 
238 238
 		// Print the value of this fact/event
239 239
 		switch ($fact->getTag()) {
240
-		case 'ADDR':
241
-			echo $fact->getValue();
242
-			break;
243
-		case 'AFN':
244
-			echo '<div class="field"><a href="https://familysearch.org/search/tree/results#count=20&query=afn:', Filter::escapeUrl($fact->getValue()), '">', Filter::escapeHtml($fact->getValue()), '</a></div>';
245
-			break;
246
-		case 'ASSO':
247
-			// we handle this later, in format_asso_rela_record()
248
-			break;
249
-		case 'EMAIL':
250
-		case 'EMAI':
251
-		case '_EMAIL':
252
-			echo '<div class="field"><a href="mailto:', Filter::escapeHtml($fact->getValue()), '">', Filter::escapeHtml($fact->getValue()), '</a></div>';
253
-			break;
254
-		case 'FILE':
255
-			if (Auth::isEditor($fact->getParent()->getTree())) {
256
-				echo '<div class="field">', Filter::escapeHtml($fact->getValue()), '</div>';
257
-			}
258
-			break;
259
-		case 'RESN':
260
-			echo '<div class="field">';
261
-			switch ($fact->getValue()) {
262
-			case 'none':
263
-			// Note: "1 RESN none" is not valid gedcom.
264
-			// However, webtrees privacy rules will interpret it as "show an otherwise private record to public".
265
-			echo '<i class="icon-resn-none"></i> ', I18N::translate('Show to visitors');
266
-			break;
267
-			case 'privacy':
268
-			echo '<i class="icon-class-none"></i> ', I18N::translate('Show to members');
269
-			break;
270
-			case 'confidential':
271
-			echo '<i class="icon-confidential-none"></i> ', I18N::translate('Show to managers');
272
-			break;
273
-			case 'locked':
274
-			echo '<i class="icon-locked-none"></i> ', I18N::translate('Only managers can edit');
275
-			break;
276
-			default:
277
-			echo Filter::escapeHtml($fact->getValue());
278
-			break;
279
-			}
240
+		    case 'ADDR':
241
+			    echo $fact->getValue();
242
+			    break;
243
+		    case 'AFN':
244
+			    echo '<div class="field"><a href="https://familysearch.org/search/tree/results#count=20&query=afn:', Filter::escapeUrl($fact->getValue()), '">', Filter::escapeHtml($fact->getValue()), '</a></div>';
245
+			    break;
246
+		    case 'ASSO':
247
+			    // we handle this later, in format_asso_rela_record()
248
+			    break;
249
+		    case 'EMAIL':
250
+		    case 'EMAI':
251
+		    case '_EMAIL':
252
+			    echo '<div class="field"><a href="mailto:', Filter::escapeHtml($fact->getValue()), '">', Filter::escapeHtml($fact->getValue()), '</a></div>';
253
+			    break;
254
+		    case 'FILE':
255
+			    if (Auth::isEditor($fact->getParent()->getTree())) {
256
+				    echo '<div class="field">', Filter::escapeHtml($fact->getValue()), '</div>';
257
+			    }
258
+			    break;
259
+		    case 'RESN':
260
+			    echo '<div class="field">';
261
+			    switch ($fact->getValue()) {
262
+			        case 'none':
263
+			        // Note: "1 RESN none" is not valid gedcom.
264
+			        // However, webtrees privacy rules will interpret it as "show an otherwise private record to public".
265
+			        echo '<i class="icon-resn-none"></i> ', I18N::translate('Show to visitors');
266
+			        break;
267
+			        case 'privacy':
268
+			        echo '<i class="icon-class-none"></i> ', I18N::translate('Show to members');
269
+			        break;
270
+			        case 'confidential':
271
+			        echo '<i class="icon-confidential-none"></i> ', I18N::translate('Show to managers');
272
+			        break;
273
+			        case 'locked':
274
+			        echo '<i class="icon-locked-none"></i> ', I18N::translate('Only managers can edit');
275
+			        break;
276
+			        default:
277
+			        echo Filter::escapeHtml($fact->getValue());
278
+			        break;
279
+			    }
280 280
 				echo '</div>';
281 281
 				break;
282
-		case 'PUBL': // Publication details might contain URLs.
283
-			echo '<div class="field">', Filter::expandUrls($fact->getValue()), '</div>';
284
-			break;
285
-		case 'REPO':
286
-			if (preg_match('/^@(' . WT_REGEX_XREF . ')@$/', $fact->getValue(), $match)) {
287
-				self::printRepositoryRecord($match[1]);
288
-			} else {
289
-				echo '<div class="error">', Filter::escapeHtml($fact->getValue()), '</div>';
290
-			}
291
-			break;
292
-		case 'URL':
293
-		case '_URL':
294
-		case 'WWW':
295
-			echo '<div class="field"><a href="', Filter::escapeHtml($fact->getValue()), '">', Filter::escapeHtml($fact->getValue()), '</a></div>';
296
-			break;
297
-		case 'TEXT': // 0 SOUR / 1 TEXT
298
-			echo '<div class="field">', nl2br(Filter::escapeHtml($fact->getValue()), false), '</div>';
299
-			break;
300
-		default:
301
-			// Display the value for all other facts/events
302
-			switch ($fact->getValue()) {
303
-			case '':
304
-			// Nothing to display
305
-			break;
306
-			case 'N':
307
-			// Not valid GEDCOM
308
-			echo '<div class="field">', I18N::translate('No'), '</div>';
309
-			break;
310
-			case 'Y':
311
-			// Do not display "Yes".
312
-			break;
313
-			default:
314
-			if (preg_match('/^@(' . WT_REGEX_XREF . ')@$/', $fact->getValue(), $match)) {
315
-			$target = GedcomRecord::getInstance($match[1], $fact->getParent()->getTree());
316
-			if ($target) {
317
-				echo '<div><a href="', $target->getHtmlUrl(), '">', $target->getFullName(), '</a></div>';
318
-			} else {
319
-				echo '<div class="error">', Filter::escapeHtml($fact->getValue()), '</div>';
320
-			}
321
-			} else {
322
-			echo '<div class="field"><span dir="auto">', Filter::escapeHtml($fact->getValue()), '</span></div>';
323
-			}
324
-			break;
325
-			}
282
+		    case 'PUBL': // Publication details might contain URLs.
283
+			    echo '<div class="field">', Filter::expandUrls($fact->getValue()), '</div>';
284
+			    break;
285
+		    case 'REPO':
286
+			    if (preg_match('/^@(' . WT_REGEX_XREF . ')@$/', $fact->getValue(), $match)) {
287
+				    self::printRepositoryRecord($match[1]);
288
+			    } else {
289
+				    echo '<div class="error">', Filter::escapeHtml($fact->getValue()), '</div>';
290
+			    }
291
+			    break;
292
+		    case 'URL':
293
+		    case '_URL':
294
+		    case 'WWW':
295
+			    echo '<div class="field"><a href="', Filter::escapeHtml($fact->getValue()), '">', Filter::escapeHtml($fact->getValue()), '</a></div>';
296
+			    break;
297
+		    case 'TEXT': // 0 SOUR / 1 TEXT
298
+			    echo '<div class="field">', nl2br(Filter::escapeHtml($fact->getValue()), false), '</div>';
299
+			    break;
300
+		    default:
301
+			    // Display the value for all other facts/events
302
+			    switch ($fact->getValue()) {
303
+			        case '':
304
+			        // Nothing to display
305
+			        break;
306
+			        case 'N':
307
+			        // Not valid GEDCOM
308
+			        echo '<div class="field">', I18N::translate('No'), '</div>';
309
+			        break;
310
+			        case 'Y':
311
+			        // Do not display "Yes".
312
+			        break;
313
+			        default:
314
+			        if (preg_match('/^@(' . WT_REGEX_XREF . ')@$/', $fact->getValue(), $match)) {
315
+			        $target = GedcomRecord::getInstance($match[1], $fact->getParent()->getTree());
316
+			        if ($target) {
317
+				        echo '<div><a href="', $target->getHtmlUrl(), '">', $target->getFullName(), '</a></div>';
318
+			        } else {
319
+				        echo '<div class="error">', Filter::escapeHtml($fact->getValue()), '</div>';
320
+			        }
321
+			        } else {
322
+			        echo '<div class="field"><span dir="auto">', Filter::escapeHtml($fact->getValue()), '</span></div>';
323
+			        }
324
+			        break;
325
+			    }
326 326
 				break;
327 327
 		}
328 328
 
@@ -362,123 +362,123 @@  discard block
 block discarded – undo
362 362
 		preg_match_all('/\n2 (' . WT_REGEX_TAG . ') (.+)/', $fact->getGedcom(), $matches, PREG_SET_ORDER);
363 363
 		foreach ($matches as $match) {
364 364
 			switch ($match[1]) {
365
-			case 'DATE':
366
-			case 'TIME':
367
-			case 'AGE':
368
-			case 'PLAC':
369
-			case 'ADDR':
370
-			case 'ALIA':
371
-			case 'ASSO':
372
-			case '_ASSO':
373
-			case 'DESC':
374
-			case 'RELA':
375
-			case 'STAT':
376
-			case 'TEMP':
377
-			case 'TYPE':
378
-			case 'FAMS':
379
-			case 'CONT':
380
-				// These were already shown at the beginning
381
-				break;
382
-			case 'NOTE':
383
-			case 'OBJE':
384
-			case 'SOUR':
385
-				// These will be shown at the end
386
-				break;
387
-			case '_UID':
388
-			case 'RIN':
389
-				// These don't belong at level 2, so do not display them.
390
-				// They are only shown when editing.
391
-				break;
392
-			case 'EVEN': // 0 SOUR / 1 DATA / 2 EVEN / 3 DATE / 3 PLAC
393
-				$events = array();
394
-				foreach (preg_split('/ *, */', $match[2]) as $event) {
395
-					$events[] = GedcomTag::getLabel($event);
396
-				}
397
-				if (count($events) == 1) {
398
-					echo GedcomTag::getLabelValue('EVEN', $event);
399
-				} else {
400
-					echo GedcomTag::getLabelValue('EVEN', implode(I18N::$list_separator, $events));
401
-				}
402
-				if (preg_match('/\n3 DATE (.+)/', $fact->getGedcom(), $date_match)) {
403
-					$date = new Date($date_match[1]);
404
-					echo GedcomTag::getLabelValue('DATE', $date->display());
405
-				}
406
-				if (preg_match('/\n3 PLAC (.+)/', $fact->getGedcom(), $plac_match)) {
407
-					echo GedcomTag::getLabelValue('PLAC', $plac_match[1]);
408
-				}
409
-				break;
410
-			case 'FAMC': // 0 INDI / 1 ADOP / 2 FAMC / 3 ADOP
411
-				$family = Family::getInstance(str_replace('@', '', $match[2]), $fact->getParent()->getTree());
412
-				if ($family) {
413
-					echo GedcomTag::getLabelValue('FAM', '<a href="' . $family->getHtmlUrl() . '">' . $family->getFullName() . '</a>');
414
-					if (preg_match('/\n3 ADOP (HUSB|WIFE|BOTH)/', $fact->getGedcom(), $match)) {
415
-						echo GedcomTag::getLabelValue('ADOP', GedcomCodeAdop::getValue($match[1], $label_person));
416
-					}
417
-				} else {
418
-					echo GedcomTag::getLabelValue('FAM', '<span class="error">' . $match[2] . '</span>');
419
-				}
420
-				break;
421
-			case '_WT_USER':
422
-				$user = User::findByIdentifier($match[2]); // may not exist
423
-				if ($user) {
424
-					echo GedcomTag::getLabelValue('_WT_USER', $user->getRealNameHtml());
425
-				} else {
426
-					echo GedcomTag::getLabelValue('_WT_USER', Filter::escapeHtml($match[2]));
427
-				}
428
-				break;
429
-			case 'RESN':
430
-				switch ($match[2]) {
431
-				case 'none':
432
-				// Note: "2 RESN none" is not valid gedcom.
433
-				// However, webtrees privacy rules will interpret it as "show an otherwise private fact to public".
434
-				echo GedcomTag::getLabelValue('RESN', '<i class="icon-resn-none"></i> ' . I18N::translate('Show to visitors'));
435
-				break;
436
-				case 'privacy':
437
-				echo GedcomTag::getLabelValue('RESN', '<i class="icon-resn-privacy"></i> ' . I18N::translate('Show to members'));
438
-				break;
439
-				case 'confidential':
440
-				echo GedcomTag::getLabelValue('RESN', '<i class="icon-resn-confidential"></i> ' . I18N::translate('Show to managers'));
441
-				break;
442
-				case 'locked':
443
-				echo GedcomTag::getLabelValue('RESN', '<i class="icon-resn-locked"></i> ' . I18N::translate('Only managers can edit'));
444
-				break;
445
-				default:
446
-				echo GedcomTag::getLabelValue('RESN', Filter::escapeHtml($match[2]));
447
-				break;
448
-				}
365
+			    case 'DATE':
366
+			    case 'TIME':
367
+			    case 'AGE':
368
+			    case 'PLAC':
369
+			    case 'ADDR':
370
+			    case 'ALIA':
371
+			    case 'ASSO':
372
+			    case '_ASSO':
373
+			    case 'DESC':
374
+			    case 'RELA':
375
+			    case 'STAT':
376
+			    case 'TEMP':
377
+			    case 'TYPE':
378
+			    case 'FAMS':
379
+			    case 'CONT':
380
+				    // These were already shown at the beginning
381
+				    break;
382
+			    case 'NOTE':
383
+			    case 'OBJE':
384
+			    case 'SOUR':
385
+				    // These will be shown at the end
386
+				    break;
387
+			    case '_UID':
388
+			    case 'RIN':
389
+				    // These don't belong at level 2, so do not display them.
390
+				    // They are only shown when editing.
391
+				    break;
392
+			    case 'EVEN': // 0 SOUR / 1 DATA / 2 EVEN / 3 DATE / 3 PLAC
393
+				    $events = array();
394
+				    foreach (preg_split('/ *, */', $match[2]) as $event) {
395
+					    $events[] = GedcomTag::getLabel($event);
396
+				    }
397
+				    if (count($events) == 1) {
398
+					    echo GedcomTag::getLabelValue('EVEN', $event);
399
+				    } else {
400
+					    echo GedcomTag::getLabelValue('EVEN', implode(I18N::$list_separator, $events));
401
+				    }
402
+				    if (preg_match('/\n3 DATE (.+)/', $fact->getGedcom(), $date_match)) {
403
+					    $date = new Date($date_match[1]);
404
+					    echo GedcomTag::getLabelValue('DATE', $date->display());
405
+				    }
406
+				    if (preg_match('/\n3 PLAC (.+)/', $fact->getGedcom(), $plac_match)) {
407
+					    echo GedcomTag::getLabelValue('PLAC', $plac_match[1]);
408
+				    }
409
+				    break;
410
+			    case 'FAMC': // 0 INDI / 1 ADOP / 2 FAMC / 3 ADOP
411
+				    $family = Family::getInstance(str_replace('@', '', $match[2]), $fact->getParent()->getTree());
412
+				    if ($family) {
413
+					    echo GedcomTag::getLabelValue('FAM', '<a href="' . $family->getHtmlUrl() . '">' . $family->getFullName() . '</a>');
414
+					    if (preg_match('/\n3 ADOP (HUSB|WIFE|BOTH)/', $fact->getGedcom(), $match)) {
415
+						    echo GedcomTag::getLabelValue('ADOP', GedcomCodeAdop::getValue($match[1], $label_person));
416
+					    }
417
+				    } else {
418
+					    echo GedcomTag::getLabelValue('FAM', '<span class="error">' . $match[2] . '</span>');
419
+				    }
420
+				    break;
421
+			    case '_WT_USER':
422
+				    $user = User::findByIdentifier($match[2]); // may not exist
423
+				    if ($user) {
424
+					    echo GedcomTag::getLabelValue('_WT_USER', $user->getRealNameHtml());
425
+				    } else {
426
+					    echo GedcomTag::getLabelValue('_WT_USER', Filter::escapeHtml($match[2]));
427
+				    }
428
+				    break;
429
+			    case 'RESN':
430
+				    switch ($match[2]) {
431
+				        case 'none':
432
+				        // Note: "2 RESN none" is not valid gedcom.
433
+				        // However, webtrees privacy rules will interpret it as "show an otherwise private fact to public".
434
+				        echo GedcomTag::getLabelValue('RESN', '<i class="icon-resn-none"></i> ' . I18N::translate('Show to visitors'));
435
+				        break;
436
+				        case 'privacy':
437
+				        echo GedcomTag::getLabelValue('RESN', '<i class="icon-resn-privacy"></i> ' . I18N::translate('Show to members'));
438
+				        break;
439
+				        case 'confidential':
440
+				        echo GedcomTag::getLabelValue('RESN', '<i class="icon-resn-confidential"></i> ' . I18N::translate('Show to managers'));
441
+				        break;
442
+				        case 'locked':
443
+				        echo GedcomTag::getLabelValue('RESN', '<i class="icon-resn-locked"></i> ' . I18N::translate('Only managers can edit'));
444
+				        break;
445
+				        default:
446
+				        echo GedcomTag::getLabelValue('RESN', Filter::escapeHtml($match[2]));
447
+				        break;
448
+				    }
449 449
 					break;
450
-			case 'CALN':
451
-				echo GedcomTag::getLabelValue('CALN', Filter::expandUrls($match[2]));
452
-				break;
453
-			case 'FORM': // 0 OBJE / 1 FILE / 2 FORM / 3 TYPE
454
-				echo GedcomTag::getLabelValue('FORM', $match[2]);
455
-				if (preg_match('/\n3 TYPE (.+)/', $fact->getGedcom(), $type_match)) {
456
-					echo GedcomTag::getLabelValue('TYPE', GedcomTag::getFileFormTypeValue($type_match[1]));
457
-				}
458
-				break;
459
-			case 'URL':
460
-			case '_URL':
461
-			case 'WWW':
462
-				$link = '<a href="' . Filter::escapeHtml($match[2]) . '">' . Filter::escapeHtml($match[2]) . '</a>';
463
-				echo GedcomTag::getLabelValue($fact->getTag() . ':' . $match[1], $link);
464
-				break;
465
-			default:
466
-				if ($fact->getParent()->getTree()->getPreference('HIDE_GEDCOM_ERRORS') === '1' || GedcomTag::isTag($match[1])) {
467
-					if (preg_match('/^@(' . WT_REGEX_XREF . ')@$/', $match[2], $xmatch)) {
468
-						// Links
469
-						$linked_record = GedcomRecord::getInstance($xmatch[1], $fact->getParent()->getTree());
470
-						if ($linked_record) {
471
-							$link = '<a href="' . $linked_record->getHtmlUrl() . '">' . $linked_record->getFullName() . '</a>';
472
-							echo GedcomTag::getLabelValue($fact->getTag() . ':' . $match[1], $link);
473
-						} else {
474
-							echo GedcomTag::getLabelValue($fact->getTag() . ':' . $match[1], Filter::escapeHtml($match[2]));
475
-						}
476
-					} else {
477
-						// Non links
478
-						echo GedcomTag::getLabelValue($fact->getTag() . ':' . $match[1], Filter::escapeHtml($match[2]));
479
-					}
480
-				}
481
-				break;
450
+			    case 'CALN':
451
+				    echo GedcomTag::getLabelValue('CALN', Filter::expandUrls($match[2]));
452
+				    break;
453
+			    case 'FORM': // 0 OBJE / 1 FILE / 2 FORM / 3 TYPE
454
+				    echo GedcomTag::getLabelValue('FORM', $match[2]);
455
+				    if (preg_match('/\n3 TYPE (.+)/', $fact->getGedcom(), $type_match)) {
456
+					    echo GedcomTag::getLabelValue('TYPE', GedcomTag::getFileFormTypeValue($type_match[1]));
457
+				    }
458
+				    break;
459
+			    case 'URL':
460
+			    case '_URL':
461
+			    case 'WWW':
462
+				    $link = '<a href="' . Filter::escapeHtml($match[2]) . '">' . Filter::escapeHtml($match[2]) . '</a>';
463
+				    echo GedcomTag::getLabelValue($fact->getTag() . ':' . $match[1], $link);
464
+				    break;
465
+			    default:
466
+				    if ($fact->getParent()->getTree()->getPreference('HIDE_GEDCOM_ERRORS') === '1' || GedcomTag::isTag($match[1])) {
467
+					    if (preg_match('/^@(' . WT_REGEX_XREF . ')@$/', $match[2], $xmatch)) {
468
+						    // Links
469
+						    $linked_record = GedcomRecord::getInstance($xmatch[1], $fact->getParent()->getTree());
470
+						    if ($linked_record) {
471
+							    $link = '<a href="' . $linked_record->getHtmlUrl() . '">' . $linked_record->getFullName() . '</a>';
472
+							    echo GedcomTag::getLabelValue($fact->getTag() . ':' . $match[1], $link);
473
+						    } else {
474
+							    echo GedcomTag::getLabelValue($fact->getTag() . ':' . $match[1], Filter::escapeHtml($match[2]));
475
+						    }
476
+					    } else {
477
+						    // Non links
478
+						    echo GedcomTag::getLabelValue($fact->getTag() . ':' . $match[1], Filter::escapeHtml($match[2]));
479
+					    }
480
+				    }
481
+				    break;
482 482
 			}
483 483
 		}
484 484
 		echo self::printFactSources($fact->getGedcom(), 2);
@@ -824,23 +824,23 @@  discard block
 block discarded – undo
824 824
 						foreach ($rmatches[1] as $rmatch) {
825 825
 							echo '<br><span class="label">', GedcomTag::getLabel('RESN'), ':</span> <span class="field">';
826 826
 							switch ($rmatch) {
827
-							case 'none':
828
-								// Note: "2 RESN none" is not valid gedcom, and the GUI will not let you add it.
829
-								// However, webtrees privacy rules will interpret it as "show an otherwise private fact to public".
830
-								echo '<i class="icon-resn-none"></i> ', I18N::translate('Show to visitors');
831
-								break;
832
-							case 'privacy':
833
-								echo '<i class="icon-resn-privacy"></i> ', I18N::translate('Show to members');
834
-								break;
835
-							case 'confidential':
836
-								echo '<i class="icon-resn-confidential"></i> ', I18N::translate('Show to managers');
837
-								break;
838
-							case 'locked':
839
-								echo '<i class="icon-resn-locked"></i> ', I18N::translate('Only managers can edit');
840
-								break;
841
-							default:
842
-								echo $rmatch;
843
-								break;
827
+							    case 'none':
828
+								    // Note: "2 RESN none" is not valid gedcom, and the GUI will not let you add it.
829
+								    // However, webtrees privacy rules will interpret it as "show an otherwise private fact to public".
830
+								    echo '<i class="icon-resn-none"></i> ', I18N::translate('Show to visitors');
831
+								    break;
832
+							    case 'privacy':
833
+								    echo '<i class="icon-resn-privacy"></i> ', I18N::translate('Show to members');
834
+								    break;
835
+							    case 'confidential':
836
+								    echo '<i class="icon-resn-confidential"></i> ', I18N::translate('Show to managers');
837
+								    break;
838
+							    case 'locked':
839
+								    echo '<i class="icon-resn-locked"></i> ', I18N::translate('Only managers can edit');
840
+								    break;
841
+							    default:
842
+								    echo $rmatch;
843
+								    break;
844 844
 							}
845 845
 							echo '</span>';
846 846
 						}
@@ -1090,23 +1090,23 @@  discard block
 block discarded – undo
1090 1090
 				foreach ($matches[1] as $match) {
1091 1091
 					echo '<br><span class="label">', GedcomTag::getLabel('RESN'), ':</span> <span class="field">';
1092 1092
 					switch ($match) {
1093
-					case 'none':
1094
-						// Note: "2 RESN none" is not valid gedcom, and the GUI will not let you add it.
1095
-						// However, webtrees privacy rules will interpret it as "show an otherwise private fact to public".
1096
-						echo '<i class="icon-resn-none"></i> ', I18N::translate('Show to visitors');
1097
-						break;
1098
-					case 'privacy':
1099
-						echo '<i class="icon-resn-privacy"></i> ', I18N::translate('Show to members');
1100
-						break;
1101
-					case 'confidential':
1102
-						echo '<i class="icon-resn-confidential"></i> ', I18N::translate('Show to managers');
1103
-						break;
1104
-					case 'locked':
1105
-						echo '<i class="icon-resn-locked"></i> ', I18N::translate('Only managers can edit');
1106
-						break;
1107
-					default:
1108
-						echo $match;
1109
-						break;
1093
+					    case 'none':
1094
+						    // Note: "2 RESN none" is not valid gedcom, and the GUI will not let you add it.
1095
+						    // However, webtrees privacy rules will interpret it as "show an otherwise private fact to public".
1096
+						    echo '<i class="icon-resn-none"></i> ', I18N::translate('Show to visitors');
1097
+						    break;
1098
+					    case 'privacy':
1099
+						    echo '<i class="icon-resn-privacy"></i> ', I18N::translate('Show to members');
1100
+						    break;
1101
+					    case 'confidential':
1102
+						    echo '<i class="icon-resn-confidential"></i> ', I18N::translate('Show to managers');
1103
+						    break;
1104
+					    case 'locked':
1105
+						    echo '<i class="icon-resn-locked"></i> ', I18N::translate('Only managers can edit');
1106
+						    break;
1107
+					    default:
1108
+						    echo $match;
1109
+						    break;
1110 1110
 					}
1111 1111
 					echo '</span>';
1112 1112
 				}
@@ -1207,12 +1207,12 @@  discard block
 block discarded – undo
1207 1207
 					}
1208 1208
 
1209 1209
 					switch ($media->isPrimary()) {
1210
-					case 'Y':
1211
-						echo GedcomTag::getLabelValue('_PRIM', I18N::translate('yes'));
1212
-						break;
1213
-					case 'N':
1214
-						echo GedcomTag::getLabelValue('_PRIM', I18N::translate('no'));
1215
-						break;
1210
+					    case 'Y':
1211
+						    echo GedcomTag::getLabelValue('_PRIM', I18N::translate('yes'));
1212
+						    break;
1213
+					    case 'N':
1214
+						    echo GedcomTag::getLabelValue('_PRIM', I18N::translate('no'));
1215
+						    break;
1216 1216
 					}
1217 1217
 					echo FunctionsPrint::printFactNotes($media->getGedcom(), 1);
1218 1218
 					echo self::printFactSources($media->getGedcom(), 1);
Please login to merge, or discard this patch.
Braces   +22 added lines, -11 removed lines patch added patch discarded remove patch
@@ -40,7 +40,8 @@  discard block
 block discarded – undo
40 40
 /**
41 41
  * Class FunctionsPrintFacts - common functions
42 42
  */
43
-class FunctionsPrintFacts {
43
+class FunctionsPrintFacts
44
+{
44 45
 	/**
45 46
 	 * Print a fact record, for the individual/family/source/repository/etc. pages.
46 47
 	 *
@@ -53,7 +54,8 @@  discard block
 block discarded – undo
53 54
 	 * @param Fact $fact
54 55
 	 * @param GedcomRecord $record
55 56
 	 */
56
-	public static function printFact(Fact $fact, GedcomRecord $record) {
57
+	public static function printFact(Fact $fact, GedcomRecord $record)
58
+	{
57 59
 		static $n_chil = 0, $n_gchi = 0;
58 60
 
59 61
 		$parent = $fact->getParent();
@@ -494,7 +496,8 @@  discard block
 block discarded – undo
494 496
 	 *
495 497
 	 * @return string
496 498
 	 */
497
-	private static function formatAssociateRelationship(Fact $event) {
499
+	private static function formatAssociateRelationship(Fact $event)
500
+	{
498 501
 		$parent = $event->getParent();
499 502
 		// To whom is this record an assocate?
500 503
 		if ($parent instanceof Individual) {
@@ -561,7 +564,8 @@  discard block
 block discarded – undo
561 564
 	 *
562 565
 	 * @param string $xref the Gedcom Xref ID of the repository to print
563 566
 	 */
564
-	public static function printRepositoryRecord($xref) {
567
+	public static function printRepositoryRecord($xref)
568
+	{
565 569
 		global $WT_TREE;
566 570
 
567 571
 		$repository = Repository::getInstance($xref, $WT_TREE);
@@ -583,7 +587,8 @@  discard block
 block discarded – undo
583 587
 	 *
584 588
 	 * @return string HTML text
585 589
 	 */
586
-	public static function printFactSources($factrec, $level) {
590
+	public static function printFactSources($factrec, $level)
591
+	{
587 592
 		global $WT_TREE;
588 593
 
589 594
 		$data   = '';
@@ -666,7 +671,8 @@  discard block
 block discarded – undo
666 671
 	 * @param string $factrec
667 672
 	 * @param int $level
668 673
 	 */
669
-	public static function printMediaLinks($factrec, $level) {
674
+	public static function printMediaLinks($factrec, $level)
675
+	{
670 676
 		global $WT_TREE;
671 677
 
672 678
 		$nlevel = $level + 1;
@@ -735,7 +741,8 @@  discard block
 block discarded – undo
735 741
 	 * @param Fact $fact
736 742
 	 * @param int $level
737 743
 	 */
738
-	public static function printMainSources(Fact $fact, $level) {
744
+	public static function printMainSources(Fact $fact, $level)
745
+	{
739 746
 		$factrec = $fact->getGedcom();
740 747
 		$fact_id = $fact->getFactId();
741 748
 		$parent  = $fact->getParent();
@@ -881,7 +888,8 @@  discard block
 block discarded – undo
881 888
 	 *
882 889
 	 * @return string
883 890
 	 */
884
-	public static function printSourceStructure($textSOUR) {
891
+	public static function printSourceStructure($textSOUR)
892
+	{
885 893
 		global $WT_TREE;
886 894
 		$html = '';
887 895
 
@@ -928,7 +936,8 @@  discard block
 block discarded – undo
928 936
 	 *
929 937
 	 * @return string[]
930 938
 	 */
931
-	public static function getSourceStructure($srec) {
939
+	public static function getSourceStructure($srec)
940
+	{
932 941
 		// Set up the output array
933 942
 		$textSOUR = array(
934 943
 			'PAGE' => '',
@@ -974,7 +983,8 @@  discard block
 block discarded – undo
974 983
 	 * @param Fact $fact
975 984
 	 * @param int $level
976 985
 	 */
977
-	public static function printMainNotes(Fact $fact, $level) {
986
+	public static function printMainNotes(Fact $fact, $level)
987
+	{
978 988
 		$factrec = $fact->getGedcom();
979 989
 		$fact_id = $fact->getFactId();
980 990
 		$parent  = $fact->getParent();
@@ -1121,7 +1131,8 @@  discard block
 block discarded – undo
1121 1131
 	 * @param Fact $fact
1122 1132
 	 * @param int $level
1123 1133
 	 */
1124
-	public static function printMainMedia(Fact $fact, $level) {
1134
+	public static function printMainMedia(Fact $fact, $level)
1135
+	{
1125 1136
 		$factrec = $fact->getGedcom();
1126 1137
 		$parent  = $fact->getParent();
1127 1138
 
Please login to merge, or discard this patch.
app/Functions/FunctionsMedia.php 3 patches
Indentation   +68 added lines, -68 removed lines patch added patch discarded remove patch
@@ -21,77 +21,77 @@
 block discarded – undo
21 21
  * Class FunctionsMedia - common functions
22 22
  */
23 23
 class FunctionsMedia {
24
-	/**
25
-	 * Convert raw values from php.ini file into bytes
26
-	 *
27
-	 * @param string $val
28
-	 *
29
-	 * @return int
30
-	 */
31
-	public static function sizeToBytes($val) {
32
-		if (!$val) {
33
-			// no value was passed in, assume no limit and return -1
34
-			$val = -1;
35
-		}
36
-		switch (substr($val, -1)) {
37
-		case 'g':
38
-		case 'G':
39
-			return (int) $val * 1024 * 1024 * 1024;
40
-		case 'm':
41
-		case 'M':
42
-			return (int) $val * 1024 * 1024;
43
-		case 'k':
44
-		case 'K':
45
-			return (int) $val * 1024;
46
-		default:
47
-			return (int) $val;
48
-		}
49
-	}
24
+    /**
25
+     * Convert raw values from php.ini file into bytes
26
+     *
27
+     * @param string $val
28
+     *
29
+     * @return int
30
+     */
31
+    public static function sizeToBytes($val) {
32
+        if (!$val) {
33
+            // no value was passed in, assume no limit and return -1
34
+            $val = -1;
35
+        }
36
+        switch (substr($val, -1)) {
37
+        case 'g':
38
+        case 'G':
39
+            return (int) $val * 1024 * 1024 * 1024;
40
+        case 'm':
41
+        case 'M':
42
+            return (int) $val * 1024 * 1024;
43
+        case 'k':
44
+        case 'K':
45
+            return (int) $val * 1024;
46
+        default:
47
+            return (int) $val;
48
+        }
49
+    }
50 50
 
51
-	/**
52
-	 * Determine whether there is enough memory to load a particular image.
53
-	 *
54
-	 * @param string $serverFilename
55
-	 *
56
-	 * @return bool
57
-	 */
58
-	public static function hasMemoryForImage($serverFilename) {
59
-		// find out how much total memory this script can access
60
-		$memoryAvailable = self::sizeToBytes(ini_get('memory_limit'));
61
-		// if memory is unlimited, it will return -1 and we don’t need to worry about it
62
-		if ($memoryAvailable == -1) {
63
-			return true;
64
-		}
51
+    /**
52
+     * Determine whether there is enough memory to load a particular image.
53
+     *
54
+     * @param string $serverFilename
55
+     *
56
+     * @return bool
57
+     */
58
+    public static function hasMemoryForImage($serverFilename) {
59
+        // find out how much total memory this script can access
60
+        $memoryAvailable = self::sizeToBytes(ini_get('memory_limit'));
61
+        // if memory is unlimited, it will return -1 and we don’t need to worry about it
62
+        if ($memoryAvailable == -1) {
63
+            return true;
64
+        }
65 65
 
66
-		// find out how much memory we are already using
67
-		$memoryUsed = memory_get_usage();
66
+        // find out how much memory we are already using
67
+        $memoryUsed = memory_get_usage();
68 68
 
69
-		try {
70
-			$imgsize = getimagesize($serverFilename);
71
-		} catch (\ErrorException $ex) {
72
-			// Not an image, or not a valid image?
73
-			$imgsize = false;
74
-		}
69
+        try {
70
+            $imgsize = getimagesize($serverFilename);
71
+        } catch (\ErrorException $ex) {
72
+            // Not an image, or not a valid image?
73
+            $imgsize = false;
74
+        }
75 75
 
76
-		// find out how much memory this image needs for processing, probably only works for jpegs
77
-		// from comments on http://www.php.net/imagecreatefromjpeg
78
-		if ($imgsize && isset($imgsize['bits']) && (isset($imgsize['channels']))) {
79
-			$memoryNeeded = round(($imgsize[0] * $imgsize[1] * $imgsize['bits'] * $imgsize['channels'] / 8 + pow(2, 16)) * 1.65);
80
-			$memorySpare  = $memoryAvailable - $memoryUsed - $memoryNeeded;
81
-			if ($memorySpare > 0) {
82
-				// we have enough memory to load this file
83
-				return true;
84
-			} else {
85
-				// not enough memory to load this file
86
-				$image_info = sprintf('%.2fKB, %d × %d %d bits %d channels', filesize($serverFilename) / 1024, $imgsize[0], $imgsize[1], $imgsize['bits'], $imgsize['channels']);
87
-				Log::addMediaLog('Cannot create thumbnail ' . $serverFilename . ' (' . $image_info . ') memory avail: ' . $memoryAvailable . ' used: ' . $memoryUsed . ' needed: ' . $memoryNeeded . ' spare: ' . $memorySpare);
76
+        // find out how much memory this image needs for processing, probably only works for jpegs
77
+        // from comments on http://www.php.net/imagecreatefromjpeg
78
+        if ($imgsize && isset($imgsize['bits']) && (isset($imgsize['channels']))) {
79
+            $memoryNeeded = round(($imgsize[0] * $imgsize[1] * $imgsize['bits'] * $imgsize['channels'] / 8 + pow(2, 16)) * 1.65);
80
+            $memorySpare  = $memoryAvailable - $memoryUsed - $memoryNeeded;
81
+            if ($memorySpare > 0) {
82
+                // we have enough memory to load this file
83
+                return true;
84
+            } else {
85
+                // not enough memory to load this file
86
+                $image_info = sprintf('%.2fKB, %d × %d %d bits %d channels', filesize($serverFilename) / 1024, $imgsize[0], $imgsize[1], $imgsize['bits'], $imgsize['channels']);
87
+                Log::addMediaLog('Cannot create thumbnail ' . $serverFilename . ' (' . $image_info . ') memory avail: ' . $memoryAvailable . ' used: ' . $memoryUsed . ' needed: ' . $memoryNeeded . ' spare: ' . $memorySpare);
88 88
 
89
-				return false;
90
-			}
91
-		} else {
92
-			// assume there is enough memory
93
-			// TODO find out how to check memory needs for gif and png
94
-			return true;
95
-		}
96
-	}
89
+                return false;
90
+            }
91
+        } else {
92
+            // assume there is enough memory
93
+            // TODO find out how to check memory needs for gif and png
94
+            return true;
95
+        }
96
+    }
97 97
 }
Please login to merge, or discard this patch.
Switch Indentation   +11 added lines, -11 removed lines patch added patch discarded remove patch
@@ -34,17 +34,17 @@
 block discarded – undo
34 34
 			$val = -1;
35 35
 		}
36 36
 		switch (substr($val, -1)) {
37
-		case 'g':
38
-		case 'G':
39
-			return (int) $val * 1024 * 1024 * 1024;
40
-		case 'm':
41
-		case 'M':
42
-			return (int) $val * 1024 * 1024;
43
-		case 'k':
44
-		case 'K':
45
-			return (int) $val * 1024;
46
-		default:
47
-			return (int) $val;
37
+		    case 'g':
38
+		    case 'G':
39
+			    return (int) $val * 1024 * 1024 * 1024;
40
+		    case 'm':
41
+		    case 'M':
42
+			    return (int) $val * 1024 * 1024;
43
+		    case 'k':
44
+		    case 'K':
45
+			    return (int) $val * 1024;
46
+		    default:
47
+			    return (int) $val;
48 48
 		}
49 49
 	}
50 50
 
Please login to merge, or discard this patch.
Braces   +6 added lines, -3 removed lines patch added patch discarded remove patch
@@ -20,7 +20,8 @@  discard block
 block discarded – undo
20 20
 /**
21 21
  * Class FunctionsMedia - common functions
22 22
  */
23
-class FunctionsMedia {
23
+class FunctionsMedia
24
+{
24 25
 	/**
25 26
 	 * Convert raw values from php.ini file into bytes
26 27
 	 *
@@ -28,7 +29,8 @@  discard block
 block discarded – undo
28 29
 	 *
29 30
 	 * @return int
30 31
 	 */
31
-	public static function sizeToBytes($val) {
32
+	public static function sizeToBytes($val)
33
+	{
32 34
 		if (!$val) {
33 35
 			// no value was passed in, assume no limit and return -1
34 36
 			$val = -1;
@@ -55,7 +57,8 @@  discard block
 block discarded – undo
55 57
 	 *
56 58
 	 * @return bool
57 59
 	 */
58
-	public static function hasMemoryForImage($serverFilename) {
60
+	public static function hasMemoryForImage($serverFilename)
61
+	{
59 62
 		// find out how much total memory this script can access
60 63
 		$memoryAvailable = self::sizeToBytes(ini_get('memory_limit'));
61 64
 		// if memory is unlimited, it will return -1 and we don’t need to worry about it
Please login to merge, or discard this patch.
app/Functions/FunctionsRtl.php 3 patches
Indentation   +1135 added lines, -1135 removed lines patch added patch discarded remove patch
@@ -21,1140 +21,1140 @@
 block discarded – undo
21 21
  * RTL Functions for use in the PDF/HTML reports
22 22
  */
23 23
 class FunctionsRtl {
24
-	const OPEN_PARENTHESES = '([{';
24
+    const OPEN_PARENTHESES = '([{';
25 25
 
26
-	const CLOSE_PARENTHESES = ')]}';
27
-
28
-	const NUMBERS = '0123456789';
29
-
30
-	const NUMBER_PREFIX = '+-'; // Treat these like numbers when at beginning or end of numeric strings
31
-
32
-	const NUMBER_PUNCTUATION = '- ,.:/'; // Treat these like numbers when inside numeric strings
33
-
34
-	const PUNCTUATION = ',.:;?!';
35
-
36
-	/** @var string Were we previously processing LTR or RTL. */
37
-	private static $previousState;
38
-
39
-	/** @var string Are we currently processing LTR or RTL. */
40
-	private static $currentState;
41
-
42
-	/** @var string Text waiting to be processed. */
43
-	private static $waitingText;
44
-
45
-	/** @var string LTR text. */
46
-	private static $startLTR;
47
-
48
-	/** @var string LTR text. */
49
-	private static $endLTR;
50
-
51
-	/** @var string RTL text. */
52
-	private static $startRTL;
53
-
54
-	/** @var string RTL text. */
55
-	private static $endRTL;
56
-
57
-	/** @var int Offset into the text. */
58
-	private static $lenStart;
59
-
60
-	/** @var int Offset into the text. */
61
-	private static $lenEnd;
62
-
63
-	/** @var int Offset into the text. */
64
-	private static $posSpanStart;
65
-
66
-	/**
67
-	 * This function strips &lrm; and &rlm; from the input string. It should be used for all
68
-	 * text that has been passed through the PrintReady() function before that text is stored
69
-	 * in the database. The database should NEVER contain these characters.
70
-	 *
71
-	 * @param  string $inputText The string from which the &lrm; and &rlm; characters should be stripped
72
-	 *
73
-	 * @return string The input string, with &lrm; and &rlm; stripped
74
-	 */
75
-	public static function stripLrmRlm($inputText) {
76
-		return str_replace(array(WT_UTF8_LRM, WT_UTF8_RLM, WT_UTF8_LRO, WT_UTF8_RLO, WT_UTF8_LRE, WT_UTF8_RLE, WT_UTF8_PDF, "&lrm;", "&rlm;", "&LRM;", "&RLM;"), "", $inputText);
77
-	}
78
-
79
-	/**
80
-	 * This function encapsulates all texts in the input with <span dir='xxx'> and </span>
81
-	 * according to the directionality specified.
82
-	 *
83
-	 * @param string $inputText Raw input
84
-	 * @param string $direction Directionality (LTR, BOTH, RTL) default BOTH
85
-	 * @param string $class Additional text to insert into output <span dir="xxx"> (such as 'class="yyy"')
86
-	 *
87
-	 * @return string The string with all texts encapsulated as required
88
-	 */
89
-	public static function spanLtrRtl($inputText, $direction = 'BOTH', $class = '') {
90
-		if ($inputText == '') {
91
-			// Nothing to do
92
-			return '';
93
-		}
94
-
95
-		$workingText = str_replace("\n", '<br>', $inputText);
96
-		$workingText = str_replace(array('<span class="starredname"><br>', '<span<br>class="starredname">'), '<br><span class="starredname">', $workingText); // Reposition some incorrectly placed line breaks
97
-		$workingText = self::stripLrmRlm($workingText); // Get rid of any existing UTF8 control codes
98
-
99
-		// $nothing  = '&zwnj;'; // Zero Width Non-Joiner  (not sure whether this is still needed to work around a TCPDF bug)
100
-		$nothing = '';
101
-
102
-		self::$startLTR = '<LTR>'; // This will become '<span dir="ltr">' at the end
103
-		self::$endLTR   = '</LTR>'; // This will become '</span>' at the end
104
-		self::$startRTL = '<RTL>'; // This will become '<span dir="rtl">' at the end
105
-		self::$endRTL   = '</RTL>'; // This will become '</span>' at the end
106
-		self::$lenStart = strlen(self::$startLTR); // RTL version MUST have same length
107
-		self::$lenEnd   = strlen(self::$endLTR); // RTL version MUST have same length
108
-
109
-		self::$previousState = '';
110
-		self::$currentState  = strtoupper(I18N::direction());
111
-		$numberState         = false; // Set when we're inside a numeric string
112
-		$result              = '';
113
-		self::$waitingText   = '';
114
-		$openParDirection    = array();
115
-
116
-		self::beginCurrentSpan($result);
117
-
118
-		while ($workingText != '') {
119
-			$charArray     = self::getChar($workingText, 0); // Get the next ASCII or UTF-8 character
120
-			$currentLetter = $charArray['letter'];
121
-			$currentLen    = $charArray['length'];
122
-
123
-			$openParIndex  = strpos(self::OPEN_PARENTHESES, $currentLetter); // Which opening parenthesis is this?
124
-			$closeParIndex = strpos(self::CLOSE_PARENTHESES, $currentLetter); // Which closing parenthesis is this?
125
-
126
-			switch ($currentLetter) {
127
-			case '<':
128
-				// Assume this '<' starts an HTML element
129
-				$endPos = strpos($workingText, '>'); // look for the terminating '>'
130
-				if ($endPos === false) {
131
-					$endPos = 0;
132
-				}
133
-				$currentLen += $endPos;
134
-				$element = substr($workingText, 0, $currentLen);
135
-				$temp    = strtolower(substr($element, 0, 3));
136
-				if (strlen($element) < 7 && $temp == '<br') {
137
-					if ($numberState) {
138
-						$numberState = false;
139
-						if (self::$currentState == 'RTL') {
140
-							self::$waitingText .= WT_UTF8_PDF;
141
-						}
142
-					}
143
-					self::breakCurrentSpan($result);
144
-				} elseif (self::$waitingText == '') {
145
-					$result .= $element;
146
-				} else {
147
-					self::$waitingText .= $element;
148
-				}
149
-				$workingText = substr($workingText, $currentLen);
150
-				break;
151
-			case '&':
152
-				// Assume this '&' starts an HTML entity
153
-				$endPos = strpos($workingText, ';'); // look for the terminating ';'
154
-				if ($endPos === false) {
155
-					$endPos = 0;
156
-				}
157
-				$currentLen += $endPos;
158
-				$entity = substr($workingText, 0, $currentLen);
159
-				if (strtolower($entity) == '&nbsp;') {
160
-					$entity .= '&nbsp;'; // Ensure consistent case for this entity
161
-				}
162
-				if (self::$waitingText == '') {
163
-					$result .= $entity;
164
-				} else {
165
-					self::$waitingText .= $entity;
166
-				}
167
-				$workingText = substr($workingText, $currentLen);
168
-				break;
169
-			case '{':
170
-				if (substr($workingText, 1, 1) == '{') {
171
-					// Assume this '{{' starts a TCPDF directive
172
-					$endPos = strpos($workingText, '}}'); // look for the terminating '}}'
173
-					if ($endPos === false) {
174
-						$endPos = 0;
175
-					}
176
-					$currentLen        = $endPos + 2;
177
-					$directive         = substr($workingText, 0, $currentLen);
178
-					$workingText       = substr($workingText, $currentLen);
179
-					$result            = $result . self::$waitingText . $directive;
180
-					self::$waitingText = '';
181
-					break;
182
-				}
183
-			default:
184
-				// Look for strings of numbers with optional leading or trailing + or -
185
-				// and with optional embedded numeric punctuation
186
-				if ($numberState) {
187
-					// If we're inside a numeric string, look for reasons to end it
188
-					$offset    = 0; // Be sure to look at the current character first
189
-					$charArray = self::getChar($workingText . "\n", $offset);
190
-					if (strpos(self::NUMBERS, $charArray['letter']) === false) {
191
-						// This is not a digit. Is it numeric punctuation?
192
-						if (substr($workingText . "\n", $offset, 6) == '&nbsp;') {
193
-							$offset += 6; // This could be numeric punctuation
194
-						} elseif (strpos(self::NUMBER_PUNCTUATION, $charArray['letter']) !== false) {
195
-							$offset += $charArray['length']; // This could be numeric punctuation
196
-						}
197
-						// If the next character is a digit, the current character is numeric punctuation
198
-						$charArray = self::getChar($workingText . "\n", $offset);
199
-						if (strpos(self::NUMBERS, $charArray['letter']) === false) {
200
-							// This is not a digit. End the run of digits and punctuation.
201
-							$numberState = false;
202
-							if (self::$currentState == 'RTL') {
203
-								if (strpos(self::NUMBER_PREFIX, $currentLetter) === false) {
204
-									$currentLetter = WT_UTF8_PDF . $currentLetter;
205
-								} else {
206
-									$currentLetter = $currentLetter . WT_UTF8_PDF; // Include a trailing + or - in the run
207
-								}
208
-							}
209
-						}
210
-					}
211
-				} else {
212
-					// If we're outside a numeric string, look for reasons to start it
213
-					if (strpos(self::NUMBER_PREFIX, $currentLetter) !== false) {
214
-						// This might be a number lead-in
215
-						$offset   = $currentLen;
216
-						$nextChar = substr($workingText . "\n", $offset, 1);
217
-						if (strpos(self::NUMBERS, $nextChar) !== false) {
218
-							$numberState = true; // We found a digit: the lead-in is therefore numeric
219
-							if (self::$currentState == 'RTL') {
220
-								$currentLetter = WT_UTF8_LRE . $currentLetter;
221
-							}
222
-						}
223
-					} elseif (strpos(self::NUMBERS, $currentLetter) !== false) {
224
-						$numberState = true; // The current letter is a digit
225
-						if (self::$currentState == 'RTL') {
226
-							$currentLetter = WT_UTF8_LRE . $currentLetter;
227
-						}
228
-					}
229
-				}
230
-
231
-				// Determine the directionality of the current UTF-8 character
232
-				$newState = self::$currentState;
233
-				while (true) {
234
-					if (I18N::scriptDirection(I18N::textScript($currentLetter)) === 'rtl') {
235
-						if (self::$currentState == '') {
236
-							$newState = 'RTL';
237
-							break;
238
-						}
239
-
240
-						if (self::$currentState == 'RTL') {
241
-							break;
242
-						}
243
-						// Switch to RTL only if this isn't a solitary RTL letter
244
-						$tempText = substr($workingText, $currentLen);
245
-						while ($tempText != '') {
246
-							$nextCharArray = self::getChar($tempText, 0);
247
-							$nextLetter    = $nextCharArray['letter'];
248
-							$nextLen       = $nextCharArray['length'];
249
-							$tempText      = substr($tempText, $nextLen);
250
-
251
-							if (I18N::scriptDirection(I18N::textScript($nextLetter)) === 'rtl') {
252
-								$newState = 'RTL';
253
-								break 2;
254
-							}
255
-
256
-							if (strpos(self::PUNCTUATION, $nextLetter) !== false || strpos(self::OPEN_PARENTHESES, $nextLetter) !== false) {
257
-								$newState = 'RTL';
258
-								break 2;
259
-							}
260
-
261
-							if ($nextLetter === ' ') {
262
-								break;
263
-							}
264
-							$nextLetter .= substr($tempText . "\n", 0, 5);
265
-							if ($nextLetter === '&nbsp;') {
266
-								break;
267
-							}
268
-						}
269
-						// This is a solitary RTL letter : wrap it in UTF8 control codes to force LTR directionality
270
-						$currentLetter = WT_UTF8_LRO . $currentLetter . WT_UTF8_PDF;
271
-						$newState      = 'LTR';
272
-						break;
273
-					}
274
-					if (($currentLen != 1) || ($currentLetter >= 'A' && $currentLetter <= 'Z') || ($currentLetter >= 'a' && $currentLetter <= 'z')) {
275
-						// Since it’s neither Hebrew nor Arabic, this UTF-8 character or ASCII letter must be LTR
276
-						$newState = 'LTR';
277
-						break;
278
-					}
279
-					if ($closeParIndex !== false) {
280
-						// This closing parenthesis has to inherit the matching opening parenthesis' directionality
281
-						if (!empty($openParDirection[$closeParIndex]) && $openParDirection[$closeParIndex] != '?') {
282
-							$newState = $openParDirection[$closeParIndex];
283
-						}
284
-						$openParDirection[$closeParIndex] = '';
285
-						break;
286
-					}
287
-					if ($openParIndex !== false) {
288
-						// Opening parentheses always inherit the following directionality
289
-						self::$waitingText .= $currentLetter;
290
-						$workingText = substr($workingText, $currentLen);
291
-						while (true) {
292
-							if ($workingText === '') {
293
-								break;
294
-							}
295
-							if (substr($workingText, 0, 1) === ' ') {
296
-								// Spaces following this left parenthesis inherit the following directionality too
297
-								self::$waitingText .= ' ';
298
-								$workingText = substr($workingText, 1);
299
-								continue;
300
-							}
301
-							if (substr($workingText, 0, 6) === '&nbsp;') {
302
-								// Spaces following this left parenthesis inherit the following directionality too
303
-								self::$waitingText .= '&nbsp;';
304
-								$workingText = substr($workingText, 6);
305
-								continue;
306
-							}
307
-							break;
308
-						}
309
-						$openParDirection[$openParIndex] = '?';
310
-						break 2; // double break because we're waiting for more information
311
-					}
312
-
313
-					// We have a digit or a "normal" special character.
314
-					//
315
-					// When this character is not at the start of the input string, it inherits the preceding directionality;
316
-					// at the start of the input string, it assumes the following directionality.
317
-					//
318
-					// Exceptions to this rule will be handled later during final clean-up.
319
-					//
320
-					self::$waitingText .= $currentLetter;
321
-					$workingText = substr($workingText, $currentLen);
322
-					if (self::$currentState != '') {
323
-						$result .= self::$waitingText;
324
-						self::$waitingText = '';
325
-					}
326
-					break 2; // double break because we're waiting for more information
327
-				}
328
-				if ($newState != self::$currentState) {
329
-					// A direction change has occurred
330
-					self::finishCurrentSpan($result, false);
331
-					self::$previousState = self::$currentState;
332
-					self::$currentState  = $newState;
333
-					self::beginCurrentSpan($result);
334
-				}
335
-				self::$waitingText .= $currentLetter;
336
-				$workingText = substr($workingText, $currentLen);
337
-				$result .= self::$waitingText;
338
-				self::$waitingText = '';
339
-
340
-				foreach ($openParDirection as $index => $value) {
341
-					// Since we now know the proper direction, remember it for all waiting opening parentheses
342
-					if ($value === '?') {
343
-						$openParDirection[$index] = self::$currentState;
344
-					}
345
-				}
346
-
347
-				break;
348
-			}
349
-		}
350
-
351
-		// We're done. Finish last <span> if necessary
352
-		if ($numberState) {
353
-			if (self::$waitingText === '') {
354
-				if (self::$currentState === 'RTL') {
355
-					$result .= WT_UTF8_PDF;
356
-				}
357
-			} else {
358
-				if (self::$currentState === 'RTL') {
359
-					self::$waitingText .= WT_UTF8_PDF;
360
-				}
361
-			}
362
-		}
363
-		self::finishCurrentSpan($result, true);
364
-
365
-		// Get rid of any waiting text
366
-		if (self::$waitingText != '') {
367
-			if (I18N::direction() === 'rtl' && self::$currentState === 'LTR') {
368
-				$result .= self::$startRTL;
369
-				$result .= self::$waitingText;
370
-				$result .= self::$endRTL;
371
-			} else {
372
-				$result .= self::$startLTR;
373
-				$result .= self::$waitingText;
374
-				$result .= self::$endLTR;
375
-			}
376
-			self::$waitingText = '';
377
-		}
378
-
379
-		// Lastly, do some more cleanups
380
-
381
-		// Move leading RTL numeric strings to following LTR text
382
-		// (this happens when the page direction is RTL and the original text begins with a number and is followed by LTR text)
383
-		while (substr($result, 0, self::$lenStart + 3) === self::$startRTL . WT_UTF8_LRE) {
384
-			$spanEnd = strpos($result, self::$endRTL . self::$startLTR);
385
-			if ($spanEnd === false) {
386
-				break;
387
-			}
388
-			$textSpan = self::stripLrmRlm(substr($result, self::$lenStart + 3, $spanEnd - self::$lenStart - 3));
389
-			if (I18N::scriptDirection(I18N::textScript($textSpan)) === 'rtl') {
390
-				break;
391
-			}
392
-			$result = self::$startLTR . substr($result, self::$lenStart, $spanEnd - self::$lenStart) . substr($result, $spanEnd + self::$lenStart + self::$lenEnd);
393
-			break;
394
-		}
395
-
396
-		// On RTL pages, put trailing "." in RTL numeric strings into its own RTL span
397
-		if (I18N::direction() === 'rtl') {
398
-			$result = str_replace(WT_UTF8_PDF . '.' . self::$endRTL, WT_UTF8_PDF . self::$endRTL . self::$startRTL . '.' . self::$endRTL, $result);
399
-		}
400
-
401
-		// Trim trailing blanks preceding <br> in LTR text
402
-		while (self::$previousState != 'RTL') {
403
-			if (strpos($result, ' <LTRbr>') !== false) {
404
-				$result = str_replace(' <LTRbr>', '<LTRbr>', $result);
405
-				continue;
406
-			}
407
-			if (strpos($result, '&nbsp;<LTRbr>') !== false) {
408
-				$result = str_replace('&nbsp;<LTRbr>', '<LTRbr>', $result);
409
-				continue;
410
-			}
411
-			if (strpos($result, ' <br>') !== false) {
412
-				$result = str_replace(' <br>', '<br>', $result);
413
-				continue;
414
-			}
415
-			if (strpos($result, '&nbsp;<br>') !== false) {
416
-				$result = str_replace('&nbsp;<br>', '<br>', $result);
417
-				continue;
418
-			}
419
-			break; // Neither space nor &nbsp; : we're done
420
-		}
421
-
422
-		// Trim trailing blanks preceding <br> in RTL text
423
-		while (true) {
424
-			if (strpos($result, ' <RTLbr>') !== false) {
425
-				$result = str_replace(' <RTLbr>', '<RTLbr>', $result);
426
-				continue;
427
-			}
428
-			if (strpos($result, '&nbsp;<RTLbr>') !== false) {
429
-				$result = str_replace('&nbsp;<RTLbr>', '<RTLbr>', $result);
430
-				continue;
431
-			}
432
-			break; // Neither space nor &nbsp; : we're done
433
-		}
434
-
435
-		// Convert '<LTRbr>' and '<RTLbr /'
436
-		$result = str_replace(array('<LTRbr>', '<RTLbr>'), array(self::$endLTR . '<br>' . self::$startLTR, self::$endRTL . '<br>' . self::$startRTL), $result);
437
-
438
-		// Include leading indeterminate directional text in whatever follows
439
-		if (substr($result . "\n", 0, self::$lenStart) != self::$startLTR && substr($result . "\n", 0, self::$lenStart) != self::$startRTL && substr($result . "\n", 0, 6) != '<br>') {
440
-			$leadingText = '';
441
-			while (true) {
442
-				if ($result == '') {
443
-					$result = $leadingText;
444
-					break;
445
-				}
446
-				if (substr($result . "\n", 0, self::$lenStart) != self::$startLTR && substr($result . "\n", 0, self::$lenStart) != self::$startRTL) {
447
-					$leadingText .= substr($result, 0, 1);
448
-					$result = substr($result, 1);
449
-					continue;
450
-				}
451
-				$result = substr($result, 0, self::$lenStart) . $leadingText . substr($result, self::$lenStart);
452
-				break;
453
-			}
454
-		}
455
-
456
-		// Include solitary "-" and "+" in surrounding RTL text
457
-		$result = str_replace(array(self::$endRTL . self::$startLTR . '-' . self::$endLTR . self::$startRTL, self::$endRTL . self::$startLTR . '-' . self::$endLTR . self::$startRTL), array('-', '+'), $result);
458
-
459
-		// Remove empty spans
460
-		$result = str_replace(array(self::$startLTR . self::$endLTR, self::$startRTL . self::$endRTL), '', $result);
461
-
462
-		// Finally, correct '<LTR>', '</LTR>', '<RTL>', and '</RTL>'
463
-		switch ($direction) {
464
-		case 'BOTH':
465
-		case 'both':
466
-			// LTR text: <span dir="ltr"> text </span>
467
-			// RTL text: <span dir="rtl"> text </span>
468
-			$sLTR = '<span dir="ltr" ' . $class . '>' . $nothing;
469
-			$eLTR = $nothing . '</span>';
470
-			$sRTL = '<span dir="rtl" ' . $class . '>' . $nothing;
471
-			$eRTL = $nothing . '</span>';
472
-			break;
473
-		case 'LTR':
474
-		case 'ltr':
475
-			// LTR text: <span dir="ltr"> text </span>
476
-			// RTL text: text
477
-			$sLTR = '<span dir="ltr" ' . $class . '>' . $nothing;
478
-			$eLTR = $nothing . '</span>';
479
-			$sRTL = '';
480
-			$eRTL = '';
481
-			break;
482
-		case 'RTL':
483
-		case 'rtl':
484
-		default:
485
-			// LTR text: text
486
-			// RTL text: <span dir="rtl"> text </span>
487
-			$sLTR = '';
488
-			$eLTR = '';
489
-			$sRTL = '<span dir="rtl" ' . $class . '>' . $nothing;
490
-			$eRTL = $nothing . '</span>';
491
-			break;
492
-		}
493
-		$result = str_replace(array(self::$startLTR, self::$endLTR, self::$startRTL, self::$endRTL), array($sLTR, $eLTR, $sRTL, $eRTL), $result);
494
-
495
-		return $result;
496
-	}
497
-
498
-	/**
499
-	 * Wrap words that have an asterisk suffix in <u> and </u> tags.
500
-	 * This should underline starred names to show the preferred name.
501
-	 *
502
-	 * @param string $textSpan
503
-	 * @param string $direction
504
-	 *
505
-	 * @return string
506
-	 */
507
-	public static function starredName($textSpan, $direction) {
508
-		// To avoid a TCPDF bug that mixes up the word order, insert those <u> and </u> tags
509
-		// only when page and span directions are identical.
510
-		if ($direction === strtoupper(I18N::direction())) {
511
-			while (true) {
512
-				$starPos = strpos($textSpan, '*');
513
-				if ($starPos === false) {
514
-					break;
515
-				}
516
-				$trailingText = substr($textSpan, $starPos + 1);
517
-				$textSpan     = substr($textSpan, 0, $starPos);
518
-				$wordStart    = strrpos($textSpan, ' '); // Find the start of the word
519
-				if ($wordStart !== false) {
520
-					$leadingText = substr($textSpan, 0, $wordStart + 1);
521
-					$wordText    = substr($textSpan, $wordStart + 1);
522
-				} else {
523
-					$leadingText = '';
524
-					$wordText    = $textSpan;
525
-				}
526
-				$textSpan = $leadingText . '<u>' . $wordText . '</u>' . $trailingText;
527
-			}
528
-			$textSpan = preg_replace('~<span class="starredname">(.*)</span>~', '<u>\1</u>', $textSpan);
529
-			// The &nbsp; is a work-around for a TCPDF bug eating blanks.
530
-			$textSpan = str_replace(array(' <u>', '</u> '), array('&nbsp;<u>', '</u>&nbsp;'), $textSpan);
531
-		} else {
532
-			// Text and page directions differ:  remove the <span> and </span>
533
-			$textSpan = preg_replace('~(.*)\*~', '\1', $textSpan);
534
-			$textSpan = preg_replace('~<span class="starredname">(.*)</span>~', '\1', $textSpan);
535
-		}
536
-
537
-		return $textSpan;
538
-	}
539
-
540
-	/**
541
-	 * Get the next character from an input string
542
-	 *
543
-	 * @param string $text
544
-	 * @param string $offset
545
-	 *
546
-	 * @return array
547
-	 */
548
-	public static function getChar($text, $offset) {
549
-
550
-		if ($text == '') {
551
-			return array('letter' => '', 'length' => 0);
552
-		}
553
-
554
-		$char   = substr($text, $offset, 1);
555
-		$length = 1;
556
-		if ((ord($char) & 0xE0) == 0xC0) {
557
-			$length = 2;
558
-		}
559
-		if ((ord($char) & 0xF0) == 0xE0) {
560
-			$length = 3;
561
-		}
562
-		if ((ord($char) & 0xF8) == 0xF0) {
563
-			$length = 4;
564
-		}
565
-		$letter = substr($text, $offset, $length);
566
-
567
-		return array('letter' => $letter, 'length' => $length);
568
-	}
569
-
570
-	/**
571
-	 * Insert <br> into current span
572
-	 *
573
-	 * @param string $result
574
-	 */
575
-	public static function breakCurrentSpan(&$result) {
576
-		// Interrupt the current span, insert that <br>, and then continue the current span
577
-		$result .= self::$waitingText;
578
-		self::$waitingText = '';
579
-
580
-		$breakString = '<' . self::$currentState . 'br>';
581
-		$result .= $breakString;
582
-
583
-		return;
584
-	}
585
-
586
-	/**
587
-	 * Begin current span
588
-	 *
589
-	 * @param string $result
590
-	 */
591
-	public static function beginCurrentSpan(&$result) {
592
-		if (self::$currentState == 'LTR') {
593
-			$result .= self::$startLTR;
594
-		}
595
-		if (self::$currentState == 'RTL') {
596
-			$result .= self::$startRTL;
597
-		}
598
-
599
-		self::$posSpanStart = strlen($result);
600
-	}
601
-
602
-	/**
603
-	 * Finish current span
604
-	 *
605
-	 * @param string $result
606
-	 * @param bool $theEnd
607
-	 */
608
-	public static function finishCurrentSpan(&$result, $theEnd = false) {
609
-		$textSpan = substr($result, self::$posSpanStart);
610
-		$result   = substr($result, 0, self::$posSpanStart);
611
-
612
-		// Get rid of empty spans, so that our check for presence of RTL will work
613
-		$result = str_replace(array(self::$startLTR . self::$endLTR, self::$startRTL . self::$endRTL), '', $result);
614
-
615
-		// Look for numeric strings that are times (hh:mm:ss). These have to be separated from surrounding numbers.
616
-		$tempResult = '';
617
-		while ($textSpan != '') {
618
-			$posColon = strpos($textSpan, ':');
619
-			if ($posColon === false) {
620
-				break;
621
-			} // No more possible time strings
622
-			$posLRE = strpos($textSpan, WT_UTF8_LRE);
623
-			if ($posLRE === false) {
624
-				break;
625
-			} // No more numeric strings
626
-			$posPDF = strpos($textSpan, WT_UTF8_PDF, $posLRE);
627
-			if ($posPDF === false) {
628
-				break;
629
-			} // No more numeric strings
630
-
631
-			$tempResult .= substr($textSpan, 0, $posLRE + 3); // Copy everything preceding the numeric string
632
-			$numericString = substr($textSpan, $posLRE + 3, $posPDF - $posLRE); // Separate the entire numeric string
633
-			$textSpan      = substr($textSpan, $posPDF + 3);
634
-			$posColon      = strpos($numericString, ':');
635
-			if ($posColon === false) {
636
-				// Nothing that looks like a time here
637
-				$tempResult .= $numericString;
638
-				continue;
639
-			}
640
-			$posBlank = strpos($numericString . ' ', ' ');
641
-			$posNbsp  = strpos($numericString . '&nbsp;', '&nbsp;');
642
-			if ($posBlank < $posNbsp) {
643
-				$posSeparator    = $posBlank;
644
-				$lengthSeparator = 1;
645
-			} else {
646
-				$posSeparator    = $posNbsp;
647
-				$lengthSeparator = 6;
648
-			}
649
-			if ($posColon > $posSeparator) {
650
-				// We have a time string preceded by a blank: Exclude that blank from the numeric string
651
-				$tempResult .= substr($numericString, 0, $posSeparator);
652
-				$tempResult .= WT_UTF8_PDF;
653
-				$tempResult .= substr($numericString, $posSeparator, $lengthSeparator);
654
-				$tempResult .= WT_UTF8_LRE;
655
-				$numericString = substr($numericString, $posSeparator + $lengthSeparator);
656
-			}
657
-
658
-			$posBlank = strpos($numericString, ' ');
659
-			$posNbsp  = strpos($numericString, '&nbsp;');
660
-			if ($posBlank === false && $posNbsp === false) {
661
-				// The time string isn't followed by a blank
662
-				$textSpan = $numericString . $textSpan;
663
-				continue;
664
-			}
665
-
666
-			// We have a time string followed by a blank: Exclude that blank from the numeric string
667
-			if ($posBlank === false) {
668
-				$posSeparator    = $posNbsp;
669
-				$lengthSeparator = 6;
670
-			} elseif ($posNbsp === false) {
671
-				$posSeparator    = $posBlank;
672
-				$lengthSeparator = 1;
673
-			} elseif ($posBlank < $posNbsp) {
674
-				$posSeparator    = $posBlank;
675
-				$lengthSeparator = 1;
676
-			} else {
677
-				$posSeparator    = $posNbsp;
678
-				$lengthSeparator = 6;
679
-			}
680
-			$tempResult .= substr($numericString, 0, $posSeparator);
681
-			$tempResult .= WT_UTF8_PDF;
682
-			$tempResult .= substr($numericString, $posSeparator, $lengthSeparator);
683
-			$posSeparator += $lengthSeparator;
684
-			$numericString = substr($numericString, $posSeparator);
685
-			$textSpan      = WT_UTF8_LRE . $numericString . $textSpan;
686
-		}
687
-		$textSpan       = $tempResult . $textSpan;
688
-		$trailingBlanks = '';
689
-		$trailingBreaks = '';
690
-
691
-		/* ****************************** LTR text handling ******************************** */
692
-
693
-		if (self::$currentState === 'LTR') {
694
-			// Move trailing numeric strings to the following RTL text. Include any blanks preceding or following the numeric text too.
695
-			if (I18N::direction() === 'rtl' && self::$previousState === 'RTL' && !$theEnd) {
696
-				$trailingString = '';
697
-				$savedSpan      = $textSpan;
698
-				while ($textSpan !== '') {
699
-					// Look for trailing spaces and tentatively move them
700
-					if (substr($textSpan, -1) === ' ') {
701
-						$trailingString = ' ' . $trailingString;
702
-						$textSpan       = substr($textSpan, 0, -1);
703
-						continue;
704
-					}
705
-					if (substr($textSpan, -6) === '&nbsp;') {
706
-						$trailingString = '&nbsp;' . $trailingString;
707
-						$textSpan       = substr($textSpan, 0, -1);
708
-						continue;
709
-					}
710
-					if (substr($textSpan, -3) !== WT_UTF8_PDF) {
711
-						// There is no trailing numeric string
712
-						$textSpan = $savedSpan;
713
-						break;
714
-					}
715
-
716
-					// We have a numeric string
717
-					$posStartNumber = strrpos($textSpan, WT_UTF8_LRE);
718
-					if ($posStartNumber === false) {
719
-						$posStartNumber = 0;
720
-					}
721
-					$trailingString = substr($textSpan, $posStartNumber, strlen($textSpan) - $posStartNumber) . $trailingString;
722
-					$textSpan       = substr($textSpan, 0, $posStartNumber);
723
-
724
-					// Look for more spaces and move them too
725
-					while ($textSpan != '') {
726
-						if (substr($textSpan, -1) == ' ') {
727
-							$trailingString = ' ' . $trailingString;
728
-							$textSpan       = substr($textSpan, 0, -1);
729
-							continue;
730
-						}
731
-						if (substr($textSpan, -6) == '&nbsp;') {
732
-							$trailingString = '&nbsp;' . $trailingString;
733
-							$textSpan       = substr($textSpan, 0, -1);
734
-							continue;
735
-						}
736
-						break;
737
-					}
738
-
739
-					self::$waitingText = $trailingString . self::$waitingText;
740
-					break;
741
-				}
742
-			}
743
-
744
-			$savedSpan = $textSpan;
745
-			// Move any trailing <br>, optionally preceded or followed by blanks, outside this LTR span
746
-			while ($textSpan != '') {
747
-				if (substr($textSpan, -1) == ' ') {
748
-					$trailingBlanks = ' ' . $trailingBlanks;
749
-					$textSpan       = substr($textSpan, 0, -1);
750
-					continue;
751
-				}
752
-				if (substr('......' . $textSpan, -6) == '&nbsp;') {
753
-					$trailingBlanks = '&nbsp;' . $trailingBlanks;
754
-					$textSpan       = substr($textSpan, 0, -6);
755
-					continue;
756
-				}
757
-				break;
758
-			}
759
-			while (substr($textSpan, -9) == '<LTRbr>') {
760
-				$trailingBreaks = '<br>' . $trailingBreaks; // Plain <br> because it’s outside a span
761
-				$textSpan       = substr($textSpan, 0, -9);
762
-			}
763
-			if ($trailingBreaks != '') {
764
-				while ($textSpan != '') {
765
-					if (substr($textSpan, -1) == ' ') {
766
-						$trailingBreaks = ' ' . $trailingBreaks;
767
-						$textSpan       = substr($textSpan, 0, -1);
768
-						continue;
769
-					}
770
-					if (substr('......' . $textSpan, -6) == '&nbsp;') {
771
-						$trailingBreaks = '&nbsp;' . $trailingBreaks;
772
-						$textSpan       = substr($textSpan, 0, -6);
773
-						continue;
774
-					}
775
-					break;
776
-				}
777
-				self::$waitingText = $trailingBlanks . self::$waitingText; // Put those trailing blanks inside the following span
778
-			} else {
779
-				$textSpan = $savedSpan;
780
-			}
781
-
782
-			$trailingBlanks      = '';
783
-			$trailingPunctuation = '';
784
-			$trailingID          = '';
785
-			$trailingSeparator   = '';
786
-			$leadingSeparator    = '';
787
-			while (I18N::direction() === 'rtl') {
788
-				if (strpos($result, self::$startRTL) !== false) {
789
-					// Remove trailing blanks for inclusion in a separate LTR span
790
-					while ($textSpan != '') {
791
-						if (substr($textSpan, -1) === ' ') {
792
-							$trailingBlanks = ' ' . $trailingBlanks;
793
-							$textSpan       = substr($textSpan, 0, -1);
794
-							continue;
795
-						}
796
-						if (substr($textSpan, -6) === '&nbsp;') {
797
-							$trailingBlanks = '&nbsp;' . $trailingBlanks;
798
-							$textSpan       = substr($textSpan, 0, -1);
799
-							continue;
800
-						}
801
-						break;
802
-					}
803
-
804
-					// Remove trailing punctuation for inclusion in a separate LTR span
805
-					if ($textSpan == '') {
806
-						$trailingChar = "\n";
807
-					} else {
808
-						$trailingChar = substr($textSpan, -1);
809
-					}
810
-					if (strpos(self::PUNCTUATION, $trailingChar) !== false) {
811
-						$trailingPunctuation = $trailingChar;
812
-						$textSpan            = substr($textSpan, 0, -1);
813
-					}
814
-				}
815
-
816
-				// Remove trailing ID numbers that look like "(xnnn)" for inclusion in a separate LTR span
817
-				while (true) {
818
-					if (substr($textSpan, -1) != ')') {
819
-						break;
820
-					} // There is no trailing ')'
821
-					$posLeftParen = strrpos($textSpan, '(');
822
-					if ($posLeftParen === false) {
823
-						break;
824
-					} // There is no leading '('
825
-					$temp = self::stripLrmRlm(substr($textSpan, $posLeftParen)); // Get rid of UTF8 control codes
826
-
827
-					// If the parenthesized text doesn't look like an ID number,
828
-					// we don't want to touch it.
829
-					// This check won’t work if somebody uses ID numbers with an unusual format.
830
-					$offset    = 1;
831
-					$charArray = self::getChar($temp, $offset); // Get 1st character of parenthesized text
832
-					if (strpos(self::NUMBERS, $charArray['letter']) !== false) {
833
-						break;
834
-					}
835
-					$offset += $charArray['length']; // Point at 2nd character of parenthesized text
836
-					if (strpos(self::NUMBERS, substr($temp, $offset, 1)) === false) {
837
-						break;
838
-					}
839
-					// 1st character of parenthesized text is alpha, 2nd character is a digit; last has to be a digit too
840
-					if (strpos(self::NUMBERS, substr($temp, -2, 1)) === false) {
841
-						break;
842
-					}
843
-
844
-					$trailingID = substr($textSpan, $posLeftParen);
845
-					$textSpan   = substr($textSpan, 0, $posLeftParen);
846
-					break;
847
-				}
848
-
849
-				// Look for " - " or blank preceding the ID number and remove it for inclusion in a separate LTR span
850
-				if ($trailingID != '') {
851
-					while ($textSpan != '') {
852
-						if (substr($textSpan, -1) == ' ') {
853
-							$trailingSeparator = ' ' . $trailingSeparator;
854
-							$textSpan          = substr($textSpan, 0, -1);
855
-							continue;
856
-						}
857
-						if (substr($textSpan, -6) == '&nbsp;') {
858
-							$trailingSeparator = '&nbsp;' . $trailingSeparator;
859
-							$textSpan          = substr($textSpan, 0, -6);
860
-							continue;
861
-						}
862
-						if (substr($textSpan, -1) == '-') {
863
-							$trailingSeparator = '-' . $trailingSeparator;
864
-							$textSpan          = substr($textSpan, 0, -1);
865
-							continue;
866
-						}
867
-						break;
868
-					}
869
-				}
870
-
871
-				// Look for " - " preceding the text and remove it for inclusion in a separate LTR span
872
-				$foundSeparator = false;
873
-				$savedSpan      = $textSpan;
874
-				while ($textSpan != '') {
875
-					if (substr($textSpan, 0, 1) == ' ') {
876
-						$leadingSeparator = ' ' . $leadingSeparator;
877
-						$textSpan         = substr($textSpan, 1);
878
-						continue;
879
-					}
880
-					if (substr($textSpan, 0, 6) == '&nbsp;') {
881
-						$leadingSeparator = '&nbsp;' . $leadingSeparator;
882
-						$textSpan         = substr($textSpan, 6);
883
-						continue;
884
-					}
885
-					if (substr($textSpan, 0, 1) == '-') {
886
-						$leadingSeparator = '-' . $leadingSeparator;
887
-						$textSpan         = substr($textSpan, 1);
888
-						$foundSeparator   = true;
889
-						continue;
890
-					}
891
-					break;
892
-				}
893
-				if (!$foundSeparator) {
894
-					$textSpan         = $savedSpan;
895
-					$leadingSeparator = '';
896
-				}
897
-				break;
898
-			}
899
-
900
-			// We're done: finish the span
901
-			$textSpan = self::starredName($textSpan, 'LTR'); // Wrap starred name in <u> and </u> tags
902
-			while (true) {
903
-				// Remove blanks that precede <LTRbr>
904
-				if (strpos($textSpan, ' <LTRbr>') !== false) {
905
-					$textSpan = str_replace(' <LTRbr>', '<LTRbr>', $textSpan);
906
-					continue;
907
-				}
908
-				if (strpos($textSpan, '&nbsp;<LTRbr>') !== false) {
909
-					$textSpan = str_replace('&nbsp;<LTRbr>', '<LTRbr>', $textSpan);
910
-					continue;
911
-				}
912
-				break;
913
-			}
914
-			if ($leadingSeparator != '') {
915
-				$result = $result . self::$startLTR . $leadingSeparator . self::$endLTR;
916
-			}
917
-			$result = $result . $textSpan . self::$endLTR;
918
-			if ($trailingSeparator != '') {
919
-				$result = $result . self::$startLTR . $trailingSeparator . self::$endLTR;
920
-			}
921
-			if ($trailingID != '') {
922
-				$result = $result . self::$startLTR . $trailingID . self::$endLTR;
923
-			}
924
-			if ($trailingPunctuation != '') {
925
-				$result = $result . self::$startLTR . $trailingPunctuation . self::$endLTR;
926
-			}
927
-			if ($trailingBlanks != '') {
928
-				$result = $result . self::$startLTR . $trailingBlanks . self::$endLTR;
929
-			}
930
-		}
931
-
932
-		/* ****************************** RTL text handling ******************************** */
933
-
934
-		if (self::$currentState == 'RTL') {
935
-			$savedSpan = $textSpan;
936
-
937
-			// Move any trailing <br>, optionally followed by blanks, outside this RTL span
938
-			while ($textSpan != '') {
939
-				if (substr($textSpan, -1) == ' ') {
940
-					$trailingBlanks = ' ' . $trailingBlanks;
941
-					$textSpan       = substr($textSpan, 0, -1);
942
-					continue;
943
-				}
944
-				if (substr('......' . $textSpan, -6) == '&nbsp;') {
945
-					$trailingBlanks = '&nbsp;' . $trailingBlanks;
946
-					$textSpan       = substr($textSpan, 0, -6);
947
-					continue;
948
-				}
949
-				break;
950
-			}
951
-			while (substr($textSpan, -9) == '<RTLbr>') {
952
-				$trailingBreaks = '<br>' . $trailingBreaks; // Plain <br> because it’s outside a span
953
-				$textSpan       = substr($textSpan, 0, -9);
954
-			}
955
-			if ($trailingBreaks != '') {
956
-				self::$waitingText = $trailingBlanks . self::$waitingText; // Put those trailing blanks inside the following span
957
-			} else {
958
-				$textSpan = $savedSpan;
959
-			}
960
-
961
-			// Move trailing numeric strings to the following LTR text. Include any blanks preceding or following the numeric text too.
962
-			if (!$theEnd && I18N::direction() !== 'rtl') {
963
-				$trailingString = '';
964
-				$savedSpan      = $textSpan;
965
-				while ($textSpan != '') {
966
-					// Look for trailing spaces and tentatively move them
967
-					if (substr($textSpan, -1) === ' ') {
968
-						$trailingString = ' ' . $trailingString;
969
-						$textSpan       = substr($textSpan, 0, -1);
970
-						continue;
971
-					}
972
-					if (substr($textSpan, -6) === '&nbsp;') {
973
-						$trailingString = '&nbsp;' . $trailingString;
974
-						$textSpan       = substr($textSpan, 0, -1);
975
-						continue;
976
-					}
977
-					if (substr($textSpan, -3) !== WT_UTF8_PDF) {
978
-						// There is no trailing numeric string
979
-						$textSpan = $savedSpan;
980
-						break;
981
-					}
982
-
983
-					// We have a numeric string
984
-					$posStartNumber = strrpos($textSpan, WT_UTF8_LRE);
985
-					if ($posStartNumber === false) {
986
-						$posStartNumber = 0;
987
-					}
988
-					$trailingString = substr($textSpan, $posStartNumber, strlen($textSpan) - $posStartNumber) . $trailingString;
989
-					$textSpan       = substr($textSpan, 0, $posStartNumber);
990
-
991
-					// Look for more spaces and move them too
992
-					while ($textSpan != '') {
993
-						if (substr($textSpan, -1) == ' ') {
994
-							$trailingString = ' ' . $trailingString;
995
-							$textSpan       = substr($textSpan, 0, -1);
996
-							continue;
997
-						}
998
-						if (substr($textSpan, -6) == '&nbsp;') {
999
-							$trailingString = '&nbsp;' . $trailingString;
1000
-							$textSpan       = substr($textSpan, 0, -1);
1001
-							continue;
1002
-						}
1003
-						break;
1004
-					}
1005
-
1006
-					self::$waitingText = $trailingString . self::$waitingText;
1007
-					break;
1008
-				}
1009
-			}
1010
-
1011
-			// Trailing " - " needs to be prefixed to the following span
1012
-			if (!$theEnd && substr('...' . $textSpan, -3) == ' - ') {
1013
-				$textSpan          = substr($textSpan, 0, -3);
1014
-				self::$waitingText = ' - ' . self::$waitingText;
1015
-			}
1016
-
1017
-			while (I18N::direction() === 'rtl') {
1018
-				// Look for " - " preceding <RTLbr> and relocate it to the front of the string
1019
-				$posDashString = strpos($textSpan, ' - <RTLbr>');
1020
-				if ($posDashString === false) {
1021
-					break;
1022
-				}
1023
-				$posStringStart = strrpos(substr($textSpan, 0, $posDashString), '<RTLbr>');
1024
-				if ($posStringStart === false) {
1025
-					$posStringStart = 0;
1026
-				} else {
1027
-					$posStringStart += 9;
1028
-				} // Point to the first char following the last <RTLbr>
1029
-
1030
-				$textSpan = substr($textSpan, 0, $posStringStart) . ' - ' . substr($textSpan, $posStringStart, $posDashString - $posStringStart) . substr($textSpan, $posDashString + 3);
1031
-			}
1032
-
1033
-			// Strip leading spaces from the RTL text
1034
-			$countLeadingSpaces = 0;
1035
-			while ($textSpan != '') {
1036
-				if (substr($textSpan, 0, 1) == ' ') {
1037
-					$countLeadingSpaces++;
1038
-					$textSpan = substr($textSpan, 1);
1039
-					continue;
1040
-				}
1041
-				if (substr($textSpan, 0, 6) == '&nbsp;') {
1042
-					$countLeadingSpaces++;
1043
-					$textSpan = substr($textSpan, 6);
1044
-					continue;
1045
-				}
1046
-				break;
1047
-			}
1048
-
1049
-			// Strip trailing spaces from the RTL text
1050
-			$countTrailingSpaces = 0;
1051
-			while ($textSpan != '') {
1052
-				if (substr($textSpan, -1) == ' ') {
1053
-					$countTrailingSpaces++;
1054
-					$textSpan = substr($textSpan, 0, -1);
1055
-					continue;
1056
-				}
1057
-				if (substr($textSpan, -6) == '&nbsp;') {
1058
-					$countTrailingSpaces++;
1059
-					$textSpan = substr($textSpan, 0, -6);
1060
-					continue;
1061
-				}
1062
-				break;
1063
-			}
1064
-
1065
-			// Look for trailing " -", reverse it, and relocate it to the front of the string
1066
-			if (substr($textSpan, -2) === ' -') {
1067
-				$posDashString  = strlen($textSpan) - 2;
1068
-				$posStringStart = strrpos(substr($textSpan, 0, $posDashString), '<RTLbr>');
1069
-				if ($posStringStart === false) {
1070
-					$posStringStart = 0;
1071
-				} else {
1072
-					$posStringStart += 9;
1073
-				} // Point to the first char following the last <RTLbr>
1074
-
1075
-				$textSpan = substr($textSpan, 0, $posStringStart) . '- ' . substr($textSpan, $posStringStart, $posDashString - $posStringStart) . substr($textSpan, $posDashString + 2);
1076
-			}
1077
-
1078
-			if ($countLeadingSpaces != 0) {
1079
-				$newLength = strlen($textSpan) + $countLeadingSpaces;
1080
-				$textSpan  = str_pad($textSpan, $newLength, ' ', (I18N::direction() === 'rtl' ? STR_PAD_LEFT : STR_PAD_RIGHT));
1081
-			}
1082
-			if ($countTrailingSpaces != 0) {
1083
-				if (I18N::direction() === 'ltr') {
1084
-					if ($trailingBreaks === '') {
1085
-						// Move trailing RTL spaces to front of following LTR span
1086
-						$newLength         = strlen(self::$waitingText) + $countTrailingSpaces;
1087
-						self::$waitingText = str_pad(self::$waitingText, $newLength, ' ', STR_PAD_LEFT);
1088
-					}
1089
-				} else {
1090
-					$newLength = strlen($textSpan) + $countTrailingSpaces;
1091
-					$textSpan  = str_pad($textSpan, $newLength, ' ', STR_PAD_RIGHT);
1092
-				}
1093
-			}
1094
-
1095
-			// We're done: finish the span
1096
-			$textSpan = self::starredName($textSpan, 'RTL'); // Wrap starred name in <u> and </u> tags
1097
-			$result   = $result . $textSpan . self::$endRTL;
1098
-		}
1099
-
1100
-		if (self::$currentState != 'LTR' && self::$currentState != 'RTL') {
1101
-			$result = $result . $textSpan;
1102
-		}
1103
-
1104
-		$result .= $trailingBreaks; // Get rid of any waiting <br>
1105
-
1106
-		return;
1107
-	}
1108
-
1109
-	/**
1110
-	 * Wrap text, similar to the PHP wordwrap() function.
1111
-	 *
1112
-	 * @param string $string
1113
-	 * @param int $width
1114
-	 * @param string $sep
1115
-	 * @param bool $cut
1116
-	 *
1117
-	 * @return string
1118
-	 */
1119
-	public static function utf8WordWrap($string, $width = 75, $sep = "\n", $cut = false) {
1120
-		$out = '';
1121
-		while ($string) {
1122
-			if (mb_strlen($string) <= $width) {
1123
-				// Do not wrap any text that is less than the output area.
1124
-				$out .= $string;
1125
-				$string = '';
1126
-			} else {
1127
-				$sub1 = mb_substr($string, 0, $width + 1);
1128
-				if (mb_substr($string, mb_strlen($sub1) - 1, 1) == ' ') {
1129
-					// include words that end by a space immediately after the area.
1130
-					$sub = $sub1;
1131
-				} else {
1132
-					$sub = mb_substr($string, 0, $width);
1133
-				}
1134
-				$spacepos = strrpos($sub, ' ');
1135
-				if ($spacepos === false) {
1136
-					// No space on line?
1137
-					if ($cut) {
1138
-						$out .= $sub . $sep;
1139
-						$string = mb_substr($string, mb_strlen($sub));
1140
-					} else {
1141
-						$spacepos = strpos($string, ' ');
1142
-						if ($spacepos === false) {
1143
-							$out .= $string;
1144
-							$string = '';
1145
-						} else {
1146
-							$out .= substr($string, 0, $spacepos) . $sep;
1147
-							$string = substr($string, $spacepos + 1);
1148
-						}
1149
-					}
1150
-				} else {
1151
-					// Split at space;
1152
-					$out .= substr($string, 0, $spacepos) . $sep;
1153
-					$string = substr($string, $spacepos + 1);
1154
-				}
1155
-			}
1156
-		}
1157
-
1158
-		return $out;
1159
-	}
26
+    const CLOSE_PARENTHESES = ')]}';
27
+
28
+    const NUMBERS = '0123456789';
29
+
30
+    const NUMBER_PREFIX = '+-'; // Treat these like numbers when at beginning or end of numeric strings
31
+
32
+    const NUMBER_PUNCTUATION = '- ,.:/'; // Treat these like numbers when inside numeric strings
33
+
34
+    const PUNCTUATION = ',.:;?!';
35
+
36
+    /** @var string Were we previously processing LTR or RTL. */
37
+    private static $previousState;
38
+
39
+    /** @var string Are we currently processing LTR or RTL. */
40
+    private static $currentState;
41
+
42
+    /** @var string Text waiting to be processed. */
43
+    private static $waitingText;
44
+
45
+    /** @var string LTR text. */
46
+    private static $startLTR;
47
+
48
+    /** @var string LTR text. */
49
+    private static $endLTR;
50
+
51
+    /** @var string RTL text. */
52
+    private static $startRTL;
53
+
54
+    /** @var string RTL text. */
55
+    private static $endRTL;
56
+
57
+    /** @var int Offset into the text. */
58
+    private static $lenStart;
59
+
60
+    /** @var int Offset into the text. */
61
+    private static $lenEnd;
62
+
63
+    /** @var int Offset into the text. */
64
+    private static $posSpanStart;
65
+
66
+    /**
67
+     * This function strips &lrm; and &rlm; from the input string. It should be used for all
68
+     * text that has been passed through the PrintReady() function before that text is stored
69
+     * in the database. The database should NEVER contain these characters.
70
+     *
71
+     * @param  string $inputText The string from which the &lrm; and &rlm; characters should be stripped
72
+     *
73
+     * @return string The input string, with &lrm; and &rlm; stripped
74
+     */
75
+    public static function stripLrmRlm($inputText) {
76
+        return str_replace(array(WT_UTF8_LRM, WT_UTF8_RLM, WT_UTF8_LRO, WT_UTF8_RLO, WT_UTF8_LRE, WT_UTF8_RLE, WT_UTF8_PDF, "&lrm;", "&rlm;", "&LRM;", "&RLM;"), "", $inputText);
77
+    }
78
+
79
+    /**
80
+     * This function encapsulates all texts in the input with <span dir='xxx'> and </span>
81
+     * according to the directionality specified.
82
+     *
83
+     * @param string $inputText Raw input
84
+     * @param string $direction Directionality (LTR, BOTH, RTL) default BOTH
85
+     * @param string $class Additional text to insert into output <span dir="xxx"> (such as 'class="yyy"')
86
+     *
87
+     * @return string The string with all texts encapsulated as required
88
+     */
89
+    public static function spanLtrRtl($inputText, $direction = 'BOTH', $class = '') {
90
+        if ($inputText == '') {
91
+            // Nothing to do
92
+            return '';
93
+        }
94
+
95
+        $workingText = str_replace("\n", '<br>', $inputText);
96
+        $workingText = str_replace(array('<span class="starredname"><br>', '<span<br>class="starredname">'), '<br><span class="starredname">', $workingText); // Reposition some incorrectly placed line breaks
97
+        $workingText = self::stripLrmRlm($workingText); // Get rid of any existing UTF8 control codes
98
+
99
+        // $nothing  = '&zwnj;'; // Zero Width Non-Joiner  (not sure whether this is still needed to work around a TCPDF bug)
100
+        $nothing = '';
101
+
102
+        self::$startLTR = '<LTR>'; // This will become '<span dir="ltr">' at the end
103
+        self::$endLTR   = '</LTR>'; // This will become '</span>' at the end
104
+        self::$startRTL = '<RTL>'; // This will become '<span dir="rtl">' at the end
105
+        self::$endRTL   = '</RTL>'; // This will become '</span>' at the end
106
+        self::$lenStart = strlen(self::$startLTR); // RTL version MUST have same length
107
+        self::$lenEnd   = strlen(self::$endLTR); // RTL version MUST have same length
108
+
109
+        self::$previousState = '';
110
+        self::$currentState  = strtoupper(I18N::direction());
111
+        $numberState         = false; // Set when we're inside a numeric string
112
+        $result              = '';
113
+        self::$waitingText   = '';
114
+        $openParDirection    = array();
115
+
116
+        self::beginCurrentSpan($result);
117
+
118
+        while ($workingText != '') {
119
+            $charArray     = self::getChar($workingText, 0); // Get the next ASCII or UTF-8 character
120
+            $currentLetter = $charArray['letter'];
121
+            $currentLen    = $charArray['length'];
122
+
123
+            $openParIndex  = strpos(self::OPEN_PARENTHESES, $currentLetter); // Which opening parenthesis is this?
124
+            $closeParIndex = strpos(self::CLOSE_PARENTHESES, $currentLetter); // Which closing parenthesis is this?
125
+
126
+            switch ($currentLetter) {
127
+            case '<':
128
+                // Assume this '<' starts an HTML element
129
+                $endPos = strpos($workingText, '>'); // look for the terminating '>'
130
+                if ($endPos === false) {
131
+                    $endPos = 0;
132
+                }
133
+                $currentLen += $endPos;
134
+                $element = substr($workingText, 0, $currentLen);
135
+                $temp    = strtolower(substr($element, 0, 3));
136
+                if (strlen($element) < 7 && $temp == '<br') {
137
+                    if ($numberState) {
138
+                        $numberState = false;
139
+                        if (self::$currentState == 'RTL') {
140
+                            self::$waitingText .= WT_UTF8_PDF;
141
+                        }
142
+                    }
143
+                    self::breakCurrentSpan($result);
144
+                } elseif (self::$waitingText == '') {
145
+                    $result .= $element;
146
+                } else {
147
+                    self::$waitingText .= $element;
148
+                }
149
+                $workingText = substr($workingText, $currentLen);
150
+                break;
151
+            case '&':
152
+                // Assume this '&' starts an HTML entity
153
+                $endPos = strpos($workingText, ';'); // look for the terminating ';'
154
+                if ($endPos === false) {
155
+                    $endPos = 0;
156
+                }
157
+                $currentLen += $endPos;
158
+                $entity = substr($workingText, 0, $currentLen);
159
+                if (strtolower($entity) == '&nbsp;') {
160
+                    $entity .= '&nbsp;'; // Ensure consistent case for this entity
161
+                }
162
+                if (self::$waitingText == '') {
163
+                    $result .= $entity;
164
+                } else {
165
+                    self::$waitingText .= $entity;
166
+                }
167
+                $workingText = substr($workingText, $currentLen);
168
+                break;
169
+            case '{':
170
+                if (substr($workingText, 1, 1) == '{') {
171
+                    // Assume this '{{' starts a TCPDF directive
172
+                    $endPos = strpos($workingText, '}}'); // look for the terminating '}}'
173
+                    if ($endPos === false) {
174
+                        $endPos = 0;
175
+                    }
176
+                    $currentLen        = $endPos + 2;
177
+                    $directive         = substr($workingText, 0, $currentLen);
178
+                    $workingText       = substr($workingText, $currentLen);
179
+                    $result            = $result . self::$waitingText . $directive;
180
+                    self::$waitingText = '';
181
+                    break;
182
+                }
183
+            default:
184
+                // Look for strings of numbers with optional leading or trailing + or -
185
+                // and with optional embedded numeric punctuation
186
+                if ($numberState) {
187
+                    // If we're inside a numeric string, look for reasons to end it
188
+                    $offset    = 0; // Be sure to look at the current character first
189
+                    $charArray = self::getChar($workingText . "\n", $offset);
190
+                    if (strpos(self::NUMBERS, $charArray['letter']) === false) {
191
+                        // This is not a digit. Is it numeric punctuation?
192
+                        if (substr($workingText . "\n", $offset, 6) == '&nbsp;') {
193
+                            $offset += 6; // This could be numeric punctuation
194
+                        } elseif (strpos(self::NUMBER_PUNCTUATION, $charArray['letter']) !== false) {
195
+                            $offset += $charArray['length']; // This could be numeric punctuation
196
+                        }
197
+                        // If the next character is a digit, the current character is numeric punctuation
198
+                        $charArray = self::getChar($workingText . "\n", $offset);
199
+                        if (strpos(self::NUMBERS, $charArray['letter']) === false) {
200
+                            // This is not a digit. End the run of digits and punctuation.
201
+                            $numberState = false;
202
+                            if (self::$currentState == 'RTL') {
203
+                                if (strpos(self::NUMBER_PREFIX, $currentLetter) === false) {
204
+                                    $currentLetter = WT_UTF8_PDF . $currentLetter;
205
+                                } else {
206
+                                    $currentLetter = $currentLetter . WT_UTF8_PDF; // Include a trailing + or - in the run
207
+                                }
208
+                            }
209
+                        }
210
+                    }
211
+                } else {
212
+                    // If we're outside a numeric string, look for reasons to start it
213
+                    if (strpos(self::NUMBER_PREFIX, $currentLetter) !== false) {
214
+                        // This might be a number lead-in
215
+                        $offset   = $currentLen;
216
+                        $nextChar = substr($workingText . "\n", $offset, 1);
217
+                        if (strpos(self::NUMBERS, $nextChar) !== false) {
218
+                            $numberState = true; // We found a digit: the lead-in is therefore numeric
219
+                            if (self::$currentState == 'RTL') {
220
+                                $currentLetter = WT_UTF8_LRE . $currentLetter;
221
+                            }
222
+                        }
223
+                    } elseif (strpos(self::NUMBERS, $currentLetter) !== false) {
224
+                        $numberState = true; // The current letter is a digit
225
+                        if (self::$currentState == 'RTL') {
226
+                            $currentLetter = WT_UTF8_LRE . $currentLetter;
227
+                        }
228
+                    }
229
+                }
230
+
231
+                // Determine the directionality of the current UTF-8 character
232
+                $newState = self::$currentState;
233
+                while (true) {
234
+                    if (I18N::scriptDirection(I18N::textScript($currentLetter)) === 'rtl') {
235
+                        if (self::$currentState == '') {
236
+                            $newState = 'RTL';
237
+                            break;
238
+                        }
239
+
240
+                        if (self::$currentState == 'RTL') {
241
+                            break;
242
+                        }
243
+                        // Switch to RTL only if this isn't a solitary RTL letter
244
+                        $tempText = substr($workingText, $currentLen);
245
+                        while ($tempText != '') {
246
+                            $nextCharArray = self::getChar($tempText, 0);
247
+                            $nextLetter    = $nextCharArray['letter'];
248
+                            $nextLen       = $nextCharArray['length'];
249
+                            $tempText      = substr($tempText, $nextLen);
250
+
251
+                            if (I18N::scriptDirection(I18N::textScript($nextLetter)) === 'rtl') {
252
+                                $newState = 'RTL';
253
+                                break 2;
254
+                            }
255
+
256
+                            if (strpos(self::PUNCTUATION, $nextLetter) !== false || strpos(self::OPEN_PARENTHESES, $nextLetter) !== false) {
257
+                                $newState = 'RTL';
258
+                                break 2;
259
+                            }
260
+
261
+                            if ($nextLetter === ' ') {
262
+                                break;
263
+                            }
264
+                            $nextLetter .= substr($tempText . "\n", 0, 5);
265
+                            if ($nextLetter === '&nbsp;') {
266
+                                break;
267
+                            }
268
+                        }
269
+                        // This is a solitary RTL letter : wrap it in UTF8 control codes to force LTR directionality
270
+                        $currentLetter = WT_UTF8_LRO . $currentLetter . WT_UTF8_PDF;
271
+                        $newState      = 'LTR';
272
+                        break;
273
+                    }
274
+                    if (($currentLen != 1) || ($currentLetter >= 'A' && $currentLetter <= 'Z') || ($currentLetter >= 'a' && $currentLetter <= 'z')) {
275
+                        // Since it’s neither Hebrew nor Arabic, this UTF-8 character or ASCII letter must be LTR
276
+                        $newState = 'LTR';
277
+                        break;
278
+                    }
279
+                    if ($closeParIndex !== false) {
280
+                        // This closing parenthesis has to inherit the matching opening parenthesis' directionality
281
+                        if (!empty($openParDirection[$closeParIndex]) && $openParDirection[$closeParIndex] != '?') {
282
+                            $newState = $openParDirection[$closeParIndex];
283
+                        }
284
+                        $openParDirection[$closeParIndex] = '';
285
+                        break;
286
+                    }
287
+                    if ($openParIndex !== false) {
288
+                        // Opening parentheses always inherit the following directionality
289
+                        self::$waitingText .= $currentLetter;
290
+                        $workingText = substr($workingText, $currentLen);
291
+                        while (true) {
292
+                            if ($workingText === '') {
293
+                                break;
294
+                            }
295
+                            if (substr($workingText, 0, 1) === ' ') {
296
+                                // Spaces following this left parenthesis inherit the following directionality too
297
+                                self::$waitingText .= ' ';
298
+                                $workingText = substr($workingText, 1);
299
+                                continue;
300
+                            }
301
+                            if (substr($workingText, 0, 6) === '&nbsp;') {
302
+                                // Spaces following this left parenthesis inherit the following directionality too
303
+                                self::$waitingText .= '&nbsp;';
304
+                                $workingText = substr($workingText, 6);
305
+                                continue;
306
+                            }
307
+                            break;
308
+                        }
309
+                        $openParDirection[$openParIndex] = '?';
310
+                        break 2; // double break because we're waiting for more information
311
+                    }
312
+
313
+                    // We have a digit or a "normal" special character.
314
+                    //
315
+                    // When this character is not at the start of the input string, it inherits the preceding directionality;
316
+                    // at the start of the input string, it assumes the following directionality.
317
+                    //
318
+                    // Exceptions to this rule will be handled later during final clean-up.
319
+                    //
320
+                    self::$waitingText .= $currentLetter;
321
+                    $workingText = substr($workingText, $currentLen);
322
+                    if (self::$currentState != '') {
323
+                        $result .= self::$waitingText;
324
+                        self::$waitingText = '';
325
+                    }
326
+                    break 2; // double break because we're waiting for more information
327
+                }
328
+                if ($newState != self::$currentState) {
329
+                    // A direction change has occurred
330
+                    self::finishCurrentSpan($result, false);
331
+                    self::$previousState = self::$currentState;
332
+                    self::$currentState  = $newState;
333
+                    self::beginCurrentSpan($result);
334
+                }
335
+                self::$waitingText .= $currentLetter;
336
+                $workingText = substr($workingText, $currentLen);
337
+                $result .= self::$waitingText;
338
+                self::$waitingText = '';
339
+
340
+                foreach ($openParDirection as $index => $value) {
341
+                    // Since we now know the proper direction, remember it for all waiting opening parentheses
342
+                    if ($value === '?') {
343
+                        $openParDirection[$index] = self::$currentState;
344
+                    }
345
+                }
346
+
347
+                break;
348
+            }
349
+        }
350
+
351
+        // We're done. Finish last <span> if necessary
352
+        if ($numberState) {
353
+            if (self::$waitingText === '') {
354
+                if (self::$currentState === 'RTL') {
355
+                    $result .= WT_UTF8_PDF;
356
+                }
357
+            } else {
358
+                if (self::$currentState === 'RTL') {
359
+                    self::$waitingText .= WT_UTF8_PDF;
360
+                }
361
+            }
362
+        }
363
+        self::finishCurrentSpan($result, true);
364
+
365
+        // Get rid of any waiting text
366
+        if (self::$waitingText != '') {
367
+            if (I18N::direction() === 'rtl' && self::$currentState === 'LTR') {
368
+                $result .= self::$startRTL;
369
+                $result .= self::$waitingText;
370
+                $result .= self::$endRTL;
371
+            } else {
372
+                $result .= self::$startLTR;
373
+                $result .= self::$waitingText;
374
+                $result .= self::$endLTR;
375
+            }
376
+            self::$waitingText = '';
377
+        }
378
+
379
+        // Lastly, do some more cleanups
380
+
381
+        // Move leading RTL numeric strings to following LTR text
382
+        // (this happens when the page direction is RTL and the original text begins with a number and is followed by LTR text)
383
+        while (substr($result, 0, self::$lenStart + 3) === self::$startRTL . WT_UTF8_LRE) {
384
+            $spanEnd = strpos($result, self::$endRTL . self::$startLTR);
385
+            if ($spanEnd === false) {
386
+                break;
387
+            }
388
+            $textSpan = self::stripLrmRlm(substr($result, self::$lenStart + 3, $spanEnd - self::$lenStart - 3));
389
+            if (I18N::scriptDirection(I18N::textScript($textSpan)) === 'rtl') {
390
+                break;
391
+            }
392
+            $result = self::$startLTR . substr($result, self::$lenStart, $spanEnd - self::$lenStart) . substr($result, $spanEnd + self::$lenStart + self::$lenEnd);
393
+            break;
394
+        }
395
+
396
+        // On RTL pages, put trailing "." in RTL numeric strings into its own RTL span
397
+        if (I18N::direction() === 'rtl') {
398
+            $result = str_replace(WT_UTF8_PDF . '.' . self::$endRTL, WT_UTF8_PDF . self::$endRTL . self::$startRTL . '.' . self::$endRTL, $result);
399
+        }
400
+
401
+        // Trim trailing blanks preceding <br> in LTR text
402
+        while (self::$previousState != 'RTL') {
403
+            if (strpos($result, ' <LTRbr>') !== false) {
404
+                $result = str_replace(' <LTRbr>', '<LTRbr>', $result);
405
+                continue;
406
+            }
407
+            if (strpos($result, '&nbsp;<LTRbr>') !== false) {
408
+                $result = str_replace('&nbsp;<LTRbr>', '<LTRbr>', $result);
409
+                continue;
410
+            }
411
+            if (strpos($result, ' <br>') !== false) {
412
+                $result = str_replace(' <br>', '<br>', $result);
413
+                continue;
414
+            }
415
+            if (strpos($result, '&nbsp;<br>') !== false) {
416
+                $result = str_replace('&nbsp;<br>', '<br>', $result);
417
+                continue;
418
+            }
419
+            break; // Neither space nor &nbsp; : we're done
420
+        }
421
+
422
+        // Trim trailing blanks preceding <br> in RTL text
423
+        while (true) {
424
+            if (strpos($result, ' <RTLbr>') !== false) {
425
+                $result = str_replace(' <RTLbr>', '<RTLbr>', $result);
426
+                continue;
427
+            }
428
+            if (strpos($result, '&nbsp;<RTLbr>') !== false) {
429
+                $result = str_replace('&nbsp;<RTLbr>', '<RTLbr>', $result);
430
+                continue;
431
+            }
432
+            break; // Neither space nor &nbsp; : we're done
433
+        }
434
+
435
+        // Convert '<LTRbr>' and '<RTLbr /'
436
+        $result = str_replace(array('<LTRbr>', '<RTLbr>'), array(self::$endLTR . '<br>' . self::$startLTR, self::$endRTL . '<br>' . self::$startRTL), $result);
437
+
438
+        // Include leading indeterminate directional text in whatever follows
439
+        if (substr($result . "\n", 0, self::$lenStart) != self::$startLTR && substr($result . "\n", 0, self::$lenStart) != self::$startRTL && substr($result . "\n", 0, 6) != '<br>') {
440
+            $leadingText = '';
441
+            while (true) {
442
+                if ($result == '') {
443
+                    $result = $leadingText;
444
+                    break;
445
+                }
446
+                if (substr($result . "\n", 0, self::$lenStart) != self::$startLTR && substr($result . "\n", 0, self::$lenStart) != self::$startRTL) {
447
+                    $leadingText .= substr($result, 0, 1);
448
+                    $result = substr($result, 1);
449
+                    continue;
450
+                }
451
+                $result = substr($result, 0, self::$lenStart) . $leadingText . substr($result, self::$lenStart);
452
+                break;
453
+            }
454
+        }
455
+
456
+        // Include solitary "-" and "+" in surrounding RTL text
457
+        $result = str_replace(array(self::$endRTL . self::$startLTR . '-' . self::$endLTR . self::$startRTL, self::$endRTL . self::$startLTR . '-' . self::$endLTR . self::$startRTL), array('-', '+'), $result);
458
+
459
+        // Remove empty spans
460
+        $result = str_replace(array(self::$startLTR . self::$endLTR, self::$startRTL . self::$endRTL), '', $result);
461
+
462
+        // Finally, correct '<LTR>', '</LTR>', '<RTL>', and '</RTL>'
463
+        switch ($direction) {
464
+        case 'BOTH':
465
+        case 'both':
466
+            // LTR text: <span dir="ltr"> text </span>
467
+            // RTL text: <span dir="rtl"> text </span>
468
+            $sLTR = '<span dir="ltr" ' . $class . '>' . $nothing;
469
+            $eLTR = $nothing . '</span>';
470
+            $sRTL = '<span dir="rtl" ' . $class . '>' . $nothing;
471
+            $eRTL = $nothing . '</span>';
472
+            break;
473
+        case 'LTR':
474
+        case 'ltr':
475
+            // LTR text: <span dir="ltr"> text </span>
476
+            // RTL text: text
477
+            $sLTR = '<span dir="ltr" ' . $class . '>' . $nothing;
478
+            $eLTR = $nothing . '</span>';
479
+            $sRTL = '';
480
+            $eRTL = '';
481
+            break;
482
+        case 'RTL':
483
+        case 'rtl':
484
+        default:
485
+            // LTR text: text
486
+            // RTL text: <span dir="rtl"> text </span>
487
+            $sLTR = '';
488
+            $eLTR = '';
489
+            $sRTL = '<span dir="rtl" ' . $class . '>' . $nothing;
490
+            $eRTL = $nothing . '</span>';
491
+            break;
492
+        }
493
+        $result = str_replace(array(self::$startLTR, self::$endLTR, self::$startRTL, self::$endRTL), array($sLTR, $eLTR, $sRTL, $eRTL), $result);
494
+
495
+        return $result;
496
+    }
497
+
498
+    /**
499
+     * Wrap words that have an asterisk suffix in <u> and </u> tags.
500
+     * This should underline starred names to show the preferred name.
501
+     *
502
+     * @param string $textSpan
503
+     * @param string $direction
504
+     *
505
+     * @return string
506
+     */
507
+    public static function starredName($textSpan, $direction) {
508
+        // To avoid a TCPDF bug that mixes up the word order, insert those <u> and </u> tags
509
+        // only when page and span directions are identical.
510
+        if ($direction === strtoupper(I18N::direction())) {
511
+            while (true) {
512
+                $starPos = strpos($textSpan, '*');
513
+                if ($starPos === false) {
514
+                    break;
515
+                }
516
+                $trailingText = substr($textSpan, $starPos + 1);
517
+                $textSpan     = substr($textSpan, 0, $starPos);
518
+                $wordStart    = strrpos($textSpan, ' '); // Find the start of the word
519
+                if ($wordStart !== false) {
520
+                    $leadingText = substr($textSpan, 0, $wordStart + 1);
521
+                    $wordText    = substr($textSpan, $wordStart + 1);
522
+                } else {
523
+                    $leadingText = '';
524
+                    $wordText    = $textSpan;
525
+                }
526
+                $textSpan = $leadingText . '<u>' . $wordText . '</u>' . $trailingText;
527
+            }
528
+            $textSpan = preg_replace('~<span class="starredname">(.*)</span>~', '<u>\1</u>', $textSpan);
529
+            // The &nbsp; is a work-around for a TCPDF bug eating blanks.
530
+            $textSpan = str_replace(array(' <u>', '</u> '), array('&nbsp;<u>', '</u>&nbsp;'), $textSpan);
531
+        } else {
532
+            // Text and page directions differ:  remove the <span> and </span>
533
+            $textSpan = preg_replace('~(.*)\*~', '\1', $textSpan);
534
+            $textSpan = preg_replace('~<span class="starredname">(.*)</span>~', '\1', $textSpan);
535
+        }
536
+
537
+        return $textSpan;
538
+    }
539
+
540
+    /**
541
+     * Get the next character from an input string
542
+     *
543
+     * @param string $text
544
+     * @param string $offset
545
+     *
546
+     * @return array
547
+     */
548
+    public static function getChar($text, $offset) {
549
+
550
+        if ($text == '') {
551
+            return array('letter' => '', 'length' => 0);
552
+        }
553
+
554
+        $char   = substr($text, $offset, 1);
555
+        $length = 1;
556
+        if ((ord($char) & 0xE0) == 0xC0) {
557
+            $length = 2;
558
+        }
559
+        if ((ord($char) & 0xF0) == 0xE0) {
560
+            $length = 3;
561
+        }
562
+        if ((ord($char) & 0xF8) == 0xF0) {
563
+            $length = 4;
564
+        }
565
+        $letter = substr($text, $offset, $length);
566
+
567
+        return array('letter' => $letter, 'length' => $length);
568
+    }
569
+
570
+    /**
571
+     * Insert <br> into current span
572
+     *
573
+     * @param string $result
574
+     */
575
+    public static function breakCurrentSpan(&$result) {
576
+        // Interrupt the current span, insert that <br>, and then continue the current span
577
+        $result .= self::$waitingText;
578
+        self::$waitingText = '';
579
+
580
+        $breakString = '<' . self::$currentState . 'br>';
581
+        $result .= $breakString;
582
+
583
+        return;
584
+    }
585
+
586
+    /**
587
+     * Begin current span
588
+     *
589
+     * @param string $result
590
+     */
591
+    public static function beginCurrentSpan(&$result) {
592
+        if (self::$currentState == 'LTR') {
593
+            $result .= self::$startLTR;
594
+        }
595
+        if (self::$currentState == 'RTL') {
596
+            $result .= self::$startRTL;
597
+        }
598
+
599
+        self::$posSpanStart = strlen($result);
600
+    }
601
+
602
+    /**
603
+     * Finish current span
604
+     *
605
+     * @param string $result
606
+     * @param bool $theEnd
607
+     */
608
+    public static function finishCurrentSpan(&$result, $theEnd = false) {
609
+        $textSpan = substr($result, self::$posSpanStart);
610
+        $result   = substr($result, 0, self::$posSpanStart);
611
+
612
+        // Get rid of empty spans, so that our check for presence of RTL will work
613
+        $result = str_replace(array(self::$startLTR . self::$endLTR, self::$startRTL . self::$endRTL), '', $result);
614
+
615
+        // Look for numeric strings that are times (hh:mm:ss). These have to be separated from surrounding numbers.
616
+        $tempResult = '';
617
+        while ($textSpan != '') {
618
+            $posColon = strpos($textSpan, ':');
619
+            if ($posColon === false) {
620
+                break;
621
+            } // No more possible time strings
622
+            $posLRE = strpos($textSpan, WT_UTF8_LRE);
623
+            if ($posLRE === false) {
624
+                break;
625
+            } // No more numeric strings
626
+            $posPDF = strpos($textSpan, WT_UTF8_PDF, $posLRE);
627
+            if ($posPDF === false) {
628
+                break;
629
+            } // No more numeric strings
630
+
631
+            $tempResult .= substr($textSpan, 0, $posLRE + 3); // Copy everything preceding the numeric string
632
+            $numericString = substr($textSpan, $posLRE + 3, $posPDF - $posLRE); // Separate the entire numeric string
633
+            $textSpan      = substr($textSpan, $posPDF + 3);
634
+            $posColon      = strpos($numericString, ':');
635
+            if ($posColon === false) {
636
+                // Nothing that looks like a time here
637
+                $tempResult .= $numericString;
638
+                continue;
639
+            }
640
+            $posBlank = strpos($numericString . ' ', ' ');
641
+            $posNbsp  = strpos($numericString . '&nbsp;', '&nbsp;');
642
+            if ($posBlank < $posNbsp) {
643
+                $posSeparator    = $posBlank;
644
+                $lengthSeparator = 1;
645
+            } else {
646
+                $posSeparator    = $posNbsp;
647
+                $lengthSeparator = 6;
648
+            }
649
+            if ($posColon > $posSeparator) {
650
+                // We have a time string preceded by a blank: Exclude that blank from the numeric string
651
+                $tempResult .= substr($numericString, 0, $posSeparator);
652
+                $tempResult .= WT_UTF8_PDF;
653
+                $tempResult .= substr($numericString, $posSeparator, $lengthSeparator);
654
+                $tempResult .= WT_UTF8_LRE;
655
+                $numericString = substr($numericString, $posSeparator + $lengthSeparator);
656
+            }
657
+
658
+            $posBlank = strpos($numericString, ' ');
659
+            $posNbsp  = strpos($numericString, '&nbsp;');
660
+            if ($posBlank === false && $posNbsp === false) {
661
+                // The time string isn't followed by a blank
662
+                $textSpan = $numericString . $textSpan;
663
+                continue;
664
+            }
665
+
666
+            // We have a time string followed by a blank: Exclude that blank from the numeric string
667
+            if ($posBlank === false) {
668
+                $posSeparator    = $posNbsp;
669
+                $lengthSeparator = 6;
670
+            } elseif ($posNbsp === false) {
671
+                $posSeparator    = $posBlank;
672
+                $lengthSeparator = 1;
673
+            } elseif ($posBlank < $posNbsp) {
674
+                $posSeparator    = $posBlank;
675
+                $lengthSeparator = 1;
676
+            } else {
677
+                $posSeparator    = $posNbsp;
678
+                $lengthSeparator = 6;
679
+            }
680
+            $tempResult .= substr($numericString, 0, $posSeparator);
681
+            $tempResult .= WT_UTF8_PDF;
682
+            $tempResult .= substr($numericString, $posSeparator, $lengthSeparator);
683
+            $posSeparator += $lengthSeparator;
684
+            $numericString = substr($numericString, $posSeparator);
685
+            $textSpan      = WT_UTF8_LRE . $numericString . $textSpan;
686
+        }
687
+        $textSpan       = $tempResult . $textSpan;
688
+        $trailingBlanks = '';
689
+        $trailingBreaks = '';
690
+
691
+        /* ****************************** LTR text handling ******************************** */
692
+
693
+        if (self::$currentState === 'LTR') {
694
+            // Move trailing numeric strings to the following RTL text. Include any blanks preceding or following the numeric text too.
695
+            if (I18N::direction() === 'rtl' && self::$previousState === 'RTL' && !$theEnd) {
696
+                $trailingString = '';
697
+                $savedSpan      = $textSpan;
698
+                while ($textSpan !== '') {
699
+                    // Look for trailing spaces and tentatively move them
700
+                    if (substr($textSpan, -1) === ' ') {
701
+                        $trailingString = ' ' . $trailingString;
702
+                        $textSpan       = substr($textSpan, 0, -1);
703
+                        continue;
704
+                    }
705
+                    if (substr($textSpan, -6) === '&nbsp;') {
706
+                        $trailingString = '&nbsp;' . $trailingString;
707
+                        $textSpan       = substr($textSpan, 0, -1);
708
+                        continue;
709
+                    }
710
+                    if (substr($textSpan, -3) !== WT_UTF8_PDF) {
711
+                        // There is no trailing numeric string
712
+                        $textSpan = $savedSpan;
713
+                        break;
714
+                    }
715
+
716
+                    // We have a numeric string
717
+                    $posStartNumber = strrpos($textSpan, WT_UTF8_LRE);
718
+                    if ($posStartNumber === false) {
719
+                        $posStartNumber = 0;
720
+                    }
721
+                    $trailingString = substr($textSpan, $posStartNumber, strlen($textSpan) - $posStartNumber) . $trailingString;
722
+                    $textSpan       = substr($textSpan, 0, $posStartNumber);
723
+
724
+                    // Look for more spaces and move them too
725
+                    while ($textSpan != '') {
726
+                        if (substr($textSpan, -1) == ' ') {
727
+                            $trailingString = ' ' . $trailingString;
728
+                            $textSpan       = substr($textSpan, 0, -1);
729
+                            continue;
730
+                        }
731
+                        if (substr($textSpan, -6) == '&nbsp;') {
732
+                            $trailingString = '&nbsp;' . $trailingString;
733
+                            $textSpan       = substr($textSpan, 0, -1);
734
+                            continue;
735
+                        }
736
+                        break;
737
+                    }
738
+
739
+                    self::$waitingText = $trailingString . self::$waitingText;
740
+                    break;
741
+                }
742
+            }
743
+
744
+            $savedSpan = $textSpan;
745
+            // Move any trailing <br>, optionally preceded or followed by blanks, outside this LTR span
746
+            while ($textSpan != '') {
747
+                if (substr($textSpan, -1) == ' ') {
748
+                    $trailingBlanks = ' ' . $trailingBlanks;
749
+                    $textSpan       = substr($textSpan, 0, -1);
750
+                    continue;
751
+                }
752
+                if (substr('......' . $textSpan, -6) == '&nbsp;') {
753
+                    $trailingBlanks = '&nbsp;' . $trailingBlanks;
754
+                    $textSpan       = substr($textSpan, 0, -6);
755
+                    continue;
756
+                }
757
+                break;
758
+            }
759
+            while (substr($textSpan, -9) == '<LTRbr>') {
760
+                $trailingBreaks = '<br>' . $trailingBreaks; // Plain <br> because it’s outside a span
761
+                $textSpan       = substr($textSpan, 0, -9);
762
+            }
763
+            if ($trailingBreaks != '') {
764
+                while ($textSpan != '') {
765
+                    if (substr($textSpan, -1) == ' ') {
766
+                        $trailingBreaks = ' ' . $trailingBreaks;
767
+                        $textSpan       = substr($textSpan, 0, -1);
768
+                        continue;
769
+                    }
770
+                    if (substr('......' . $textSpan, -6) == '&nbsp;') {
771
+                        $trailingBreaks = '&nbsp;' . $trailingBreaks;
772
+                        $textSpan       = substr($textSpan, 0, -6);
773
+                        continue;
774
+                    }
775
+                    break;
776
+                }
777
+                self::$waitingText = $trailingBlanks . self::$waitingText; // Put those trailing blanks inside the following span
778
+            } else {
779
+                $textSpan = $savedSpan;
780
+            }
781
+
782
+            $trailingBlanks      = '';
783
+            $trailingPunctuation = '';
784
+            $trailingID          = '';
785
+            $trailingSeparator   = '';
786
+            $leadingSeparator    = '';
787
+            while (I18N::direction() === 'rtl') {
788
+                if (strpos($result, self::$startRTL) !== false) {
789
+                    // Remove trailing blanks for inclusion in a separate LTR span
790
+                    while ($textSpan != '') {
791
+                        if (substr($textSpan, -1) === ' ') {
792
+                            $trailingBlanks = ' ' . $trailingBlanks;
793
+                            $textSpan       = substr($textSpan, 0, -1);
794
+                            continue;
795
+                        }
796
+                        if (substr($textSpan, -6) === '&nbsp;') {
797
+                            $trailingBlanks = '&nbsp;' . $trailingBlanks;
798
+                            $textSpan       = substr($textSpan, 0, -1);
799
+                            continue;
800
+                        }
801
+                        break;
802
+                    }
803
+
804
+                    // Remove trailing punctuation for inclusion in a separate LTR span
805
+                    if ($textSpan == '') {
806
+                        $trailingChar = "\n";
807
+                    } else {
808
+                        $trailingChar = substr($textSpan, -1);
809
+                    }
810
+                    if (strpos(self::PUNCTUATION, $trailingChar) !== false) {
811
+                        $trailingPunctuation = $trailingChar;
812
+                        $textSpan            = substr($textSpan, 0, -1);
813
+                    }
814
+                }
815
+
816
+                // Remove trailing ID numbers that look like "(xnnn)" for inclusion in a separate LTR span
817
+                while (true) {
818
+                    if (substr($textSpan, -1) != ')') {
819
+                        break;
820
+                    } // There is no trailing ')'
821
+                    $posLeftParen = strrpos($textSpan, '(');
822
+                    if ($posLeftParen === false) {
823
+                        break;
824
+                    } // There is no leading '('
825
+                    $temp = self::stripLrmRlm(substr($textSpan, $posLeftParen)); // Get rid of UTF8 control codes
826
+
827
+                    // If the parenthesized text doesn't look like an ID number,
828
+                    // we don't want to touch it.
829
+                    // This check won’t work if somebody uses ID numbers with an unusual format.
830
+                    $offset    = 1;
831
+                    $charArray = self::getChar($temp, $offset); // Get 1st character of parenthesized text
832
+                    if (strpos(self::NUMBERS, $charArray['letter']) !== false) {
833
+                        break;
834
+                    }
835
+                    $offset += $charArray['length']; // Point at 2nd character of parenthesized text
836
+                    if (strpos(self::NUMBERS, substr($temp, $offset, 1)) === false) {
837
+                        break;
838
+                    }
839
+                    // 1st character of parenthesized text is alpha, 2nd character is a digit; last has to be a digit too
840
+                    if (strpos(self::NUMBERS, substr($temp, -2, 1)) === false) {
841
+                        break;
842
+                    }
843
+
844
+                    $trailingID = substr($textSpan, $posLeftParen);
845
+                    $textSpan   = substr($textSpan, 0, $posLeftParen);
846
+                    break;
847
+                }
848
+
849
+                // Look for " - " or blank preceding the ID number and remove it for inclusion in a separate LTR span
850
+                if ($trailingID != '') {
851
+                    while ($textSpan != '') {
852
+                        if (substr($textSpan, -1) == ' ') {
853
+                            $trailingSeparator = ' ' . $trailingSeparator;
854
+                            $textSpan          = substr($textSpan, 0, -1);
855
+                            continue;
856
+                        }
857
+                        if (substr($textSpan, -6) == '&nbsp;') {
858
+                            $trailingSeparator = '&nbsp;' . $trailingSeparator;
859
+                            $textSpan          = substr($textSpan, 0, -6);
860
+                            continue;
861
+                        }
862
+                        if (substr($textSpan, -1) == '-') {
863
+                            $trailingSeparator = '-' . $trailingSeparator;
864
+                            $textSpan          = substr($textSpan, 0, -1);
865
+                            continue;
866
+                        }
867
+                        break;
868
+                    }
869
+                }
870
+
871
+                // Look for " - " preceding the text and remove it for inclusion in a separate LTR span
872
+                $foundSeparator = false;
873
+                $savedSpan      = $textSpan;
874
+                while ($textSpan != '') {
875
+                    if (substr($textSpan, 0, 1) == ' ') {
876
+                        $leadingSeparator = ' ' . $leadingSeparator;
877
+                        $textSpan         = substr($textSpan, 1);
878
+                        continue;
879
+                    }
880
+                    if (substr($textSpan, 0, 6) == '&nbsp;') {
881
+                        $leadingSeparator = '&nbsp;' . $leadingSeparator;
882
+                        $textSpan         = substr($textSpan, 6);
883
+                        continue;
884
+                    }
885
+                    if (substr($textSpan, 0, 1) == '-') {
886
+                        $leadingSeparator = '-' . $leadingSeparator;
887
+                        $textSpan         = substr($textSpan, 1);
888
+                        $foundSeparator   = true;
889
+                        continue;
890
+                    }
891
+                    break;
892
+                }
893
+                if (!$foundSeparator) {
894
+                    $textSpan         = $savedSpan;
895
+                    $leadingSeparator = '';
896
+                }
897
+                break;
898
+            }
899
+
900
+            // We're done: finish the span
901
+            $textSpan = self::starredName($textSpan, 'LTR'); // Wrap starred name in <u> and </u> tags
902
+            while (true) {
903
+                // Remove blanks that precede <LTRbr>
904
+                if (strpos($textSpan, ' <LTRbr>') !== false) {
905
+                    $textSpan = str_replace(' <LTRbr>', '<LTRbr>', $textSpan);
906
+                    continue;
907
+                }
908
+                if (strpos($textSpan, '&nbsp;<LTRbr>') !== false) {
909
+                    $textSpan = str_replace('&nbsp;<LTRbr>', '<LTRbr>', $textSpan);
910
+                    continue;
911
+                }
912
+                break;
913
+            }
914
+            if ($leadingSeparator != '') {
915
+                $result = $result . self::$startLTR . $leadingSeparator . self::$endLTR;
916
+            }
917
+            $result = $result . $textSpan . self::$endLTR;
918
+            if ($trailingSeparator != '') {
919
+                $result = $result . self::$startLTR . $trailingSeparator . self::$endLTR;
920
+            }
921
+            if ($trailingID != '') {
922
+                $result = $result . self::$startLTR . $trailingID . self::$endLTR;
923
+            }
924
+            if ($trailingPunctuation != '') {
925
+                $result = $result . self::$startLTR . $trailingPunctuation . self::$endLTR;
926
+            }
927
+            if ($trailingBlanks != '') {
928
+                $result = $result . self::$startLTR . $trailingBlanks . self::$endLTR;
929
+            }
930
+        }
931
+
932
+        /* ****************************** RTL text handling ******************************** */
933
+
934
+        if (self::$currentState == 'RTL') {
935
+            $savedSpan = $textSpan;
936
+
937
+            // Move any trailing <br>, optionally followed by blanks, outside this RTL span
938
+            while ($textSpan != '') {
939
+                if (substr($textSpan, -1) == ' ') {
940
+                    $trailingBlanks = ' ' . $trailingBlanks;
941
+                    $textSpan       = substr($textSpan, 0, -1);
942
+                    continue;
943
+                }
944
+                if (substr('......' . $textSpan, -6) == '&nbsp;') {
945
+                    $trailingBlanks = '&nbsp;' . $trailingBlanks;
946
+                    $textSpan       = substr($textSpan, 0, -6);
947
+                    continue;
948
+                }
949
+                break;
950
+            }
951
+            while (substr($textSpan, -9) == '<RTLbr>') {
952
+                $trailingBreaks = '<br>' . $trailingBreaks; // Plain <br> because it’s outside a span
953
+                $textSpan       = substr($textSpan, 0, -9);
954
+            }
955
+            if ($trailingBreaks != '') {
956
+                self::$waitingText = $trailingBlanks . self::$waitingText; // Put those trailing blanks inside the following span
957
+            } else {
958
+                $textSpan = $savedSpan;
959
+            }
960
+
961
+            // Move trailing numeric strings to the following LTR text. Include any blanks preceding or following the numeric text too.
962
+            if (!$theEnd && I18N::direction() !== 'rtl') {
963
+                $trailingString = '';
964
+                $savedSpan      = $textSpan;
965
+                while ($textSpan != '') {
966
+                    // Look for trailing spaces and tentatively move them
967
+                    if (substr($textSpan, -1) === ' ') {
968
+                        $trailingString = ' ' . $trailingString;
969
+                        $textSpan       = substr($textSpan, 0, -1);
970
+                        continue;
971
+                    }
972
+                    if (substr($textSpan, -6) === '&nbsp;') {
973
+                        $trailingString = '&nbsp;' . $trailingString;
974
+                        $textSpan       = substr($textSpan, 0, -1);
975
+                        continue;
976
+                    }
977
+                    if (substr($textSpan, -3) !== WT_UTF8_PDF) {
978
+                        // There is no trailing numeric string
979
+                        $textSpan = $savedSpan;
980
+                        break;
981
+                    }
982
+
983
+                    // We have a numeric string
984
+                    $posStartNumber = strrpos($textSpan, WT_UTF8_LRE);
985
+                    if ($posStartNumber === false) {
986
+                        $posStartNumber = 0;
987
+                    }
988
+                    $trailingString = substr($textSpan, $posStartNumber, strlen($textSpan) - $posStartNumber) . $trailingString;
989
+                    $textSpan       = substr($textSpan, 0, $posStartNumber);
990
+
991
+                    // Look for more spaces and move them too
992
+                    while ($textSpan != '') {
993
+                        if (substr($textSpan, -1) == ' ') {
994
+                            $trailingString = ' ' . $trailingString;
995
+                            $textSpan       = substr($textSpan, 0, -1);
996
+                            continue;
997
+                        }
998
+                        if (substr($textSpan, -6) == '&nbsp;') {
999
+                            $trailingString = '&nbsp;' . $trailingString;
1000
+                            $textSpan       = substr($textSpan, 0, -1);
1001
+                            continue;
1002
+                        }
1003
+                        break;
1004
+                    }
1005
+
1006
+                    self::$waitingText = $trailingString . self::$waitingText;
1007
+                    break;
1008
+                }
1009
+            }
1010
+
1011
+            // Trailing " - " needs to be prefixed to the following span
1012
+            if (!$theEnd && substr('...' . $textSpan, -3) == ' - ') {
1013
+                $textSpan          = substr($textSpan, 0, -3);
1014
+                self::$waitingText = ' - ' . self::$waitingText;
1015
+            }
1016
+
1017
+            while (I18N::direction() === 'rtl') {
1018
+                // Look for " - " preceding <RTLbr> and relocate it to the front of the string
1019
+                $posDashString = strpos($textSpan, ' - <RTLbr>');
1020
+                if ($posDashString === false) {
1021
+                    break;
1022
+                }
1023
+                $posStringStart = strrpos(substr($textSpan, 0, $posDashString), '<RTLbr>');
1024
+                if ($posStringStart === false) {
1025
+                    $posStringStart = 0;
1026
+                } else {
1027
+                    $posStringStart += 9;
1028
+                } // Point to the first char following the last <RTLbr>
1029
+
1030
+                $textSpan = substr($textSpan, 0, $posStringStart) . ' - ' . substr($textSpan, $posStringStart, $posDashString - $posStringStart) . substr($textSpan, $posDashString + 3);
1031
+            }
1032
+
1033
+            // Strip leading spaces from the RTL text
1034
+            $countLeadingSpaces = 0;
1035
+            while ($textSpan != '') {
1036
+                if (substr($textSpan, 0, 1) == ' ') {
1037
+                    $countLeadingSpaces++;
1038
+                    $textSpan = substr($textSpan, 1);
1039
+                    continue;
1040
+                }
1041
+                if (substr($textSpan, 0, 6) == '&nbsp;') {
1042
+                    $countLeadingSpaces++;
1043
+                    $textSpan = substr($textSpan, 6);
1044
+                    continue;
1045
+                }
1046
+                break;
1047
+            }
1048
+
1049
+            // Strip trailing spaces from the RTL text
1050
+            $countTrailingSpaces = 0;
1051
+            while ($textSpan != '') {
1052
+                if (substr($textSpan, -1) == ' ') {
1053
+                    $countTrailingSpaces++;
1054
+                    $textSpan = substr($textSpan, 0, -1);
1055
+                    continue;
1056
+                }
1057
+                if (substr($textSpan, -6) == '&nbsp;') {
1058
+                    $countTrailingSpaces++;
1059
+                    $textSpan = substr($textSpan, 0, -6);
1060
+                    continue;
1061
+                }
1062
+                break;
1063
+            }
1064
+
1065
+            // Look for trailing " -", reverse it, and relocate it to the front of the string
1066
+            if (substr($textSpan, -2) === ' -') {
1067
+                $posDashString  = strlen($textSpan) - 2;
1068
+                $posStringStart = strrpos(substr($textSpan, 0, $posDashString), '<RTLbr>');
1069
+                if ($posStringStart === false) {
1070
+                    $posStringStart = 0;
1071
+                } else {
1072
+                    $posStringStart += 9;
1073
+                } // Point to the first char following the last <RTLbr>
1074
+
1075
+                $textSpan = substr($textSpan, 0, $posStringStart) . '- ' . substr($textSpan, $posStringStart, $posDashString - $posStringStart) . substr($textSpan, $posDashString + 2);
1076
+            }
1077
+
1078
+            if ($countLeadingSpaces != 0) {
1079
+                $newLength = strlen($textSpan) + $countLeadingSpaces;
1080
+                $textSpan  = str_pad($textSpan, $newLength, ' ', (I18N::direction() === 'rtl' ? STR_PAD_LEFT : STR_PAD_RIGHT));
1081
+            }
1082
+            if ($countTrailingSpaces != 0) {
1083
+                if (I18N::direction() === 'ltr') {
1084
+                    if ($trailingBreaks === '') {
1085
+                        // Move trailing RTL spaces to front of following LTR span
1086
+                        $newLength         = strlen(self::$waitingText) + $countTrailingSpaces;
1087
+                        self::$waitingText = str_pad(self::$waitingText, $newLength, ' ', STR_PAD_LEFT);
1088
+                    }
1089
+                } else {
1090
+                    $newLength = strlen($textSpan) + $countTrailingSpaces;
1091
+                    $textSpan  = str_pad($textSpan, $newLength, ' ', STR_PAD_RIGHT);
1092
+                }
1093
+            }
1094
+
1095
+            // We're done: finish the span
1096
+            $textSpan = self::starredName($textSpan, 'RTL'); // Wrap starred name in <u> and </u> tags
1097
+            $result   = $result . $textSpan . self::$endRTL;
1098
+        }
1099
+
1100
+        if (self::$currentState != 'LTR' && self::$currentState != 'RTL') {
1101
+            $result = $result . $textSpan;
1102
+        }
1103
+
1104
+        $result .= $trailingBreaks; // Get rid of any waiting <br>
1105
+
1106
+        return;
1107
+    }
1108
+
1109
+    /**
1110
+     * Wrap text, similar to the PHP wordwrap() function.
1111
+     *
1112
+     * @param string $string
1113
+     * @param int $width
1114
+     * @param string $sep
1115
+     * @param bool $cut
1116
+     *
1117
+     * @return string
1118
+     */
1119
+    public static function utf8WordWrap($string, $width = 75, $sep = "\n", $cut = false) {
1120
+        $out = '';
1121
+        while ($string) {
1122
+            if (mb_strlen($string) <= $width) {
1123
+                // Do not wrap any text that is less than the output area.
1124
+                $out .= $string;
1125
+                $string = '';
1126
+            } else {
1127
+                $sub1 = mb_substr($string, 0, $width + 1);
1128
+                if (mb_substr($string, mb_strlen($sub1) - 1, 1) == ' ') {
1129
+                    // include words that end by a space immediately after the area.
1130
+                    $sub = $sub1;
1131
+                } else {
1132
+                    $sub = mb_substr($string, 0, $width);
1133
+                }
1134
+                $spacepos = strrpos($sub, ' ');
1135
+                if ($spacepos === false) {
1136
+                    // No space on line?
1137
+                    if ($cut) {
1138
+                        $out .= $sub . $sep;
1139
+                        $string = mb_substr($string, mb_strlen($sub));
1140
+                    } else {
1141
+                        $spacepos = strpos($string, ' ');
1142
+                        if ($spacepos === false) {
1143
+                            $out .= $string;
1144
+                            $string = '';
1145
+                        } else {
1146
+                            $out .= substr($string, 0, $spacepos) . $sep;
1147
+                            $string = substr($string, $spacepos + 1);
1148
+                        }
1149
+                    }
1150
+                } else {
1151
+                    // Split at space;
1152
+                    $out .= substr($string, 0, $spacepos) . $sep;
1153
+                    $string = substr($string, $spacepos + 1);
1154
+                }
1155
+            }
1156
+        }
1157
+
1158
+        return $out;
1159
+    }
1160 1160
 }
Please login to merge, or discard this patch.
Switch Indentation   +249 added lines, -249 removed lines patch added patch discarded remove patch
@@ -124,227 +124,227 @@  discard block
 block discarded – undo
124 124
 			$closeParIndex = strpos(self::CLOSE_PARENTHESES, $currentLetter); // Which closing parenthesis is this?
125 125
 
126 126
 			switch ($currentLetter) {
127
-			case '<':
128
-				// Assume this '<' starts an HTML element
129
-				$endPos = strpos($workingText, '>'); // look for the terminating '>'
130
-				if ($endPos === false) {
131
-					$endPos = 0;
132
-				}
133
-				$currentLen += $endPos;
134
-				$element = substr($workingText, 0, $currentLen);
135
-				$temp    = strtolower(substr($element, 0, 3));
136
-				if (strlen($element) < 7 && $temp == '<br') {
137
-					if ($numberState) {
138
-						$numberState = false;
139
-						if (self::$currentState == 'RTL') {
140
-							self::$waitingText .= WT_UTF8_PDF;
141
-						}
142
-					}
143
-					self::breakCurrentSpan($result);
144
-				} elseif (self::$waitingText == '') {
145
-					$result .= $element;
146
-				} else {
147
-					self::$waitingText .= $element;
148
-				}
149
-				$workingText = substr($workingText, $currentLen);
150
-				break;
151
-			case '&':
152
-				// Assume this '&' starts an HTML entity
153
-				$endPos = strpos($workingText, ';'); // look for the terminating ';'
154
-				if ($endPos === false) {
155
-					$endPos = 0;
156
-				}
157
-				$currentLen += $endPos;
158
-				$entity = substr($workingText, 0, $currentLen);
159
-				if (strtolower($entity) == '&nbsp;') {
160
-					$entity .= '&nbsp;'; // Ensure consistent case for this entity
161
-				}
162
-				if (self::$waitingText == '') {
163
-					$result .= $entity;
164
-				} else {
165
-					self::$waitingText .= $entity;
166
-				}
167
-				$workingText = substr($workingText, $currentLen);
168
-				break;
169
-			case '{':
170
-				if (substr($workingText, 1, 1) == '{') {
171
-					// Assume this '{{' starts a TCPDF directive
172
-					$endPos = strpos($workingText, '}}'); // look for the terminating '}}'
173
-					if ($endPos === false) {
174
-						$endPos = 0;
175
-					}
176
-					$currentLen        = $endPos + 2;
177
-					$directive         = substr($workingText, 0, $currentLen);
178
-					$workingText       = substr($workingText, $currentLen);
179
-					$result            = $result . self::$waitingText . $directive;
180
-					self::$waitingText = '';
181
-					break;
182
-				}
183
-			default:
184
-				// Look for strings of numbers with optional leading or trailing + or -
185
-				// and with optional embedded numeric punctuation
186
-				if ($numberState) {
187
-					// If we're inside a numeric string, look for reasons to end it
188
-					$offset    = 0; // Be sure to look at the current character first
189
-					$charArray = self::getChar($workingText . "\n", $offset);
190
-					if (strpos(self::NUMBERS, $charArray['letter']) === false) {
191
-						// This is not a digit. Is it numeric punctuation?
192
-						if (substr($workingText . "\n", $offset, 6) == '&nbsp;') {
193
-							$offset += 6; // This could be numeric punctuation
194
-						} elseif (strpos(self::NUMBER_PUNCTUATION, $charArray['letter']) !== false) {
195
-							$offset += $charArray['length']; // This could be numeric punctuation
196
-						}
197
-						// If the next character is a digit, the current character is numeric punctuation
198
-						$charArray = self::getChar($workingText . "\n", $offset);
199
-						if (strpos(self::NUMBERS, $charArray['letter']) === false) {
200
-							// This is not a digit. End the run of digits and punctuation.
201
-							$numberState = false;
202
-							if (self::$currentState == 'RTL') {
203
-								if (strpos(self::NUMBER_PREFIX, $currentLetter) === false) {
204
-									$currentLetter = WT_UTF8_PDF . $currentLetter;
205
-								} else {
206
-									$currentLetter = $currentLetter . WT_UTF8_PDF; // Include a trailing + or - in the run
207
-								}
208
-							}
209
-						}
210
-					}
211
-				} else {
212
-					// If we're outside a numeric string, look for reasons to start it
213
-					if (strpos(self::NUMBER_PREFIX, $currentLetter) !== false) {
214
-						// This might be a number lead-in
215
-						$offset   = $currentLen;
216
-						$nextChar = substr($workingText . "\n", $offset, 1);
217
-						if (strpos(self::NUMBERS, $nextChar) !== false) {
218
-							$numberState = true; // We found a digit: the lead-in is therefore numeric
219
-							if (self::$currentState == 'RTL') {
220
-								$currentLetter = WT_UTF8_LRE . $currentLetter;
221
-							}
222
-						}
223
-					} elseif (strpos(self::NUMBERS, $currentLetter) !== false) {
224
-						$numberState = true; // The current letter is a digit
225
-						if (self::$currentState == 'RTL') {
226
-							$currentLetter = WT_UTF8_LRE . $currentLetter;
227
-						}
228
-					}
229
-				}
230
-
231
-				// Determine the directionality of the current UTF-8 character
232
-				$newState = self::$currentState;
233
-				while (true) {
234
-					if (I18N::scriptDirection(I18N::textScript($currentLetter)) === 'rtl') {
235
-						if (self::$currentState == '') {
236
-							$newState = 'RTL';
237
-							break;
238
-						}
239
-
240
-						if (self::$currentState == 'RTL') {
241
-							break;
242
-						}
243
-						// Switch to RTL only if this isn't a solitary RTL letter
244
-						$tempText = substr($workingText, $currentLen);
245
-						while ($tempText != '') {
246
-							$nextCharArray = self::getChar($tempText, 0);
247
-							$nextLetter    = $nextCharArray['letter'];
248
-							$nextLen       = $nextCharArray['length'];
249
-							$tempText      = substr($tempText, $nextLen);
250
-
251
-							if (I18N::scriptDirection(I18N::textScript($nextLetter)) === 'rtl') {
252
-								$newState = 'RTL';
253
-								break 2;
254
-							}
255
-
256
-							if (strpos(self::PUNCTUATION, $nextLetter) !== false || strpos(self::OPEN_PARENTHESES, $nextLetter) !== false) {
257
-								$newState = 'RTL';
258
-								break 2;
259
-							}
260
-
261
-							if ($nextLetter === ' ') {
262
-								break;
263
-							}
264
-							$nextLetter .= substr($tempText . "\n", 0, 5);
265
-							if ($nextLetter === '&nbsp;') {
266
-								break;
267
-							}
268
-						}
269
-						// This is a solitary RTL letter : wrap it in UTF8 control codes to force LTR directionality
270
-						$currentLetter = WT_UTF8_LRO . $currentLetter . WT_UTF8_PDF;
271
-						$newState      = 'LTR';
272
-						break;
273
-					}
274
-					if (($currentLen != 1) || ($currentLetter >= 'A' && $currentLetter <= 'Z') || ($currentLetter >= 'a' && $currentLetter <= 'z')) {
275
-						// Since it’s neither Hebrew nor Arabic, this UTF-8 character or ASCII letter must be LTR
276
-						$newState = 'LTR';
277
-						break;
278
-					}
279
-					if ($closeParIndex !== false) {
280
-						// This closing parenthesis has to inherit the matching opening parenthesis' directionality
281
-						if (!empty($openParDirection[$closeParIndex]) && $openParDirection[$closeParIndex] != '?') {
282
-							$newState = $openParDirection[$closeParIndex];
283
-						}
284
-						$openParDirection[$closeParIndex] = '';
285
-						break;
286
-					}
287
-					if ($openParIndex !== false) {
288
-						// Opening parentheses always inherit the following directionality
289
-						self::$waitingText .= $currentLetter;
290
-						$workingText = substr($workingText, $currentLen);
291
-						while (true) {
292
-							if ($workingText === '') {
293
-								break;
294
-							}
295
-							if (substr($workingText, 0, 1) === ' ') {
296
-								// Spaces following this left parenthesis inherit the following directionality too
297
-								self::$waitingText .= ' ';
298
-								$workingText = substr($workingText, 1);
299
-								continue;
300
-							}
301
-							if (substr($workingText, 0, 6) === '&nbsp;') {
302
-								// Spaces following this left parenthesis inherit the following directionality too
303
-								self::$waitingText .= '&nbsp;';
304
-								$workingText = substr($workingText, 6);
305
-								continue;
306
-							}
307
-							break;
308
-						}
309
-						$openParDirection[$openParIndex] = '?';
310
-						break 2; // double break because we're waiting for more information
311
-					}
312
-
313
-					// We have a digit or a "normal" special character.
314
-					//
315
-					// When this character is not at the start of the input string, it inherits the preceding directionality;
316
-					// at the start of the input string, it assumes the following directionality.
317
-					//
318
-					// Exceptions to this rule will be handled later during final clean-up.
319
-					//
320
-					self::$waitingText .= $currentLetter;
321
-					$workingText = substr($workingText, $currentLen);
322
-					if (self::$currentState != '') {
323
-						$result .= self::$waitingText;
324
-						self::$waitingText = '';
325
-					}
326
-					break 2; // double break because we're waiting for more information
327
-				}
328
-				if ($newState != self::$currentState) {
329
-					// A direction change has occurred
330
-					self::finishCurrentSpan($result, false);
331
-					self::$previousState = self::$currentState;
332
-					self::$currentState  = $newState;
333
-					self::beginCurrentSpan($result);
334
-				}
335
-				self::$waitingText .= $currentLetter;
336
-				$workingText = substr($workingText, $currentLen);
337
-				$result .= self::$waitingText;
338
-				self::$waitingText = '';
339
-
340
-				foreach ($openParDirection as $index => $value) {
341
-					// Since we now know the proper direction, remember it for all waiting opening parentheses
342
-					if ($value === '?') {
343
-						$openParDirection[$index] = self::$currentState;
344
-					}
345
-				}
346
-
347
-				break;
127
+			    case '<':
128
+				    // Assume this '<' starts an HTML element
129
+				    $endPos = strpos($workingText, '>'); // look for the terminating '>'
130
+				    if ($endPos === false) {
131
+					    $endPos = 0;
132
+				    }
133
+				    $currentLen += $endPos;
134
+				    $element = substr($workingText, 0, $currentLen);
135
+				    $temp    = strtolower(substr($element, 0, 3));
136
+				    if (strlen($element) < 7 && $temp == '<br') {
137
+					    if ($numberState) {
138
+						    $numberState = false;
139
+						    if (self::$currentState == 'RTL') {
140
+							    self::$waitingText .= WT_UTF8_PDF;
141
+						    }
142
+					    }
143
+					    self::breakCurrentSpan($result);
144
+				    } elseif (self::$waitingText == '') {
145
+					    $result .= $element;
146
+				    } else {
147
+					    self::$waitingText .= $element;
148
+				    }
149
+				    $workingText = substr($workingText, $currentLen);
150
+				    break;
151
+			    case '&':
152
+				    // Assume this '&' starts an HTML entity
153
+				    $endPos = strpos($workingText, ';'); // look for the terminating ';'
154
+				    if ($endPos === false) {
155
+					    $endPos = 0;
156
+				    }
157
+				    $currentLen += $endPos;
158
+				    $entity = substr($workingText, 0, $currentLen);
159
+				    if (strtolower($entity) == '&nbsp;') {
160
+					    $entity .= '&nbsp;'; // Ensure consistent case for this entity
161
+				    }
162
+				    if (self::$waitingText == '') {
163
+					    $result .= $entity;
164
+				    } else {
165
+					    self::$waitingText .= $entity;
166
+				    }
167
+				    $workingText = substr($workingText, $currentLen);
168
+				    break;
169
+			    case '{':
170
+				    if (substr($workingText, 1, 1) == '{') {
171
+					    // Assume this '{{' starts a TCPDF directive
172
+					    $endPos = strpos($workingText, '}}'); // look for the terminating '}}'
173
+					    if ($endPos === false) {
174
+						    $endPos = 0;
175
+					    }
176
+					    $currentLen        = $endPos + 2;
177
+					    $directive         = substr($workingText, 0, $currentLen);
178
+					    $workingText       = substr($workingText, $currentLen);
179
+					    $result            = $result . self::$waitingText . $directive;
180
+					    self::$waitingText = '';
181
+					    break;
182
+				    }
183
+			    default:
184
+				    // Look for strings of numbers with optional leading or trailing + or -
185
+				    // and with optional embedded numeric punctuation
186
+				    if ($numberState) {
187
+					    // If we're inside a numeric string, look for reasons to end it
188
+					    $offset    = 0; // Be sure to look at the current character first
189
+					    $charArray = self::getChar($workingText . "\n", $offset);
190
+					    if (strpos(self::NUMBERS, $charArray['letter']) === false) {
191
+						    // This is not a digit. Is it numeric punctuation?
192
+						    if (substr($workingText . "\n", $offset, 6) == '&nbsp;') {
193
+							    $offset += 6; // This could be numeric punctuation
194
+						    } elseif (strpos(self::NUMBER_PUNCTUATION, $charArray['letter']) !== false) {
195
+							    $offset += $charArray['length']; // This could be numeric punctuation
196
+						    }
197
+						    // If the next character is a digit, the current character is numeric punctuation
198
+						    $charArray = self::getChar($workingText . "\n", $offset);
199
+						    if (strpos(self::NUMBERS, $charArray['letter']) === false) {
200
+							    // This is not a digit. End the run of digits and punctuation.
201
+							    $numberState = false;
202
+							    if (self::$currentState == 'RTL') {
203
+								    if (strpos(self::NUMBER_PREFIX, $currentLetter) === false) {
204
+									    $currentLetter = WT_UTF8_PDF . $currentLetter;
205
+								    } else {
206
+									    $currentLetter = $currentLetter . WT_UTF8_PDF; // Include a trailing + or - in the run
207
+								    }
208
+							    }
209
+						    }
210
+					    }
211
+				    } else {
212
+					    // If we're outside a numeric string, look for reasons to start it
213
+					    if (strpos(self::NUMBER_PREFIX, $currentLetter) !== false) {
214
+						    // This might be a number lead-in
215
+						    $offset   = $currentLen;
216
+						    $nextChar = substr($workingText . "\n", $offset, 1);
217
+						    if (strpos(self::NUMBERS, $nextChar) !== false) {
218
+							    $numberState = true; // We found a digit: the lead-in is therefore numeric
219
+							    if (self::$currentState == 'RTL') {
220
+								    $currentLetter = WT_UTF8_LRE . $currentLetter;
221
+							    }
222
+						    }
223
+					    } elseif (strpos(self::NUMBERS, $currentLetter) !== false) {
224
+						    $numberState = true; // The current letter is a digit
225
+						    if (self::$currentState == 'RTL') {
226
+							    $currentLetter = WT_UTF8_LRE . $currentLetter;
227
+						    }
228
+					    }
229
+				    }
230
+
231
+				    // Determine the directionality of the current UTF-8 character
232
+				    $newState = self::$currentState;
233
+				    while (true) {
234
+					    if (I18N::scriptDirection(I18N::textScript($currentLetter)) === 'rtl') {
235
+						    if (self::$currentState == '') {
236
+							    $newState = 'RTL';
237
+							    break;
238
+						    }
239
+
240
+						    if (self::$currentState == 'RTL') {
241
+							    break;
242
+						    }
243
+						    // Switch to RTL only if this isn't a solitary RTL letter
244
+						    $tempText = substr($workingText, $currentLen);
245
+						    while ($tempText != '') {
246
+							    $nextCharArray = self::getChar($tempText, 0);
247
+							    $nextLetter    = $nextCharArray['letter'];
248
+							    $nextLen       = $nextCharArray['length'];
249
+							    $tempText      = substr($tempText, $nextLen);
250
+
251
+							    if (I18N::scriptDirection(I18N::textScript($nextLetter)) === 'rtl') {
252
+								    $newState = 'RTL';
253
+								    break 2;
254
+							    }
255
+
256
+							    if (strpos(self::PUNCTUATION, $nextLetter) !== false || strpos(self::OPEN_PARENTHESES, $nextLetter) !== false) {
257
+								    $newState = 'RTL';
258
+								    break 2;
259
+							    }
260
+
261
+							    if ($nextLetter === ' ') {
262
+								    break;
263
+							    }
264
+							    $nextLetter .= substr($tempText . "\n", 0, 5);
265
+							    if ($nextLetter === '&nbsp;') {
266
+								    break;
267
+							    }
268
+						    }
269
+						    // This is a solitary RTL letter : wrap it in UTF8 control codes to force LTR directionality
270
+						    $currentLetter = WT_UTF8_LRO . $currentLetter . WT_UTF8_PDF;
271
+						    $newState      = 'LTR';
272
+						    break;
273
+					    }
274
+					    if (($currentLen != 1) || ($currentLetter >= 'A' && $currentLetter <= 'Z') || ($currentLetter >= 'a' && $currentLetter <= 'z')) {
275
+						    // Since it’s neither Hebrew nor Arabic, this UTF-8 character or ASCII letter must be LTR
276
+						    $newState = 'LTR';
277
+						    break;
278
+					    }
279
+					    if ($closeParIndex !== false) {
280
+						    // This closing parenthesis has to inherit the matching opening parenthesis' directionality
281
+						    if (!empty($openParDirection[$closeParIndex]) && $openParDirection[$closeParIndex] != '?') {
282
+							    $newState = $openParDirection[$closeParIndex];
283
+						    }
284
+						    $openParDirection[$closeParIndex] = '';
285
+						    break;
286
+					    }
287
+					    if ($openParIndex !== false) {
288
+						    // Opening parentheses always inherit the following directionality
289
+						    self::$waitingText .= $currentLetter;
290
+						    $workingText = substr($workingText, $currentLen);
291
+						    while (true) {
292
+							    if ($workingText === '') {
293
+								    break;
294
+							    }
295
+							    if (substr($workingText, 0, 1) === ' ') {
296
+								    // Spaces following this left parenthesis inherit the following directionality too
297
+								    self::$waitingText .= ' ';
298
+								    $workingText = substr($workingText, 1);
299
+								    continue;
300
+							    }
301
+							    if (substr($workingText, 0, 6) === '&nbsp;') {
302
+								    // Spaces following this left parenthesis inherit the following directionality too
303
+								    self::$waitingText .= '&nbsp;';
304
+								    $workingText = substr($workingText, 6);
305
+								    continue;
306
+							    }
307
+							    break;
308
+						    }
309
+						    $openParDirection[$openParIndex] = '?';
310
+						    break 2; // double break because we're waiting for more information
311
+					    }
312
+
313
+					    // We have a digit or a "normal" special character.
314
+					    //
315
+					    // When this character is not at the start of the input string, it inherits the preceding directionality;
316
+					    // at the start of the input string, it assumes the following directionality.
317
+					    //
318
+					    // Exceptions to this rule will be handled later during final clean-up.
319
+					    //
320
+					    self::$waitingText .= $currentLetter;
321
+					    $workingText = substr($workingText, $currentLen);
322
+					    if (self::$currentState != '') {
323
+						    $result .= self::$waitingText;
324
+						    self::$waitingText = '';
325
+					    }
326
+					    break 2; // double break because we're waiting for more information
327
+				    }
328
+				    if ($newState != self::$currentState) {
329
+					    // A direction change has occurred
330
+					    self::finishCurrentSpan($result, false);
331
+					    self::$previousState = self::$currentState;
332
+					    self::$currentState  = $newState;
333
+					    self::beginCurrentSpan($result);
334
+				    }
335
+				    self::$waitingText .= $currentLetter;
336
+				    $workingText = substr($workingText, $currentLen);
337
+				    $result .= self::$waitingText;
338
+				    self::$waitingText = '';
339
+
340
+				    foreach ($openParDirection as $index => $value) {
341
+					    // Since we now know the proper direction, remember it for all waiting opening parentheses
342
+					    if ($value === '?') {
343
+						    $openParDirection[$index] = self::$currentState;
344
+					    }
345
+				    }
346
+
347
+				    break;
348 348
 			}
349 349
 		}
350 350
 
@@ -461,34 +461,34 @@  discard block
 block discarded – undo
461 461
 
462 462
 		// Finally, correct '<LTR>', '</LTR>', '<RTL>', and '</RTL>'
463 463
 		switch ($direction) {
464
-		case 'BOTH':
465
-		case 'both':
466
-			// LTR text: <span dir="ltr"> text </span>
467
-			// RTL text: <span dir="rtl"> text </span>
468
-			$sLTR = '<span dir="ltr" ' . $class . '>' . $nothing;
469
-			$eLTR = $nothing . '</span>';
470
-			$sRTL = '<span dir="rtl" ' . $class . '>' . $nothing;
471
-			$eRTL = $nothing . '</span>';
472
-			break;
473
-		case 'LTR':
474
-		case 'ltr':
475
-			// LTR text: <span dir="ltr"> text </span>
476
-			// RTL text: text
477
-			$sLTR = '<span dir="ltr" ' . $class . '>' . $nothing;
478
-			$eLTR = $nothing . '</span>';
479
-			$sRTL = '';
480
-			$eRTL = '';
481
-			break;
482
-		case 'RTL':
483
-		case 'rtl':
484
-		default:
485
-			// LTR text: text
486
-			// RTL text: <span dir="rtl"> text </span>
487
-			$sLTR = '';
488
-			$eLTR = '';
489
-			$sRTL = '<span dir="rtl" ' . $class . '>' . $nothing;
490
-			$eRTL = $nothing . '</span>';
491
-			break;
464
+		    case 'BOTH':
465
+		    case 'both':
466
+			    // LTR text: <span dir="ltr"> text </span>
467
+			    // RTL text: <span dir="rtl"> text </span>
468
+			    $sLTR = '<span dir="ltr" ' . $class . '>' . $nothing;
469
+			    $eLTR = $nothing . '</span>';
470
+			    $sRTL = '<span dir="rtl" ' . $class . '>' . $nothing;
471
+			    $eRTL = $nothing . '</span>';
472
+			    break;
473
+		    case 'LTR':
474
+		    case 'ltr':
475
+			    // LTR text: <span dir="ltr"> text </span>
476
+			    // RTL text: text
477
+			    $sLTR = '<span dir="ltr" ' . $class . '>' . $nothing;
478
+			    $eLTR = $nothing . '</span>';
479
+			    $sRTL = '';
480
+			    $eRTL = '';
481
+			    break;
482
+		    case 'RTL':
483
+		    case 'rtl':
484
+		    default:
485
+			    // LTR text: text
486
+			    // RTL text: <span dir="rtl"> text </span>
487
+			    $sLTR = '';
488
+			    $eLTR = '';
489
+			    $sRTL = '<span dir="rtl" ' . $class . '>' . $nothing;
490
+			    $eRTL = $nothing . '</span>';
491
+			    break;
492 492
 		}
493 493
 		$result = str_replace(array(self::$startLTR, self::$endLTR, self::$startRTL, self::$endRTL), array($sLTR, $eLTR, $sRTL, $eRTL), $result);
494 494
 
Please login to merge, or discard this patch.
Braces   +18 added lines, -9 removed lines patch added patch discarded remove patch
@@ -20,7 +20,8 @@  discard block
 block discarded – undo
20 20
 /**
21 21
  * RTL Functions for use in the PDF/HTML reports
22 22
  */
23
-class FunctionsRtl {
23
+class FunctionsRtl
24
+{
24 25
 	const OPEN_PARENTHESES = '([{';
25 26
 
26 27
 	const CLOSE_PARENTHESES = ')]}';
@@ -72,7 +73,8 @@  discard block
 block discarded – undo
72 73
 	 *
73 74
 	 * @return string The input string, with &lrm; and &rlm; stripped
74 75
 	 */
75
-	public static function stripLrmRlm($inputText) {
76
+	public static function stripLrmRlm($inputText)
77
+	{
76 78
 		return str_replace(array(WT_UTF8_LRM, WT_UTF8_RLM, WT_UTF8_LRO, WT_UTF8_RLO, WT_UTF8_LRE, WT_UTF8_RLE, WT_UTF8_PDF, "&lrm;", "&rlm;", "&LRM;", "&RLM;"), "", $inputText);
77 79
 	}
78 80
 
@@ -86,7 +88,8 @@  discard block
 block discarded – undo
86 88
 	 *
87 89
 	 * @return string The string with all texts encapsulated as required
88 90
 	 */
89
-	public static function spanLtrRtl($inputText, $direction = 'BOTH', $class = '') {
91
+	public static function spanLtrRtl($inputText, $direction = 'BOTH', $class = '')
92
+	{
90 93
 		if ($inputText == '') {
91 94
 			// Nothing to do
92 95
 			return '';
@@ -504,7 +507,8 @@  discard block
 block discarded – undo
504 507
 	 *
505 508
 	 * @return string
506 509
 	 */
507
-	public static function starredName($textSpan, $direction) {
510
+	public static function starredName($textSpan, $direction)
511
+	{
508 512
 		// To avoid a TCPDF bug that mixes up the word order, insert those <u> and </u> tags
509 513
 		// only when page and span directions are identical.
510 514
 		if ($direction === strtoupper(I18N::direction())) {
@@ -545,7 +549,8 @@  discard block
 block discarded – undo
545 549
 	 *
546 550
 	 * @return array
547 551
 	 */
548
-	public static function getChar($text, $offset) {
552
+	public static function getChar($text, $offset)
553
+	{
549 554
 
550 555
 		if ($text == '') {
551 556
 			return array('letter' => '', 'length' => 0);
@@ -572,7 +577,8 @@  discard block
 block discarded – undo
572 577
 	 *
573 578
 	 * @param string $result
574 579
 	 */
575
-	public static function breakCurrentSpan(&$result) {
580
+	public static function breakCurrentSpan(&$result)
581
+	{
576 582
 		// Interrupt the current span, insert that <br>, and then continue the current span
577 583
 		$result .= self::$waitingText;
578 584
 		self::$waitingText = '';
@@ -588,7 +594,8 @@  discard block
 block discarded – undo
588 594
 	 *
589 595
 	 * @param string $result
590 596
 	 */
591
-	public static function beginCurrentSpan(&$result) {
597
+	public static function beginCurrentSpan(&$result)
598
+	{
592 599
 		if (self::$currentState == 'LTR') {
593 600
 			$result .= self::$startLTR;
594 601
 		}
@@ -605,7 +612,8 @@  discard block
 block discarded – undo
605 612
 	 * @param string $result
606 613
 	 * @param bool $theEnd
607 614
 	 */
608
-	public static function finishCurrentSpan(&$result, $theEnd = false) {
615
+	public static function finishCurrentSpan(&$result, $theEnd = false)
616
+	{
609 617
 		$textSpan = substr($result, self::$posSpanStart);
610 618
 		$result   = substr($result, 0, self::$posSpanStart);
611 619
 
@@ -1116,7 +1124,8 @@  discard block
 block discarded – undo
1116 1124
 	 *
1117 1125
 	 * @return string
1118 1126
 	 */
1119
-	public static function utf8WordWrap($string, $width = 75, $sep = "\n", $cut = false) {
1127
+	public static function utf8WordWrap($string, $width = 75, $sep = "\n", $cut = false)
1128
+	{
1120 1129
 		$out = '';
1121 1130
 		while ($string) {
1122 1131
 			if (mb_strlen($string) <= $width) {
Please login to merge, or discard this patch.
app/Functions/FunctionsPrint.php 3 patches
Indentation   +750 added lines, -750 removed lines patch added patch discarded remove patch
@@ -40,607 +40,607 @@  discard block
 block discarded – undo
40 40
  * Class FunctionsPrint - common functions
41 41
  */
42 42
 class FunctionsPrint {
43
-	/**
44
-	 * print the information for an individual chart box
45
-	 *
46
-	 * find and print a given individuals information for a pedigree chart
47
-	 *
48
-	 * @param Individual $person The person to print
49
-	 * @param int $show_full The style to print the box in, 0 for smaller boxes, 1 for larger boxes
50
-	 */
51
-	public static function printPedigreePerson(Individual $person = null, $show_full = 1) {
52
-
53
-		switch ($show_full) {
54
-		case 0:
55
-			if ($person) {
56
-				echo Theme::theme()->individualBoxSmall($person);
57
-			} else {
58
-				echo Theme::theme()->individualBoxSmallEmpty();
59
-			}
60
-			break;
61
-		case 1:
62
-			if ($person) {
63
-				echo Theme::theme()->individualBox($person);
64
-			} else {
65
-				echo Theme::theme()->individualBoxEmpty();
66
-			}
67
-			break;
68
-		}
69
-	}
70
-
71
-	/**
72
-	 * print a note record
73
-	 *
74
-	 * @param string $text
75
-	 * @param int $nlevel the level of the note record
76
-	 * @param string $nrec the note record to print
77
-	 * @param bool $textOnly Don't print the "Note: " introduction
78
-	 *
79
-	 * @return string
80
-	 */
81
-	public static function printNoteRecord($text, $nlevel, $nrec, $textOnly = false) {
82
-		global $WT_TREE;
83
-
84
-		$text .= Functions::getCont($nlevel, $nrec);
85
-
86
-		// Check if shared note (we have already checked that it exists)
87
-		if (preg_match('/^0 @(' . WT_REGEX_XREF . ')@ NOTE/', $nrec, $match)) {
88
-			$note  = Note::getInstance($match[1], $WT_TREE);
89
-			$label = 'SHARED_NOTE';
90
-			// If Census assistant installed, allow it to format the note
91
-			if (Module::getModuleByName('GEDFact_assistant')) {
92
-				$html = CensusAssistantModule::formatCensusNote($note);
93
-			} else {
94
-				$html = Filter::formatText($note->getNote(), $WT_TREE);
95
-			}
96
-		} else {
97
-			$note  = null;
98
-			$label = 'NOTE';
99
-			$html  = Filter::formatText($text, $WT_TREE);
100
-		}
101
-
102
-		if ($textOnly) {
103
-			return strip_tags($text);
104
-		}
105
-
106
-		if (strpos($text, "\n") === false) {
107
-			// A one-line note? strip the block-level tags, so it displays inline
108
-			return GedcomTag::getLabelValue($label, strip_tags($html, '<a><strong><em>'));
109
-		} elseif ($WT_TREE->getPreference('EXPAND_NOTES')) {
110
-			// A multi-line note, and we're expanding notes by default
111
-			return GedcomTag::getLabelValue($label, $html);
112
-		} else {
113
-			// A multi-line note, with an expand/collapse option
114
-			$element_id = Uuid::uuid4();
115
-			// NOTE: class "note-details" is (currently) used only by some third-party themes
116
-			if ($note) {
117
-				$first_line = '<a href="' . $note->getHtmlUrl() . '">' . $note->getFullName() . '</a>';
118
-			} else {
119
-				list($text) = explode("\n", strip_tags($html));
120
-				$first_line = strlen($text) > 100 ? mb_substr($text, 0, 100) . I18N::translate('…') : $text;
121
-			}
122
-
123
-			return
124
-				'<div class="fact_NOTE"><span class="label">' .
125
-				'<a href="#" onclick="expand_layer(\'' . $element_id . '\'); return false;"><i id="' . $element_id . '_img" class="icon-plus"></i></a> ' . GedcomTag::getLabel($label) . ':</span> ' . '<span id="' . $element_id . '-alt">' . $first_line . '</span>' .
126
-				'</div>' .
127
-				'<div class="note-details" id="' . $element_id . '" style="display:none">' . $html . '</div>';
128
-		}
129
-	}
130
-
131
-	/**
132
-	 * Print all of the notes in this fact record
133
-	 *
134
-	 * @param string $factrec The factrecord to print the notes from
135
-	 * @param int $level The level of the factrecord
136
-	 * @param bool $textOnly Don't print the "Note: " introduction
137
-	 *
138
-	 * @return string HTML
139
-	 */
140
-	public static function printFactNotes($factrec, $level, $textOnly = false) {
141
-		global $WT_TREE;
142
-
143
-		$data          = '';
144
-		$previous_spos = 0;
145
-		$nlevel        = $level + 1;
146
-		$ct            = preg_match_all("/$level NOTE (.*)/", $factrec, $match, PREG_SET_ORDER);
147
-		for ($j = 0; $j < $ct; $j++) {
148
-			$spos1 = strpos($factrec, $match[$j][0], $previous_spos);
149
-			$spos2 = strpos($factrec . "\n$level", "\n$level", $spos1 + 1);
150
-			if (!$spos2) {
151
-				$spos2 = strlen($factrec);
152
-			}
153
-			$previous_spos = $spos2;
154
-			$nrec          = substr($factrec, $spos1, $spos2 - $spos1);
155
-			if (!isset($match[$j][1])) {
156
-				$match[$j][1] = '';
157
-			}
158
-			if (!preg_match('/@(.*)@/', $match[$j][1], $nmatch)) {
159
-				$data .= self::printNoteRecord($match[$j][1], $nlevel, $nrec, $textOnly);
160
-			} else {
161
-				$note = Note::getInstance($nmatch[1], $WT_TREE);
162
-				if ($note) {
163
-					if ($note->canShow()) {
164
-						$noterec = $note->getGedcom();
165
-						$nt      = preg_match("/0 @$nmatch[1]@ NOTE (.*)/", $noterec, $n1match);
166
-						$data .= self::printNoteRecord(($nt > 0) ? $n1match[1] : "", 1, $noterec, $textOnly);
167
-						if (!$textOnly) {
168
-							if (strpos($noterec, '1 SOUR') !== false) {
169
-								$data .= FunctionsPrintFacts::printFactSources($noterec, 1);
170
-							}
171
-						}
172
-					}
173
-				} else {
174
-					$data = '<div class="fact_NOTE"><span class="label">' . I18N::translate('Note') . '</span>: <span class="field error">' . $nmatch[1] . '</span></div>';
175
-				}
176
-			}
177
-			if (!$textOnly) {
178
-				if (strpos($factrec, "$nlevel SOUR") !== false) {
179
-					$data .= "<div class=\"indent\">";
180
-					$data .= FunctionsPrintFacts::printFactSources($nrec, $nlevel);
181
-					$data .= "</div>";
182
-				}
183
-			}
184
-		}
185
-
186
-		return $data;
187
-	}
188
-
189
-	/**
190
-	 * Print a link for a popup help window.
191
-	 *
192
-	 * @param string $help_topic
193
-	 * @param string $module
194
-	 *
195
-	 * @return string
196
-	 */
197
-	public static function helpLink($help_topic, $module = '') {
198
-		return '<span class="icon-help" onclick="helpDialog(\'' . $help_topic . '\',\'' . $module . '\'); return false;">&nbsp;</span>';
199
-	}
200
-
201
-	/**
202
-	 * Print an external help link to the wiki site.
203
-	 *
204
-	 * @deprecated - nothing should be so complicated that it needs lengthy instructions!
205
-	 *
206
-	 * @param string $topic
207
-	 *
208
-	 * @return string
209
-	 */
210
-	public static function wikiHelpLink($topic) {
211
-		return '<a class="help icon-wiki" href="' . WT_WEBTREES_WIKI . $topic . '" title="' . I18N::translate('webtrees wiki') . '"></a>';
212
-	}
213
-
214
-	/**
215
-	 * When a user has searched for text, highlight any matches in
216
-	 * the displayed string.
217
-	 *
218
-	 * @param string $string
219
-	 *
220
-	 * @return string
221
-	 */
222
-	public static function highlightSearchHits($string) {
223
-		global $controller;
224
-
225
-		if ($controller instanceof SearchController && $controller->query) {
226
-			// TODO: when a search contains multiple words, we search independently.
227
-			// e.g. searching for "FOO BAR" will find records containing both FOO and BAR.
228
-			// However, we only highlight the original search string, not the search terms.
229
-			// The controller needs to provide its "query_terms" array.
230
-			$regex = array();
231
-			foreach (array($controller->query) as $search_term) {
232
-				$regex[] = preg_quote($search_term, '/');
233
-			}
234
-			// Match these strings, provided they do not occur inside HTML tags
235
-			$regex = '(' . implode('|', $regex) . ')(?![^<]*>)';
236
-
237
-			return preg_replace('/' . $regex . '/i', '<span class="search_hit">$1</span>', $string);
238
-		} else {
239
-			return $string;
240
-		}
241
-	}
242
-
243
-	/**
244
-	 * Format age of parents in HTML
245
-	 *
246
-	 * @param Individual $person child
247
-	 * @param Date $birth_date
248
-	 *
249
-	 * @return string HTML
250
-	 */
251
-	public static function formatParentsAges(Individual $person, Date $birth_date) {
252
-		$html     = '';
253
-		$families = $person->getChildFamilies();
254
-		// Multiple sets of parents (e.g. adoption) cause complications, so ignore.
255
-		if ($birth_date->isOK() && count($families) == 1) {
256
-			$family = current($families);
257
-			foreach ($family->getSpouses() as $parent) {
258
-				if ($parent->getBirthDate()->isOK()) {
259
-					$sex      = $parent->getSexImage();
260
-					$age      = Date::getAge($parent->getBirthDate(), $birth_date, 2);
261
-					$deatdate = $parent->getDeathDate();
262
-					switch ($parent->getSex()) {
263
-					case 'F':
264
-						// Highlight mothers who die in childbirth or shortly afterwards
265
-						if ($deatdate->isOK() && $deatdate->maximumJulianDay() < $birth_date->minimumJulianDay() + 90) {
266
-							$html .= ' <span title="' . GedcomTag::getLabel('_DEAT_PARE', $parent) . '" class="parentdeath">' . $sex . $age . '</span>';
267
-						} else {
268
-							$html .= ' <span title="' . I18N::translate('Mother’s age') . '">' . $sex . $age . '</span>';
269
-						}
270
-						break;
271
-					case 'M':
272
-						// Highlight fathers who die before the birth
273
-						if ($deatdate->isOK() && $deatdate->maximumJulianDay() < $birth_date->minimumJulianDay()) {
274
-							$html .= ' <span title="' . GedcomTag::getLabel('_DEAT_PARE', $parent) . '" class="parentdeath">' . $sex . $age . '</span>';
275
-						} else {
276
-							$html .= ' <span title="' . I18N::translate('Father’s age') . '">' . $sex . $age . '</span>';
277
-						}
278
-						break;
279
-					default:
280
-						$html .= ' <span title="' . I18N::translate('Parent’s age') . '">' . $sex . $age . '</span>';
281
-						break;
282
-					}
283
-				}
284
-			}
285
-			if ($html) {
286
-				$html = '<span class="age">' . $html . '</span>';
287
-			}
288
-		}
289
-
290
-		return $html;
291
-	}
292
-
293
-	/**
294
-	 * Print fact DATE/TIME
295
-	 *
296
-	 * @param Fact $event event containing the date/age
297
-	 * @param GedcomRecord $record the person (or couple) whose ages should be printed
298
-	 * @param bool $anchor option to print a link to calendar
299
-	 * @param bool $time option to print TIME value
300
-	 *
301
-	 * @return string
302
-	 */
303
-	public static function formatFactDate(Fact $event, GedcomRecord $record, $anchor, $time) {
304
-		global $pid;
305
-
306
-		$factrec = $event->getGedcom();
307
-		$html    = '';
308
-		// Recorded age
309
-		if (preg_match('/\n2 AGE (.+)/', $factrec, $match)) {
310
-			$fact_age = $match[1];
311
-		} else {
312
-			$fact_age = '';
313
-		}
314
-		if (preg_match('/\n2 HUSB\n3 AGE (.+)/', $factrec, $match)) {
315
-			$husb_age = $match[1];
316
-		} else {
317
-			$husb_age = '';
318
-		}
319
-		if (preg_match('/\n2 WIFE\n3 AGE (.+)/', $factrec, $match)) {
320
-			$wife_age = $match[1];
321
-		} else {
322
-			$wife_age = '';
323
-		}
324
-
325
-		// Calculated age
326
-		$fact = $event->getTag();
327
-		if (preg_match('/\n2 DATE (.+)/', $factrec, $match)) {
328
-			$date = new Date($match[1]);
329
-			$html .= ' ' . $date->display($anchor);
330
-			// time
331
-			if ($time && preg_match('/\n3 TIME (.+)/', $factrec, $match)) {
332
-				$html .= ' – <span class="date">' . $match[1] . '</span>';
333
-			}
334
-			if ($record instanceof Individual) {
335
-				if ($fact === 'BIRT' && $record->getTree()->getPreference('SHOW_PARENTS_AGE')) {
336
-					// age of parents at child birth
337
-					$html .= self::formatParentsAges($record, $date);
338
-				} elseif ($fact !== 'BIRT' && $fact !== 'CHAN' && $fact !== '_TODO') {
339
-					// age at event
340
-					$birth_date = $record->getBirthDate();
341
-					// Can't use getDeathDate(), as this also gives BURI/CREM events, which
342
-					// wouldn't give the correct "days after death" result for people with
343
-					// no DEAT.
344
-					$death_event = $record->getFirstFact('DEAT');
345
-					if ($death_event) {
346
-						$death_date = $death_event->getDate();
347
-					} else {
348
-						$death_date = new Date('');
349
-					}
350
-					$ageText = '';
351
-					if ((Date::compare($date, $death_date) <= 0 || !$record->isDead()) || $fact == 'DEAT') {
352
-						// Before death, print age
353
-						$age = Date::getAgeGedcom($birth_date, $date);
354
-						// Only show calculated age if it differs from recorded age
355
-						if ($age != '') {
356
-							if (
357
-								$fact_age != '' && $fact_age != $age ||
358
-								$fact_age == '' && $husb_age == '' && $wife_age == '' ||
359
-								$husb_age != '' && $record->getSex() == 'M' && $husb_age != $age ||
360
-								$wife_age != '' && $record->getSex() == 'F' && $wife_age != $age
361
-							) {
362
-								if ($age != "0d") {
363
-									$ageText = '(' . I18N::translate('Age') . ' ' . FunctionsDate::getAgeAtEvent($age) . ')';
364
-								}
365
-							}
366
-						}
367
-					}
368
-					if ($fact != 'DEAT' && Date::compare($date, $death_date) >= 0) {
369
-						// After death, print time since death
370
-						$age = FunctionsDate::getAgeAtEvent(Date::getAgeGedcom($death_date, $date));
371
-						if ($age != '') {
372
-							if (Date::getAgeGedcom($death_date, $date) == "0d") {
373
-								$ageText = '(' . I18N::translate('on the date of death') . ')';
374
-							} else {
375
-								$ageText = '(' . $age . ' ' . I18N::translate('after death') . ')';
376
-								// Family events which occur after death are probably errors
377
-								if ($event->getParent() instanceof Family) {
378
-									$ageText .= '<i class="icon-warning"></i>';
379
-								}
380
-							}
381
-						}
382
-					}
383
-					if ($ageText) {
384
-						$html .= ' <span class="age">' . $ageText . '</span>';
385
-					}
386
-				}
387
-			} elseif ($record instanceof Family) {
388
-				$indi = Individual::getInstance($pid, $record->getTree());
389
-				if ($indi) {
390
-					$birth_date = $indi->getBirthDate();
391
-					$death_date = $indi->getDeathDate();
392
-					$ageText    = '';
393
-					if (Date::compare($date, $death_date) <= 0) {
394
-						$age = Date::getAgeGedcom($birth_date, $date);
395
-						// Only show calculated age if it differs from recorded age
396
-						if ($age != '' && $age > 0) {
397
-							if (
398
-								$fact_age != '' && $fact_age != $age ||
399
-								$fact_age == '' && $husb_age == '' && $wife_age == '' ||
400
-								$husb_age != '' && $indi->getSex() == 'M' && $husb_age != $age ||
401
-								$wife_age != '' && $indi->getSex() == 'F' && $wife_age != $age
402
-							) {
403
-								$ageText = '(' . I18N::translate('Age') . ' ' . FunctionsDate::getAgeAtEvent($age) . ')';
404
-							}
405
-						}
406
-					}
407
-					if ($ageText) {
408
-						$html .= ' <span class="age">' . $ageText . '</span>';
409
-					}
410
-				}
411
-			}
412
-		} elseif (strpos($factrec, "\n2 PLAC ") === false && in_array($fact, Config::emptyFacts())) {
413
-			// There is no DATE.  If there is also no PLAC, then print "yes"
414
-			$html .= I18N::translate('yes');
415
-		}
416
-		// print gedcom ages
417
-		foreach (array(GedcomTag::getLabel('AGE') => $fact_age, GedcomTag::getLabel('HUSB') => $husb_age, GedcomTag::getLabel('WIFE') => $wife_age) as $label => $age) {
418
-			if ($age != '') {
419
-				$html .= ' <span class="label">' . $label . ':</span> <span class="age">' . FunctionsDate::getAgeAtEvent($age) . '</span>';
420
-			}
421
-		}
422
-
423
-		return $html;
424
-	}
425
-
426
-	/**
427
-	 * print fact PLACe TEMPle STATus
428
-	 *
429
-	 * @param Fact $event gedcom fact record
430
-	 * @param bool $anchor to print a link to placelist
431
-	 * @param bool $sub_records to print place subrecords
432
-	 * @param bool $lds to print LDS TEMPle and STATus
433
-	 *
434
-	 * @return string HTML
435
-	 */
436
-	public static function formatFactPlace(Fact $event, $anchor = false, $sub_records = false, $lds = false) {
437
-		if ($anchor) {
438
-			// Show the full place name, for facts/events tab
439
-			$html = '<a href="' . $event->getPlace()->getURL() . '">' . $event->getPlace()->getFullName() . '</a>';
440
-		} else {
441
-			// Abbreviate the place name, for chart boxes
442
-			return $event->getPlace()->getShortName();
443
-		}
444
-
445
-		if ($sub_records) {
446
-			$placerec = Functions::getSubRecord(2, '2 PLAC', $event->getGedcom());
447
-			if (!empty($placerec)) {
448
-				if (preg_match_all('/\n3 (?:_HEB|ROMN) (.+)/', $placerec, $matches)) {
449
-					foreach ($matches[1] as $match) {
450
-						$wt_place = new Place($match, $event->getParent()->getTree());
451
-						$html .= ' - ' . $wt_place->getFullName();
452
-					}
453
-				}
454
-				$map_lati = "";
455
-				$cts      = preg_match('/\d LATI (.*)/', $placerec, $match);
456
-				if ($cts > 0) {
457
-					$map_lati = $match[1];
458
-					$html .= '<br><span class="label">' . GedcomTag::getLabel('LATI') . ': </span>' . $map_lati;
459
-				}
460
-				$map_long = '';
461
-				$cts      = preg_match('/\d LONG (.*)/', $placerec, $match);
462
-				if ($cts > 0) {
463
-					$map_long = $match[1];
464
-					$html .= ' <span class="label">' . GedcomTag::getLabel('LONG') . ': </span>' . $map_long;
465
-				}
466
-				if ($map_lati && $map_long) {
467
-					$map_lati = trim(strtr($map_lati, "NSEW,�", " - -. ")); // S5,6789 ==> -5.6789
468
-					$map_long = trim(strtr($map_long, "NSEW,�", " - -. ")); // E3.456� ==> 3.456
469
-					$html .= ' <a rel="nofollow" href="https://maps.google.com/maps?q=' . $map_lati . ',' . $map_long . '" class="icon-googlemaps" title="' . I18N::translate('Google Maps™') . '"></a>';
470
-					$html .= ' <a rel="nofollow" href="https://www.bing.com/maps/?lvl=15&cp=' . $map_lati . '~' . $map_long . '" class="icon-bing" title="' . I18N::translate('Bing Maps™') . '"></a>';
471
-					$html .= ' <a rel="nofollow" href="https://www.openstreetmap.org/#map=15/' . $map_lati . '/' . $map_long . '" class="icon-osm" title="' . I18N::translate('OpenStreetMap™') . '"></a>';
472
-				}
473
-				if (preg_match('/\d NOTE (.*)/', $placerec, $match)) {
474
-					$html .= '<br>' . self::printFactNotes($placerec, 3);
475
-				}
476
-			}
477
-		}
478
-		if ($lds) {
479
-			if (preg_match('/2 TEMP (.*)/', $event->getGedcom(), $match)) {
480
-				$html .= '<br>' . I18N::translate('LDS temple') . ': ' . GedcomCodeTemp::templeName($match[1]);
481
-			}
482
-			if (preg_match('/2 STAT (.*)/', $event->getGedcom(), $match)) {
483
-				$html .= '<br>' . I18N::translate('Status') . ': ' . GedcomCodeStat::statusName($match[1]);
484
-				if (preg_match('/3 DATE (.*)/', $event->getGedcom(), $match)) {
485
-					$date = new Date($match[1]);
486
-					$html .= ', ' . GedcomTag::getLabel('STAT:DATE') . ': ' . $date->display();
487
-				}
488
-			}
489
-		}
490
-
491
-		return $html;
492
-	}
493
-
494
-	/**
495
-	 * Check for facts that may exist only once for a certain record type.
496
-	 * If the fact already exists in the second array, delete it from the first one.
497
-	 *
498
-	 * @param string[] $uniquefacts
499
-	 * @param Fact[] $recfacts
500
-	 * @param string $type
501
-	 *
502
-	 * @return string[]
503
-	 */
504
-	public static function checkFactUnique($uniquefacts, $recfacts, $type) {
505
-		foreach ($recfacts as $factarray) {
506
-			$fact = false;
507
-			if (is_object($factarray)) {
508
-				$fact = $factarray->getTag();
509
-			} else {
510
-				if ($type === 'SOUR' || $type === 'REPO') {
511
-					$factrec = $factarray[0];
512
-				}
513
-				if ($type === 'FAM' || $type === 'INDI') {
514
-					$factrec = $factarray[1];
515
-				}
516
-
517
-				$ft = preg_match("/1 (\w+)(.*)/", $factrec, $match);
518
-				if ($ft > 0) {
519
-					$fact = trim($match[1]);
520
-				}
521
-			}
522
-			if ($fact !== false) {
523
-				$key = array_search($fact, $uniquefacts);
524
-				if ($key !== false) {
525
-					unset($uniquefacts[$key]);
526
-				}
527
-			}
528
-		}
529
-
530
-		return $uniquefacts;
531
-	}
532
-
533
-	/**
534
-	 * Print a new fact box on details pages
535
-	 *
536
-	 * @param string $id the id of the person, family, source etc the fact will be added to
537
-	 * @param array $usedfacts an array of facts already used in this record
538
-	 * @param string $type the type of record INDI, FAM, SOUR etc
539
-	 */
540
-	public static function printAddNewFact($id, $usedfacts, $type) {
541
-		global $WT_TREE;
542
-
543
-		// -- Add from clipboard
544
-		if (is_array(Session::get('clipboard'))) {
545
-			$newRow = true;
546
-			foreach (array_reverse(Session::get('clipboard'), true) as $fact_id => $fact) {
547
-				if ($fact["type"] == $type || $fact["type"] == 'all') {
548
-					if ($newRow) {
549
-						$newRow = false;
550
-						echo '<tr class="noprint"><td class="descriptionbox">';
551
-						echo I18N::translate('Add from clipboard'), '</td>';
552
-						echo '<td class="optionbox wrap"><form method="get" name="newFromClipboard" action="?" onsubmit="return false;">';
553
-						echo '<select id="newClipboardFact">';
554
-					}
555
-					echo '<option value="', Filter::escapeHtml($fact_id), '">', GedcomTag::getLabel($fact['fact']);
556
-					// TODO use the event class to store/parse the clipboard events
557
-					if (preg_match('/^2 DATE (.+)/m', $fact['factrec'], $match)) {
558
-						$tmp = new Date($match[1]);
559
-						echo '; ', $tmp->minimumDate()->format('%Y');
560
-					}
561
-					if (preg_match('/^2 PLAC ([^,\n]+)/m', $fact['factrec'], $match)) {
562
-						echo '; ', $match[1];
563
-					}
564
-					echo '</option>';
565
-				}
566
-			}
567
-			if (!$newRow) {
568
-				echo '</select>';
569
-				echo '&nbsp;&nbsp;<input type="button" value="', /* I18N: A button label. */ I18N::translate('add'), "\" onclick=\"return paste_fact('$id', '#newClipboardFact');\"> ";
570
-				echo '</form></td></tr>', "\n";
571
-			}
572
-		}
573
-
574
-		// -- Add from pick list
575
-		switch ($type) {
576
-		case "INDI":
577
-			$addfacts    = preg_split("/[, ;:]+/", $WT_TREE->getPreference('INDI_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
578
-			$uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('INDI_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
579
-			$quickfacts  = preg_split("/[, ;:]+/", $WT_TREE->getPreference('INDI_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
580
-			break;
581
-		case "FAM":
582
-			$addfacts    = preg_split("/[, ;:]+/", $WT_TREE->getPreference('FAM_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
583
-			$uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('FAM_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
584
-			$quickfacts  = preg_split("/[, ;:]+/", $WT_TREE->getPreference('FAM_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
585
-			break;
586
-		case "SOUR":
587
-			$addfacts    = preg_split("/[, ;:]+/", $WT_TREE->getPreference('SOUR_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
588
-			$uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('SOUR_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
589
-			$quickfacts  = preg_split("/[, ;:]+/", $WT_TREE->getPreference('SOUR_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
590
-			break;
591
-		case "NOTE":
592
-			$addfacts    = preg_split("/[, ;:]+/", $WT_TREE->getPreference('NOTE_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
593
-			$uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('NOTE_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
594
-			$quickfacts  = preg_split("/[, ;:]+/", $WT_TREE->getPreference('NOTE_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
595
-			break;
596
-		case "REPO":
597
-			$addfacts    = preg_split("/[, ;:]+/", $WT_TREE->getPreference('REPO_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
598
-			$uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('REPO_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
599
-			$quickfacts  = preg_split("/[, ;:]+/", $WT_TREE->getPreference('REPO_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
600
-			break;
601
-		default:
602
-			return;
603
-		}
604
-		$addfacts            = array_merge(self::checkFactUnique($uniquefacts, $usedfacts, $type), $addfacts);
605
-		$quickfacts          = array_intersect($quickfacts, $addfacts);
606
-		$translated_addfacts = array();
607
-		foreach ($addfacts as $addfact) {
608
-			$translated_addfacts[$addfact] = GedcomTag::getLabel($addfact);
609
-		}
610
-		uasort($translated_addfacts, function ($x, $y) {
611
-			return I18N::strcasecmp(I18N::translate($x), I18N::translate($y));
612
-		});
613
-		echo '<tr class="noprint"><td class="descriptionbox">';
614
-		echo I18N::translate('Fact or event');
615
-		echo '</td>';
616
-		echo '<td class="optionbox wrap">';
617
-		echo '<form method="get" name="newfactform" action="?" onsubmit="return false;">';
618
-		echo '<select id="newfact" name="newfact">';
619
-		echo '<option value="" disabled selected>' . I18N::translate('&lt;select&gt;') . '</option>';
620
-		foreach ($translated_addfacts as $fact => $fact_name) {
621
-			echo '<option value="', $fact, '">', $fact_name, '</option>';
622
-		}
623
-		if ($type == 'INDI' || $type == 'FAM') {
624
-			echo '<option value="FACT">', I18N::translate('Custom fact'), '</option>';
625
-			echo '<option value="EVEN">', I18N::translate('Custom event'), '</option>';
626
-		}
627
-		echo '</select>';
628
-		echo '<input type="button" value="', /* I18N: A button label. */ I18N::translate('add'), '" onclick="add_record(\'' . $id . '\', \'newfact\');">';
629
-		echo '<span class="quickfacts">';
630
-		foreach ($quickfacts as $fact) {
631
-			echo '<a href="#" onclick="add_new_record(\'' . $id . '\', \'' . $fact . '\');return false;">', GedcomTag::getLabel($fact), '</a>';
632
-		}
633
-		echo '</span></form>';
634
-		echo '</td></tr>';
635
-	}
636
-
637
-	/**
638
-	 * javascript declaration for calendar popup
639
-	 */
640
-	public static function initializeCalendarPopup() {
641
-		global $controller;
642
-
643
-		$controller->addInlineJavascript('
43
+    /**
44
+     * print the information for an individual chart box
45
+     *
46
+     * find and print a given individuals information for a pedigree chart
47
+     *
48
+     * @param Individual $person The person to print
49
+     * @param int $show_full The style to print the box in, 0 for smaller boxes, 1 for larger boxes
50
+     */
51
+    public static function printPedigreePerson(Individual $person = null, $show_full = 1) {
52
+
53
+        switch ($show_full) {
54
+        case 0:
55
+            if ($person) {
56
+                echo Theme::theme()->individualBoxSmall($person);
57
+            } else {
58
+                echo Theme::theme()->individualBoxSmallEmpty();
59
+            }
60
+            break;
61
+        case 1:
62
+            if ($person) {
63
+                echo Theme::theme()->individualBox($person);
64
+            } else {
65
+                echo Theme::theme()->individualBoxEmpty();
66
+            }
67
+            break;
68
+        }
69
+    }
70
+
71
+    /**
72
+     * print a note record
73
+     *
74
+     * @param string $text
75
+     * @param int $nlevel the level of the note record
76
+     * @param string $nrec the note record to print
77
+     * @param bool $textOnly Don't print the "Note: " introduction
78
+     *
79
+     * @return string
80
+     */
81
+    public static function printNoteRecord($text, $nlevel, $nrec, $textOnly = false) {
82
+        global $WT_TREE;
83
+
84
+        $text .= Functions::getCont($nlevel, $nrec);
85
+
86
+        // Check if shared note (we have already checked that it exists)
87
+        if (preg_match('/^0 @(' . WT_REGEX_XREF . ')@ NOTE/', $nrec, $match)) {
88
+            $note  = Note::getInstance($match[1], $WT_TREE);
89
+            $label = 'SHARED_NOTE';
90
+            // If Census assistant installed, allow it to format the note
91
+            if (Module::getModuleByName('GEDFact_assistant')) {
92
+                $html = CensusAssistantModule::formatCensusNote($note);
93
+            } else {
94
+                $html = Filter::formatText($note->getNote(), $WT_TREE);
95
+            }
96
+        } else {
97
+            $note  = null;
98
+            $label = 'NOTE';
99
+            $html  = Filter::formatText($text, $WT_TREE);
100
+        }
101
+
102
+        if ($textOnly) {
103
+            return strip_tags($text);
104
+        }
105
+
106
+        if (strpos($text, "\n") === false) {
107
+            // A one-line note? strip the block-level tags, so it displays inline
108
+            return GedcomTag::getLabelValue($label, strip_tags($html, '<a><strong><em>'));
109
+        } elseif ($WT_TREE->getPreference('EXPAND_NOTES')) {
110
+            // A multi-line note, and we're expanding notes by default
111
+            return GedcomTag::getLabelValue($label, $html);
112
+        } else {
113
+            // A multi-line note, with an expand/collapse option
114
+            $element_id = Uuid::uuid4();
115
+            // NOTE: class "note-details" is (currently) used only by some third-party themes
116
+            if ($note) {
117
+                $first_line = '<a href="' . $note->getHtmlUrl() . '">' . $note->getFullName() . '</a>';
118
+            } else {
119
+                list($text) = explode("\n", strip_tags($html));
120
+                $first_line = strlen($text) > 100 ? mb_substr($text, 0, 100) . I18N::translate('…') : $text;
121
+            }
122
+
123
+            return
124
+                '<div class="fact_NOTE"><span class="label">' .
125
+                '<a href="#" onclick="expand_layer(\'' . $element_id . '\'); return false;"><i id="' . $element_id . '_img" class="icon-plus"></i></a> ' . GedcomTag::getLabel($label) . ':</span> ' . '<span id="' . $element_id . '-alt">' . $first_line . '</span>' .
126
+                '</div>' .
127
+                '<div class="note-details" id="' . $element_id . '" style="display:none">' . $html . '</div>';
128
+        }
129
+    }
130
+
131
+    /**
132
+     * Print all of the notes in this fact record
133
+     *
134
+     * @param string $factrec The factrecord to print the notes from
135
+     * @param int $level The level of the factrecord
136
+     * @param bool $textOnly Don't print the "Note: " introduction
137
+     *
138
+     * @return string HTML
139
+     */
140
+    public static function printFactNotes($factrec, $level, $textOnly = false) {
141
+        global $WT_TREE;
142
+
143
+        $data          = '';
144
+        $previous_spos = 0;
145
+        $nlevel        = $level + 1;
146
+        $ct            = preg_match_all("/$level NOTE (.*)/", $factrec, $match, PREG_SET_ORDER);
147
+        for ($j = 0; $j < $ct; $j++) {
148
+            $spos1 = strpos($factrec, $match[$j][0], $previous_spos);
149
+            $spos2 = strpos($factrec . "\n$level", "\n$level", $spos1 + 1);
150
+            if (!$spos2) {
151
+                $spos2 = strlen($factrec);
152
+            }
153
+            $previous_spos = $spos2;
154
+            $nrec          = substr($factrec, $spos1, $spos2 - $spos1);
155
+            if (!isset($match[$j][1])) {
156
+                $match[$j][1] = '';
157
+            }
158
+            if (!preg_match('/@(.*)@/', $match[$j][1], $nmatch)) {
159
+                $data .= self::printNoteRecord($match[$j][1], $nlevel, $nrec, $textOnly);
160
+            } else {
161
+                $note = Note::getInstance($nmatch[1], $WT_TREE);
162
+                if ($note) {
163
+                    if ($note->canShow()) {
164
+                        $noterec = $note->getGedcom();
165
+                        $nt      = preg_match("/0 @$nmatch[1]@ NOTE (.*)/", $noterec, $n1match);
166
+                        $data .= self::printNoteRecord(($nt > 0) ? $n1match[1] : "", 1, $noterec, $textOnly);
167
+                        if (!$textOnly) {
168
+                            if (strpos($noterec, '1 SOUR') !== false) {
169
+                                $data .= FunctionsPrintFacts::printFactSources($noterec, 1);
170
+                            }
171
+                        }
172
+                    }
173
+                } else {
174
+                    $data = '<div class="fact_NOTE"><span class="label">' . I18N::translate('Note') . '</span>: <span class="field error">' . $nmatch[1] . '</span></div>';
175
+                }
176
+            }
177
+            if (!$textOnly) {
178
+                if (strpos($factrec, "$nlevel SOUR") !== false) {
179
+                    $data .= "<div class=\"indent\">";
180
+                    $data .= FunctionsPrintFacts::printFactSources($nrec, $nlevel);
181
+                    $data .= "</div>";
182
+                }
183
+            }
184
+        }
185
+
186
+        return $data;
187
+    }
188
+
189
+    /**
190
+     * Print a link for a popup help window.
191
+     *
192
+     * @param string $help_topic
193
+     * @param string $module
194
+     *
195
+     * @return string
196
+     */
197
+    public static function helpLink($help_topic, $module = '') {
198
+        return '<span class="icon-help" onclick="helpDialog(\'' . $help_topic . '\',\'' . $module . '\'); return false;">&nbsp;</span>';
199
+    }
200
+
201
+    /**
202
+     * Print an external help link to the wiki site.
203
+     *
204
+     * @deprecated - nothing should be so complicated that it needs lengthy instructions!
205
+     *
206
+     * @param string $topic
207
+     *
208
+     * @return string
209
+     */
210
+    public static function wikiHelpLink($topic) {
211
+        return '<a class="help icon-wiki" href="' . WT_WEBTREES_WIKI . $topic . '" title="' . I18N::translate('webtrees wiki') . '"></a>';
212
+    }
213
+
214
+    /**
215
+     * When a user has searched for text, highlight any matches in
216
+     * the displayed string.
217
+     *
218
+     * @param string $string
219
+     *
220
+     * @return string
221
+     */
222
+    public static function highlightSearchHits($string) {
223
+        global $controller;
224
+
225
+        if ($controller instanceof SearchController && $controller->query) {
226
+            // TODO: when a search contains multiple words, we search independently.
227
+            // e.g. searching for "FOO BAR" will find records containing both FOO and BAR.
228
+            // However, we only highlight the original search string, not the search terms.
229
+            // The controller needs to provide its "query_terms" array.
230
+            $regex = array();
231
+            foreach (array($controller->query) as $search_term) {
232
+                $regex[] = preg_quote($search_term, '/');
233
+            }
234
+            // Match these strings, provided they do not occur inside HTML tags
235
+            $regex = '(' . implode('|', $regex) . ')(?![^<]*>)';
236
+
237
+            return preg_replace('/' . $regex . '/i', '<span class="search_hit">$1</span>', $string);
238
+        } else {
239
+            return $string;
240
+        }
241
+    }
242
+
243
+    /**
244
+     * Format age of parents in HTML
245
+     *
246
+     * @param Individual $person child
247
+     * @param Date $birth_date
248
+     *
249
+     * @return string HTML
250
+     */
251
+    public static function formatParentsAges(Individual $person, Date $birth_date) {
252
+        $html     = '';
253
+        $families = $person->getChildFamilies();
254
+        // Multiple sets of parents (e.g. adoption) cause complications, so ignore.
255
+        if ($birth_date->isOK() && count($families) == 1) {
256
+            $family = current($families);
257
+            foreach ($family->getSpouses() as $parent) {
258
+                if ($parent->getBirthDate()->isOK()) {
259
+                    $sex      = $parent->getSexImage();
260
+                    $age      = Date::getAge($parent->getBirthDate(), $birth_date, 2);
261
+                    $deatdate = $parent->getDeathDate();
262
+                    switch ($parent->getSex()) {
263
+                    case 'F':
264
+                        // Highlight mothers who die in childbirth or shortly afterwards
265
+                        if ($deatdate->isOK() && $deatdate->maximumJulianDay() < $birth_date->minimumJulianDay() + 90) {
266
+                            $html .= ' <span title="' . GedcomTag::getLabel('_DEAT_PARE', $parent) . '" class="parentdeath">' . $sex . $age . '</span>';
267
+                        } else {
268
+                            $html .= ' <span title="' . I18N::translate('Mother’s age') . '">' . $sex . $age . '</span>';
269
+                        }
270
+                        break;
271
+                    case 'M':
272
+                        // Highlight fathers who die before the birth
273
+                        if ($deatdate->isOK() && $deatdate->maximumJulianDay() < $birth_date->minimumJulianDay()) {
274
+                            $html .= ' <span title="' . GedcomTag::getLabel('_DEAT_PARE', $parent) . '" class="parentdeath">' . $sex . $age . '</span>';
275
+                        } else {
276
+                            $html .= ' <span title="' . I18N::translate('Father’s age') . '">' . $sex . $age . '</span>';
277
+                        }
278
+                        break;
279
+                    default:
280
+                        $html .= ' <span title="' . I18N::translate('Parent’s age') . '">' . $sex . $age . '</span>';
281
+                        break;
282
+                    }
283
+                }
284
+            }
285
+            if ($html) {
286
+                $html = '<span class="age">' . $html . '</span>';
287
+            }
288
+        }
289
+
290
+        return $html;
291
+    }
292
+
293
+    /**
294
+     * Print fact DATE/TIME
295
+     *
296
+     * @param Fact $event event containing the date/age
297
+     * @param GedcomRecord $record the person (or couple) whose ages should be printed
298
+     * @param bool $anchor option to print a link to calendar
299
+     * @param bool $time option to print TIME value
300
+     *
301
+     * @return string
302
+     */
303
+    public static function formatFactDate(Fact $event, GedcomRecord $record, $anchor, $time) {
304
+        global $pid;
305
+
306
+        $factrec = $event->getGedcom();
307
+        $html    = '';
308
+        // Recorded age
309
+        if (preg_match('/\n2 AGE (.+)/', $factrec, $match)) {
310
+            $fact_age = $match[1];
311
+        } else {
312
+            $fact_age = '';
313
+        }
314
+        if (preg_match('/\n2 HUSB\n3 AGE (.+)/', $factrec, $match)) {
315
+            $husb_age = $match[1];
316
+        } else {
317
+            $husb_age = '';
318
+        }
319
+        if (preg_match('/\n2 WIFE\n3 AGE (.+)/', $factrec, $match)) {
320
+            $wife_age = $match[1];
321
+        } else {
322
+            $wife_age = '';
323
+        }
324
+
325
+        // Calculated age
326
+        $fact = $event->getTag();
327
+        if (preg_match('/\n2 DATE (.+)/', $factrec, $match)) {
328
+            $date = new Date($match[1]);
329
+            $html .= ' ' . $date->display($anchor);
330
+            // time
331
+            if ($time && preg_match('/\n3 TIME (.+)/', $factrec, $match)) {
332
+                $html .= ' – <span class="date">' . $match[1] . '</span>';
333
+            }
334
+            if ($record instanceof Individual) {
335
+                if ($fact === 'BIRT' && $record->getTree()->getPreference('SHOW_PARENTS_AGE')) {
336
+                    // age of parents at child birth
337
+                    $html .= self::formatParentsAges($record, $date);
338
+                } elseif ($fact !== 'BIRT' && $fact !== 'CHAN' && $fact !== '_TODO') {
339
+                    // age at event
340
+                    $birth_date = $record->getBirthDate();
341
+                    // Can't use getDeathDate(), as this also gives BURI/CREM events, which
342
+                    // wouldn't give the correct "days after death" result for people with
343
+                    // no DEAT.
344
+                    $death_event = $record->getFirstFact('DEAT');
345
+                    if ($death_event) {
346
+                        $death_date = $death_event->getDate();
347
+                    } else {
348
+                        $death_date = new Date('');
349
+                    }
350
+                    $ageText = '';
351
+                    if ((Date::compare($date, $death_date) <= 0 || !$record->isDead()) || $fact == 'DEAT') {
352
+                        // Before death, print age
353
+                        $age = Date::getAgeGedcom($birth_date, $date);
354
+                        // Only show calculated age if it differs from recorded age
355
+                        if ($age != '') {
356
+                            if (
357
+                                $fact_age != '' && $fact_age != $age ||
358
+                                $fact_age == '' && $husb_age == '' && $wife_age == '' ||
359
+                                $husb_age != '' && $record->getSex() == 'M' && $husb_age != $age ||
360
+                                $wife_age != '' && $record->getSex() == 'F' && $wife_age != $age
361
+                            ) {
362
+                                if ($age != "0d") {
363
+                                    $ageText = '(' . I18N::translate('Age') . ' ' . FunctionsDate::getAgeAtEvent($age) . ')';
364
+                                }
365
+                            }
366
+                        }
367
+                    }
368
+                    if ($fact != 'DEAT' && Date::compare($date, $death_date) >= 0) {
369
+                        // After death, print time since death
370
+                        $age = FunctionsDate::getAgeAtEvent(Date::getAgeGedcom($death_date, $date));
371
+                        if ($age != '') {
372
+                            if (Date::getAgeGedcom($death_date, $date) == "0d") {
373
+                                $ageText = '(' . I18N::translate('on the date of death') . ')';
374
+                            } else {
375
+                                $ageText = '(' . $age . ' ' . I18N::translate('after death') . ')';
376
+                                // Family events which occur after death are probably errors
377
+                                if ($event->getParent() instanceof Family) {
378
+                                    $ageText .= '<i class="icon-warning"></i>';
379
+                                }
380
+                            }
381
+                        }
382
+                    }
383
+                    if ($ageText) {
384
+                        $html .= ' <span class="age">' . $ageText . '</span>';
385
+                    }
386
+                }
387
+            } elseif ($record instanceof Family) {
388
+                $indi = Individual::getInstance($pid, $record->getTree());
389
+                if ($indi) {
390
+                    $birth_date = $indi->getBirthDate();
391
+                    $death_date = $indi->getDeathDate();
392
+                    $ageText    = '';
393
+                    if (Date::compare($date, $death_date) <= 0) {
394
+                        $age = Date::getAgeGedcom($birth_date, $date);
395
+                        // Only show calculated age if it differs from recorded age
396
+                        if ($age != '' && $age > 0) {
397
+                            if (
398
+                                $fact_age != '' && $fact_age != $age ||
399
+                                $fact_age == '' && $husb_age == '' && $wife_age == '' ||
400
+                                $husb_age != '' && $indi->getSex() == 'M' && $husb_age != $age ||
401
+                                $wife_age != '' && $indi->getSex() == 'F' && $wife_age != $age
402
+                            ) {
403
+                                $ageText = '(' . I18N::translate('Age') . ' ' . FunctionsDate::getAgeAtEvent($age) . ')';
404
+                            }
405
+                        }
406
+                    }
407
+                    if ($ageText) {
408
+                        $html .= ' <span class="age">' . $ageText . '</span>';
409
+                    }
410
+                }
411
+            }
412
+        } elseif (strpos($factrec, "\n2 PLAC ") === false && in_array($fact, Config::emptyFacts())) {
413
+            // There is no DATE.  If there is also no PLAC, then print "yes"
414
+            $html .= I18N::translate('yes');
415
+        }
416
+        // print gedcom ages
417
+        foreach (array(GedcomTag::getLabel('AGE') => $fact_age, GedcomTag::getLabel('HUSB') => $husb_age, GedcomTag::getLabel('WIFE') => $wife_age) as $label => $age) {
418
+            if ($age != '') {
419
+                $html .= ' <span class="label">' . $label . ':</span> <span class="age">' . FunctionsDate::getAgeAtEvent($age) . '</span>';
420
+            }
421
+        }
422
+
423
+        return $html;
424
+    }
425
+
426
+    /**
427
+     * print fact PLACe TEMPle STATus
428
+     *
429
+     * @param Fact $event gedcom fact record
430
+     * @param bool $anchor to print a link to placelist
431
+     * @param bool $sub_records to print place subrecords
432
+     * @param bool $lds to print LDS TEMPle and STATus
433
+     *
434
+     * @return string HTML
435
+     */
436
+    public static function formatFactPlace(Fact $event, $anchor = false, $sub_records = false, $lds = false) {
437
+        if ($anchor) {
438
+            // Show the full place name, for facts/events tab
439
+            $html = '<a href="' . $event->getPlace()->getURL() . '">' . $event->getPlace()->getFullName() . '</a>';
440
+        } else {
441
+            // Abbreviate the place name, for chart boxes
442
+            return $event->getPlace()->getShortName();
443
+        }
444
+
445
+        if ($sub_records) {
446
+            $placerec = Functions::getSubRecord(2, '2 PLAC', $event->getGedcom());
447
+            if (!empty($placerec)) {
448
+                if (preg_match_all('/\n3 (?:_HEB|ROMN) (.+)/', $placerec, $matches)) {
449
+                    foreach ($matches[1] as $match) {
450
+                        $wt_place = new Place($match, $event->getParent()->getTree());
451
+                        $html .= ' - ' . $wt_place->getFullName();
452
+                    }
453
+                }
454
+                $map_lati = "";
455
+                $cts      = preg_match('/\d LATI (.*)/', $placerec, $match);
456
+                if ($cts > 0) {
457
+                    $map_lati = $match[1];
458
+                    $html .= '<br><span class="label">' . GedcomTag::getLabel('LATI') . ': </span>' . $map_lati;
459
+                }
460
+                $map_long = '';
461
+                $cts      = preg_match('/\d LONG (.*)/', $placerec, $match);
462
+                if ($cts > 0) {
463
+                    $map_long = $match[1];
464
+                    $html .= ' <span class="label">' . GedcomTag::getLabel('LONG') . ': </span>' . $map_long;
465
+                }
466
+                if ($map_lati && $map_long) {
467
+                    $map_lati = trim(strtr($map_lati, "NSEW,�", " - -. ")); // S5,6789 ==> -5.6789
468
+                    $map_long = trim(strtr($map_long, "NSEW,�", " - -. ")); // E3.456� ==> 3.456
469
+                    $html .= ' <a rel="nofollow" href="https://maps.google.com/maps?q=' . $map_lati . ',' . $map_long . '" class="icon-googlemaps" title="' . I18N::translate('Google Maps™') . '"></a>';
470
+                    $html .= ' <a rel="nofollow" href="https://www.bing.com/maps/?lvl=15&cp=' . $map_lati . '~' . $map_long . '" class="icon-bing" title="' . I18N::translate('Bing Maps™') . '"></a>';
471
+                    $html .= ' <a rel="nofollow" href="https://www.openstreetmap.org/#map=15/' . $map_lati . '/' . $map_long . '" class="icon-osm" title="' . I18N::translate('OpenStreetMap™') . '"></a>';
472
+                }
473
+                if (preg_match('/\d NOTE (.*)/', $placerec, $match)) {
474
+                    $html .= '<br>' . self::printFactNotes($placerec, 3);
475
+                }
476
+            }
477
+        }
478
+        if ($lds) {
479
+            if (preg_match('/2 TEMP (.*)/', $event->getGedcom(), $match)) {
480
+                $html .= '<br>' . I18N::translate('LDS temple') . ': ' . GedcomCodeTemp::templeName($match[1]);
481
+            }
482
+            if (preg_match('/2 STAT (.*)/', $event->getGedcom(), $match)) {
483
+                $html .= '<br>' . I18N::translate('Status') . ': ' . GedcomCodeStat::statusName($match[1]);
484
+                if (preg_match('/3 DATE (.*)/', $event->getGedcom(), $match)) {
485
+                    $date = new Date($match[1]);
486
+                    $html .= ', ' . GedcomTag::getLabel('STAT:DATE') . ': ' . $date->display();
487
+                }
488
+            }
489
+        }
490
+
491
+        return $html;
492
+    }
493
+
494
+    /**
495
+     * Check for facts that may exist only once for a certain record type.
496
+     * If the fact already exists in the second array, delete it from the first one.
497
+     *
498
+     * @param string[] $uniquefacts
499
+     * @param Fact[] $recfacts
500
+     * @param string $type
501
+     *
502
+     * @return string[]
503
+     */
504
+    public static function checkFactUnique($uniquefacts, $recfacts, $type) {
505
+        foreach ($recfacts as $factarray) {
506
+            $fact = false;
507
+            if (is_object($factarray)) {
508
+                $fact = $factarray->getTag();
509
+            } else {
510
+                if ($type === 'SOUR' || $type === 'REPO') {
511
+                    $factrec = $factarray[0];
512
+                }
513
+                if ($type === 'FAM' || $type === 'INDI') {
514
+                    $factrec = $factarray[1];
515
+                }
516
+
517
+                $ft = preg_match("/1 (\w+)(.*)/", $factrec, $match);
518
+                if ($ft > 0) {
519
+                    $fact = trim($match[1]);
520
+                }
521
+            }
522
+            if ($fact !== false) {
523
+                $key = array_search($fact, $uniquefacts);
524
+                if ($key !== false) {
525
+                    unset($uniquefacts[$key]);
526
+                }
527
+            }
528
+        }
529
+
530
+        return $uniquefacts;
531
+    }
532
+
533
+    /**
534
+     * Print a new fact box on details pages
535
+     *
536
+     * @param string $id the id of the person, family, source etc the fact will be added to
537
+     * @param array $usedfacts an array of facts already used in this record
538
+     * @param string $type the type of record INDI, FAM, SOUR etc
539
+     */
540
+    public static function printAddNewFact($id, $usedfacts, $type) {
541
+        global $WT_TREE;
542
+
543
+        // -- Add from clipboard
544
+        if (is_array(Session::get('clipboard'))) {
545
+            $newRow = true;
546
+            foreach (array_reverse(Session::get('clipboard'), true) as $fact_id => $fact) {
547
+                if ($fact["type"] == $type || $fact["type"] == 'all') {
548
+                    if ($newRow) {
549
+                        $newRow = false;
550
+                        echo '<tr class="noprint"><td class="descriptionbox">';
551
+                        echo I18N::translate('Add from clipboard'), '</td>';
552
+                        echo '<td class="optionbox wrap"><form method="get" name="newFromClipboard" action="?" onsubmit="return false;">';
553
+                        echo '<select id="newClipboardFact">';
554
+                    }
555
+                    echo '<option value="', Filter::escapeHtml($fact_id), '">', GedcomTag::getLabel($fact['fact']);
556
+                    // TODO use the event class to store/parse the clipboard events
557
+                    if (preg_match('/^2 DATE (.+)/m', $fact['factrec'], $match)) {
558
+                        $tmp = new Date($match[1]);
559
+                        echo '; ', $tmp->minimumDate()->format('%Y');
560
+                    }
561
+                    if (preg_match('/^2 PLAC ([^,\n]+)/m', $fact['factrec'], $match)) {
562
+                        echo '; ', $match[1];
563
+                    }
564
+                    echo '</option>';
565
+                }
566
+            }
567
+            if (!$newRow) {
568
+                echo '</select>';
569
+                echo '&nbsp;&nbsp;<input type="button" value="', /* I18N: A button label. */ I18N::translate('add'), "\" onclick=\"return paste_fact('$id', '#newClipboardFact');\"> ";
570
+                echo '</form></td></tr>', "\n";
571
+            }
572
+        }
573
+
574
+        // -- Add from pick list
575
+        switch ($type) {
576
+        case "INDI":
577
+            $addfacts    = preg_split("/[, ;:]+/", $WT_TREE->getPreference('INDI_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
578
+            $uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('INDI_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
579
+            $quickfacts  = preg_split("/[, ;:]+/", $WT_TREE->getPreference('INDI_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
580
+            break;
581
+        case "FAM":
582
+            $addfacts    = preg_split("/[, ;:]+/", $WT_TREE->getPreference('FAM_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
583
+            $uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('FAM_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
584
+            $quickfacts  = preg_split("/[, ;:]+/", $WT_TREE->getPreference('FAM_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
585
+            break;
586
+        case "SOUR":
587
+            $addfacts    = preg_split("/[, ;:]+/", $WT_TREE->getPreference('SOUR_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
588
+            $uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('SOUR_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
589
+            $quickfacts  = preg_split("/[, ;:]+/", $WT_TREE->getPreference('SOUR_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
590
+            break;
591
+        case "NOTE":
592
+            $addfacts    = preg_split("/[, ;:]+/", $WT_TREE->getPreference('NOTE_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
593
+            $uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('NOTE_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
594
+            $quickfacts  = preg_split("/[, ;:]+/", $WT_TREE->getPreference('NOTE_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
595
+            break;
596
+        case "REPO":
597
+            $addfacts    = preg_split("/[, ;:]+/", $WT_TREE->getPreference('REPO_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
598
+            $uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('REPO_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
599
+            $quickfacts  = preg_split("/[, ;:]+/", $WT_TREE->getPreference('REPO_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
600
+            break;
601
+        default:
602
+            return;
603
+        }
604
+        $addfacts            = array_merge(self::checkFactUnique($uniquefacts, $usedfacts, $type), $addfacts);
605
+        $quickfacts          = array_intersect($quickfacts, $addfacts);
606
+        $translated_addfacts = array();
607
+        foreach ($addfacts as $addfact) {
608
+            $translated_addfacts[$addfact] = GedcomTag::getLabel($addfact);
609
+        }
610
+        uasort($translated_addfacts, function ($x, $y) {
611
+            return I18N::strcasecmp(I18N::translate($x), I18N::translate($y));
612
+        });
613
+        echo '<tr class="noprint"><td class="descriptionbox">';
614
+        echo I18N::translate('Fact or event');
615
+        echo '</td>';
616
+        echo '<td class="optionbox wrap">';
617
+        echo '<form method="get" name="newfactform" action="?" onsubmit="return false;">';
618
+        echo '<select id="newfact" name="newfact">';
619
+        echo '<option value="" disabled selected>' . I18N::translate('&lt;select&gt;') . '</option>';
620
+        foreach ($translated_addfacts as $fact => $fact_name) {
621
+            echo '<option value="', $fact, '">', $fact_name, '</option>';
622
+        }
623
+        if ($type == 'INDI' || $type == 'FAM') {
624
+            echo '<option value="FACT">', I18N::translate('Custom fact'), '</option>';
625
+            echo '<option value="EVEN">', I18N::translate('Custom event'), '</option>';
626
+        }
627
+        echo '</select>';
628
+        echo '<input type="button" value="', /* I18N: A button label. */ I18N::translate('add'), '" onclick="add_record(\'' . $id . '\', \'newfact\');">';
629
+        echo '<span class="quickfacts">';
630
+        foreach ($quickfacts as $fact) {
631
+            echo '<a href="#" onclick="add_new_record(\'' . $id . '\', \'' . $fact . '\');return false;">', GedcomTag::getLabel($fact), '</a>';
632
+        }
633
+        echo '</span></form>';
634
+        echo '</td></tr>';
635
+    }
636
+
637
+    /**
638
+     * javascript declaration for calendar popup
639
+     */
640
+    public static function initializeCalendarPopup() {
641
+        global $controller;
642
+
643
+        $controller->addInlineJavascript('
644 644
 			cal_setMonthNames(
645 645
 				"' . I18N::translateContext('NOMINATIVE', 'January') . '",
646 646
 				"' . I18N::translateContext('NOMINATIVE', 'February') . '",
@@ -666,153 +666,153 @@  discard block
 block discarded – undo
666 666
 			)
667 667
 			cal_setWeekStart(' . I18N::firstDay() . ');
668 668
 		');
669
-	}
670
-
671
-	/**
672
-	 * HTML link to find an individual.
673
-	 *
674
-	 * @param string $element_id
675
-	 * @param string $indiname
676
-	 * @param Tree $tree
677
-	 *
678
-	 * @return string
679
-	 */
680
-	public static function printFindIndividualLink($element_id, $indiname = '', $tree = null) {
681
-		global $WT_TREE;
682
-
683
-		if ($tree === null) {
684
-			$tree = $WT_TREE;
685
-		}
686
-
687
-		return '<a href="#" onclick="findIndi(document.getElementById(\'' . $element_id . '\'), document.getElementById(\'' . $indiname . '\'), \'' . $tree->getNameHtml() . '\'); return false;" class="icon-button_indi" title="' . I18N::translate('Find an individual') . '"></a>';
688
-	}
689
-
690
-	/**
691
-	 * HTML link to find a place.
692
-	 *
693
-	 * @param string $element_id
694
-	 *
695
-	 * @return string
696
-	 */
697
-	public static function printFindPlaceLink($element_id) {
698
-		return '<a href="#" onclick="findPlace(document.getElementById(\'' . $element_id . '\'), WT_GEDCOM); return false;" class="icon-button_place" title="' . I18N::translate('Find a place') . '"></a>';
699
-	}
700
-
701
-	/**
702
-	 * HTML link to find a family.
703
-	 *
704
-	 * @param string $element_id
705
-	 *
706
-	 * @return string
707
-	 */
708
-	public static function printFindFamilyLink($element_id) {
709
-		return '<a href="#" onclick="findFamily(document.getElementById(\'' . $element_id . '\'), WT_GEDCOM); return false;" class="icon-button_family" title="' . I18N::translate('Find a family') . '"></a>';
710
-	}
711
-
712
-	/**
713
-	 * HTML link to open the special character window.
714
-	 *
715
-	 * @param string $element_id
716
-	 *
717
-	 * @return string
718
-	 */
719
-	public static function printSpecialCharacterLink($element_id) {
720
-		return '<span onclick="findSpecialChar(document.getElementById(\'' . $element_id . '\')); if (window.updatewholename) { updatewholename(); } return false;" class="icon-button_keyboard" title="' . I18N::translate('Find a special character') . '"></span>';
721
-	}
722
-
723
-	/**
724
-	 * HTML element to insert a value from a list.
725
-	 *
726
-	 * @param string $element_id
727
-	 * @param string[] $choices
728
-	 */
729
-	public static function printAutoPasteLink($element_id, $choices) {
730
-		echo '<small>';
731
-		foreach ($choices as $choice) {
732
-			echo '<span onclick="document.getElementById(\'', $element_id, '\').value=';
733
-			echo '\'', $choice, '\';';
734
-			echo " return false;\">", $choice, '</span> ';
735
-		}
736
-		echo '</small>';
737
-	}
738
-
739
-	/**
740
-	 * HTML link to find a source.
741
-	 *
742
-	 * @param string $element_id
743
-	 * @param string $sourcename
744
-	 *
745
-	 * @return string
746
-	 */
747
-	public static function printFindSourceLink($element_id, $sourcename = '') {
748
-		return '<a href="#" onclick="findSource(document.getElementById(\'' . $element_id . '\'), document.getElementById(\'' . $sourcename . '\'), WT_GEDCOM); return false;" class="icon-button_source" title="' . I18N::translate('Find a source') . '"></a>';
749
-	}
750
-
751
-	/**
752
-	 * HTML link to find a note.
753
-	 *
754
-	 * @param string $element_id
755
-	 * @param string $notename
756
-	 *
757
-	 * @return string
758
-	 */
759
-	public static function printFindNoteLink($element_id, $notename = '') {
760
-		return '<a href="#" onclick="findnote(document.getElementById(\'' . $element_id . '\'), document.getElementById(\'' . $notename . '\'), \'WT_GEDCOM\'); return false;" class="icon-button_find" title="' . I18N::translate('Find a shared note') . '"></a>';
761
-	}
762
-
763
-	/**
764
-	 * HTML link to find a repository.
765
-	 *
766
-	 * @param string $element_id
767
-	 *
768
-	 * @return string
769
-	 */
770
-	public static function printFindRepositoryLink($element_id) {
771
-		return '<a href="#" onclick="findRepository(document.getElementById(\'' . $element_id . '\'), WT_GEDCOM); return false;" class="icon-button_repository" title="' . I18N::translate('Find a repository') . '"></a>';
772
-	}
773
-
774
-	/**
775
-	 * HTML link to find a media object.
776
-	 *
777
-	 * @param string $element_id
778
-	 * @param string $choose
779
-	 *
780
-	 * @return string
781
-	 */
782
-	public static function printFindMediaLink($element_id, $choose = '') {
783
-		return '<a href="#" onclick="findMedia(document.getElementById(\'' . $element_id . '\'), \'' . $choose . '\', WT_GEDCOM); return false;" class="icon-button_media" title="' . I18N::translate('Find a media object') . '"></a>';
784
-	}
785
-
786
-	/**
787
-	 * HTML link to find a fact.
788
-	 *
789
-	 * @param string $element_id
790
-	 *
791
-	 * @return string
792
-	 */
793
-	public static function printFindFactLink($element_id) {
794
-		return '<a href="#" onclick="findFact(document.getElementById(\'' . $element_id . '\'), WT_GEDCOM); return false;" class="icon-button_find_facts" title="' . I18N::translate('Find a fact or event') . '"></a>';
795
-	}
796
-
797
-	/**
798
-	 * Summary of LDS ordinances.
799
-	 *
800
-	 * @param Individual $individual
801
-	 *
802
-	 * @return string
803
-	 */
804
-	public static function getLdsSummary(Individual $individual) {
805
-		$BAPL = $individual->getFacts('BAPL') ? 'B' : '_';
806
-		$ENDL = $individual->getFacts('ENDL') ? 'E' : '_';
807
-		$SLGC = $individual->getFacts('SLGC') ? 'C' : '_';
808
-		$SLGS = '_';
809
-
810
-		foreach ($individual->getSpouseFamilies() as $family) {
811
-			if ($family->getFacts('SLGS')) {
812
-				$SLGS = '';
813
-			}
814
-		}
815
-
816
-		return $BAPL . $ENDL . $SLGS . $SLGC;
817
-	}
669
+    }
670
+
671
+    /**
672
+     * HTML link to find an individual.
673
+     *
674
+     * @param string $element_id
675
+     * @param string $indiname
676
+     * @param Tree $tree
677
+     *
678
+     * @return string
679
+     */
680
+    public static function printFindIndividualLink($element_id, $indiname = '', $tree = null) {
681
+        global $WT_TREE;
682
+
683
+        if ($tree === null) {
684
+            $tree = $WT_TREE;
685
+        }
686
+
687
+        return '<a href="#" onclick="findIndi(document.getElementById(\'' . $element_id . '\'), document.getElementById(\'' . $indiname . '\'), \'' . $tree->getNameHtml() . '\'); return false;" class="icon-button_indi" title="' . I18N::translate('Find an individual') . '"></a>';
688
+    }
689
+
690
+    /**
691
+     * HTML link to find a place.
692
+     *
693
+     * @param string $element_id
694
+     *
695
+     * @return string
696
+     */
697
+    public static function printFindPlaceLink($element_id) {
698
+        return '<a href="#" onclick="findPlace(document.getElementById(\'' . $element_id . '\'), WT_GEDCOM); return false;" class="icon-button_place" title="' . I18N::translate('Find a place') . '"></a>';
699
+    }
700
+
701
+    /**
702
+     * HTML link to find a family.
703
+     *
704
+     * @param string $element_id
705
+     *
706
+     * @return string
707
+     */
708
+    public static function printFindFamilyLink($element_id) {
709
+        return '<a href="#" onclick="findFamily(document.getElementById(\'' . $element_id . '\'), WT_GEDCOM); return false;" class="icon-button_family" title="' . I18N::translate('Find a family') . '"></a>';
710
+    }
711
+
712
+    /**
713
+     * HTML link to open the special character window.
714
+     *
715
+     * @param string $element_id
716
+     *
717
+     * @return string
718
+     */
719
+    public static function printSpecialCharacterLink($element_id) {
720
+        return '<span onclick="findSpecialChar(document.getElementById(\'' . $element_id . '\')); if (window.updatewholename) { updatewholename(); } return false;" class="icon-button_keyboard" title="' . I18N::translate('Find a special character') . '"></span>';
721
+    }
722
+
723
+    /**
724
+     * HTML element to insert a value from a list.
725
+     *
726
+     * @param string $element_id
727
+     * @param string[] $choices
728
+     */
729
+    public static function printAutoPasteLink($element_id, $choices) {
730
+        echo '<small>';
731
+        foreach ($choices as $choice) {
732
+            echo '<span onclick="document.getElementById(\'', $element_id, '\').value=';
733
+            echo '\'', $choice, '\';';
734
+            echo " return false;\">", $choice, '</span> ';
735
+        }
736
+        echo '</small>';
737
+    }
738
+
739
+    /**
740
+     * HTML link to find a source.
741
+     *
742
+     * @param string $element_id
743
+     * @param string $sourcename
744
+     *
745
+     * @return string
746
+     */
747
+    public static function printFindSourceLink($element_id, $sourcename = '') {
748
+        return '<a href="#" onclick="findSource(document.getElementById(\'' . $element_id . '\'), document.getElementById(\'' . $sourcename . '\'), WT_GEDCOM); return false;" class="icon-button_source" title="' . I18N::translate('Find a source') . '"></a>';
749
+    }
750
+
751
+    /**
752
+     * HTML link to find a note.
753
+     *
754
+     * @param string $element_id
755
+     * @param string $notename
756
+     *
757
+     * @return string
758
+     */
759
+    public static function printFindNoteLink($element_id, $notename = '') {
760
+        return '<a href="#" onclick="findnote(document.getElementById(\'' . $element_id . '\'), document.getElementById(\'' . $notename . '\'), \'WT_GEDCOM\'); return false;" class="icon-button_find" title="' . I18N::translate('Find a shared note') . '"></a>';
761
+    }
762
+
763
+    /**
764
+     * HTML link to find a repository.
765
+     *
766
+     * @param string $element_id
767
+     *
768
+     * @return string
769
+     */
770
+    public static function printFindRepositoryLink($element_id) {
771
+        return '<a href="#" onclick="findRepository(document.getElementById(\'' . $element_id . '\'), WT_GEDCOM); return false;" class="icon-button_repository" title="' . I18N::translate('Find a repository') . '"></a>';
772
+    }
773
+
774
+    /**
775
+     * HTML link to find a media object.
776
+     *
777
+     * @param string $element_id
778
+     * @param string $choose
779
+     *
780
+     * @return string
781
+     */
782
+    public static function printFindMediaLink($element_id, $choose = '') {
783
+        return '<a href="#" onclick="findMedia(document.getElementById(\'' . $element_id . '\'), \'' . $choose . '\', WT_GEDCOM); return false;" class="icon-button_media" title="' . I18N::translate('Find a media object') . '"></a>';
784
+    }
785
+
786
+    /**
787
+     * HTML link to find a fact.
788
+     *
789
+     * @param string $element_id
790
+     *
791
+     * @return string
792
+     */
793
+    public static function printFindFactLink($element_id) {
794
+        return '<a href="#" onclick="findFact(document.getElementById(\'' . $element_id . '\'), WT_GEDCOM); return false;" class="icon-button_find_facts" title="' . I18N::translate('Find a fact or event') . '"></a>';
795
+    }
796
+
797
+    /**
798
+     * Summary of LDS ordinances.
799
+     *
800
+     * @param Individual $individual
801
+     *
802
+     * @return string
803
+     */
804
+    public static function getLdsSummary(Individual $individual) {
805
+        $BAPL = $individual->getFacts('BAPL') ? 'B' : '_';
806
+        $ENDL = $individual->getFacts('ENDL') ? 'E' : '_';
807
+        $SLGC = $individual->getFacts('SLGC') ? 'C' : '_';
808
+        $SLGS = '_';
809
+
810
+        foreach ($individual->getSpouseFamilies() as $family) {
811
+            if ($family->getFacts('SLGS')) {
812
+                $SLGS = '';
813
+            }
814
+        }
815
+
816
+        return $BAPL . $ENDL . $SLGS . $SLGC;
817
+    }
818 818
 }
Please login to merge, or discard this patch.
Switch Indentation   +60 added lines, -60 removed lines patch added patch discarded remove patch
@@ -51,20 +51,20 @@  discard block
 block discarded – undo
51 51
 	public static function printPedigreePerson(Individual $person = null, $show_full = 1) {
52 52
 
53 53
 		switch ($show_full) {
54
-		case 0:
55
-			if ($person) {
56
-				echo Theme::theme()->individualBoxSmall($person);
57
-			} else {
58
-				echo Theme::theme()->individualBoxSmallEmpty();
59
-			}
60
-			break;
61
-		case 1:
62
-			if ($person) {
63
-				echo Theme::theme()->individualBox($person);
64
-			} else {
65
-				echo Theme::theme()->individualBoxEmpty();
66
-			}
67
-			break;
54
+		    case 0:
55
+			    if ($person) {
56
+				    echo Theme::theme()->individualBoxSmall($person);
57
+			    } else {
58
+				    echo Theme::theme()->individualBoxSmallEmpty();
59
+			    }
60
+			    break;
61
+		    case 1:
62
+			    if ($person) {
63
+				    echo Theme::theme()->individualBox($person);
64
+			    } else {
65
+				    echo Theme::theme()->individualBoxEmpty();
66
+			    }
67
+			    break;
68 68
 		}
69 69
 	}
70 70
 
@@ -260,25 +260,25 @@  discard block
 block discarded – undo
260 260
 					$age      = Date::getAge($parent->getBirthDate(), $birth_date, 2);
261 261
 					$deatdate = $parent->getDeathDate();
262 262
 					switch ($parent->getSex()) {
263
-					case 'F':
264
-						// Highlight mothers who die in childbirth or shortly afterwards
265
-						if ($deatdate->isOK() && $deatdate->maximumJulianDay() < $birth_date->minimumJulianDay() + 90) {
266
-							$html .= ' <span title="' . GedcomTag::getLabel('_DEAT_PARE', $parent) . '" class="parentdeath">' . $sex . $age . '</span>';
267
-						} else {
268
-							$html .= ' <span title="' . I18N::translate('Mother’s age') . '">' . $sex . $age . '</span>';
269
-						}
270
-						break;
271
-					case 'M':
272
-						// Highlight fathers who die before the birth
273
-						if ($deatdate->isOK() && $deatdate->maximumJulianDay() < $birth_date->minimumJulianDay()) {
274
-							$html .= ' <span title="' . GedcomTag::getLabel('_DEAT_PARE', $parent) . '" class="parentdeath">' . $sex . $age . '</span>';
275
-						} else {
276
-							$html .= ' <span title="' . I18N::translate('Father’s age') . '">' . $sex . $age . '</span>';
277
-						}
278
-						break;
279
-					default:
280
-						$html .= ' <span title="' . I18N::translate('Parent’s age') . '">' . $sex . $age . '</span>';
281
-						break;
263
+					    case 'F':
264
+						    // Highlight mothers who die in childbirth or shortly afterwards
265
+						    if ($deatdate->isOK() && $deatdate->maximumJulianDay() < $birth_date->minimumJulianDay() + 90) {
266
+							    $html .= ' <span title="' . GedcomTag::getLabel('_DEAT_PARE', $parent) . '" class="parentdeath">' . $sex . $age . '</span>';
267
+						    } else {
268
+							    $html .= ' <span title="' . I18N::translate('Mother’s age') . '">' . $sex . $age . '</span>';
269
+						    }
270
+						    break;
271
+					    case 'M':
272
+						    // Highlight fathers who die before the birth
273
+						    if ($deatdate->isOK() && $deatdate->maximumJulianDay() < $birth_date->minimumJulianDay()) {
274
+							    $html .= ' <span title="' . GedcomTag::getLabel('_DEAT_PARE', $parent) . '" class="parentdeath">' . $sex . $age . '</span>';
275
+						    } else {
276
+							    $html .= ' <span title="' . I18N::translate('Father’s age') . '">' . $sex . $age . '</span>';
277
+						    }
278
+						    break;
279
+					    default:
280
+						    $html .= ' <span title="' . I18N::translate('Parent’s age') . '">' . $sex . $age . '</span>';
281
+						    break;
282 282
 					}
283 283
 				}
284 284
 			}
@@ -573,33 +573,33 @@  discard block
 block discarded – undo
573 573
 
574 574
 		// -- Add from pick list
575 575
 		switch ($type) {
576
-		case "INDI":
577
-			$addfacts    = preg_split("/[, ;:]+/", $WT_TREE->getPreference('INDI_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
578
-			$uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('INDI_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
579
-			$quickfacts  = preg_split("/[, ;:]+/", $WT_TREE->getPreference('INDI_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
580
-			break;
581
-		case "FAM":
582
-			$addfacts    = preg_split("/[, ;:]+/", $WT_TREE->getPreference('FAM_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
583
-			$uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('FAM_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
584
-			$quickfacts  = preg_split("/[, ;:]+/", $WT_TREE->getPreference('FAM_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
585
-			break;
586
-		case "SOUR":
587
-			$addfacts    = preg_split("/[, ;:]+/", $WT_TREE->getPreference('SOUR_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
588
-			$uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('SOUR_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
589
-			$quickfacts  = preg_split("/[, ;:]+/", $WT_TREE->getPreference('SOUR_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
590
-			break;
591
-		case "NOTE":
592
-			$addfacts    = preg_split("/[, ;:]+/", $WT_TREE->getPreference('NOTE_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
593
-			$uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('NOTE_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
594
-			$quickfacts  = preg_split("/[, ;:]+/", $WT_TREE->getPreference('NOTE_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
595
-			break;
596
-		case "REPO":
597
-			$addfacts    = preg_split("/[, ;:]+/", $WT_TREE->getPreference('REPO_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
598
-			$uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('REPO_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
599
-			$quickfacts  = preg_split("/[, ;:]+/", $WT_TREE->getPreference('REPO_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
600
-			break;
601
-		default:
602
-			return;
576
+		    case "INDI":
577
+			    $addfacts    = preg_split("/[, ;:]+/", $WT_TREE->getPreference('INDI_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
578
+			    $uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('INDI_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
579
+			    $quickfacts  = preg_split("/[, ;:]+/", $WT_TREE->getPreference('INDI_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
580
+			    break;
581
+		    case "FAM":
582
+			    $addfacts    = preg_split("/[, ;:]+/", $WT_TREE->getPreference('FAM_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
583
+			    $uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('FAM_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
584
+			    $quickfacts  = preg_split("/[, ;:]+/", $WT_TREE->getPreference('FAM_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
585
+			    break;
586
+		    case "SOUR":
587
+			    $addfacts    = preg_split("/[, ;:]+/", $WT_TREE->getPreference('SOUR_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
588
+			    $uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('SOUR_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
589
+			    $quickfacts  = preg_split("/[, ;:]+/", $WT_TREE->getPreference('SOUR_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
590
+			    break;
591
+		    case "NOTE":
592
+			    $addfacts    = preg_split("/[, ;:]+/", $WT_TREE->getPreference('NOTE_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
593
+			    $uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('NOTE_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
594
+			    $quickfacts  = preg_split("/[, ;:]+/", $WT_TREE->getPreference('NOTE_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
595
+			    break;
596
+		    case "REPO":
597
+			    $addfacts    = preg_split("/[, ;:]+/", $WT_TREE->getPreference('REPO_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
598
+			    $uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('REPO_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
599
+			    $quickfacts  = preg_split("/[, ;:]+/", $WT_TREE->getPreference('REPO_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
600
+			    break;
601
+		    default:
602
+			    return;
603 603
 		}
604 604
 		$addfacts            = array_merge(self::checkFactUnique($uniquefacts, $usedfacts, $type), $addfacts);
605 605
 		$quickfacts          = array_intersect($quickfacts, $addfacts);
Please login to merge, or discard this patch.
Braces   +48 added lines, -24 removed lines patch added patch discarded remove patch
@@ -39,7 +39,8 @@  discard block
 block discarded – undo
39 39
 /**
40 40
  * Class FunctionsPrint - common functions
41 41
  */
42
-class FunctionsPrint {
42
+class FunctionsPrint
43
+{
43 44
 	/**
44 45
 	 * print the information for an individual chart box
45 46
 	 *
@@ -48,7 +49,8 @@  discard block
 block discarded – undo
48 49
 	 * @param Individual $person The person to print
49 50
 	 * @param int $show_full The style to print the box in, 0 for smaller boxes, 1 for larger boxes
50 51
 	 */
51
-	public static function printPedigreePerson(Individual $person = null, $show_full = 1) {
52
+	public static function printPedigreePerson(Individual $person = null, $show_full = 1)
53
+	{
52 54
 
53 55
 		switch ($show_full) {
54 56
 		case 0:
@@ -78,7 +80,8 @@  discard block
 block discarded – undo
78 80
 	 *
79 81
 	 * @return string
80 82
 	 */
81
-	public static function printNoteRecord($text, $nlevel, $nrec, $textOnly = false) {
83
+	public static function printNoteRecord($text, $nlevel, $nrec, $textOnly = false)
84
+	{
82 85
 		global $WT_TREE;
83 86
 
84 87
 		$text .= Functions::getCont($nlevel, $nrec);
@@ -137,7 +140,8 @@  discard block
 block discarded – undo
137 140
 	 *
138 141
 	 * @return string HTML
139 142
 	 */
140
-	public static function printFactNotes($factrec, $level, $textOnly = false) {
143
+	public static function printFactNotes($factrec, $level, $textOnly = false)
144
+	{
141 145
 		global $WT_TREE;
142 146
 
143 147
 		$data          = '';
@@ -194,7 +198,8 @@  discard block
 block discarded – undo
194 198
 	 *
195 199
 	 * @return string
196 200
 	 */
197
-	public static function helpLink($help_topic, $module = '') {
201
+	public static function helpLink($help_topic, $module = '')
202
+	{
198 203
 		return '<span class="icon-help" onclick="helpDialog(\'' . $help_topic . '\',\'' . $module . '\'); return false;">&nbsp;</span>';
199 204
 	}
200 205
 
@@ -207,7 +212,8 @@  discard block
 block discarded – undo
207 212
 	 *
208 213
 	 * @return string
209 214
 	 */
210
-	public static function wikiHelpLink($topic) {
215
+	public static function wikiHelpLink($topic)
216
+	{
211 217
 		return '<a class="help icon-wiki" href="' . WT_WEBTREES_WIKI . $topic . '" title="' . I18N::translate('webtrees wiki') . '"></a>';
212 218
 	}
213 219
 
@@ -219,7 +225,8 @@  discard block
 block discarded – undo
219 225
 	 *
220 226
 	 * @return string
221 227
 	 */
222
-	public static function highlightSearchHits($string) {
228
+	public static function highlightSearchHits($string)
229
+	{
223 230
 		global $controller;
224 231
 
225 232
 		if ($controller instanceof SearchController && $controller->query) {
@@ -248,7 +255,8 @@  discard block
 block discarded – undo
248 255
 	 *
249 256
 	 * @return string HTML
250 257
 	 */
251
-	public static function formatParentsAges(Individual $person, Date $birth_date) {
258
+	public static function formatParentsAges(Individual $person, Date $birth_date)
259
+	{
252 260
 		$html     = '';
253 261
 		$families = $person->getChildFamilies();
254 262
 		// Multiple sets of parents (e.g. adoption) cause complications, so ignore.
@@ -300,7 +308,8 @@  discard block
 block discarded – undo
300 308
 	 *
301 309
 	 * @return string
302 310
 	 */
303
-	public static function formatFactDate(Fact $event, GedcomRecord $record, $anchor, $time) {
311
+	public static function formatFactDate(Fact $event, GedcomRecord $record, $anchor, $time)
312
+	{
304 313
 		global $pid;
305 314
 
306 315
 		$factrec = $event->getGedcom();
@@ -433,7 +442,8 @@  discard block
 block discarded – undo
433 442
 	 *
434 443
 	 * @return string HTML
435 444
 	 */
436
-	public static function formatFactPlace(Fact $event, $anchor = false, $sub_records = false, $lds = false) {
445
+	public static function formatFactPlace(Fact $event, $anchor = false, $sub_records = false, $lds = false)
446
+	{
437 447
 		if ($anchor) {
438 448
 			// Show the full place name, for facts/events tab
439 449
 			$html = '<a href="' . $event->getPlace()->getURL() . '">' . $event->getPlace()->getFullName() . '</a>';
@@ -501,7 +511,8 @@  discard block
 block discarded – undo
501 511
 	 *
502 512
 	 * @return string[]
503 513
 	 */
504
-	public static function checkFactUnique($uniquefacts, $recfacts, $type) {
514
+	public static function checkFactUnique($uniquefacts, $recfacts, $type)
515
+	{
505 516
 		foreach ($recfacts as $factarray) {
506 517
 			$fact = false;
507 518
 			if (is_object($factarray)) {
@@ -537,7 +548,8 @@  discard block
 block discarded – undo
537 548
 	 * @param array $usedfacts an array of facts already used in this record
538 549
 	 * @param string $type the type of record INDI, FAM, SOUR etc
539 550
 	 */
540
-	public static function printAddNewFact($id, $usedfacts, $type) {
551
+	public static function printAddNewFact($id, $usedfacts, $type)
552
+	{
541 553
 		global $WT_TREE;
542 554
 
543 555
 		// -- Add from clipboard
@@ -637,7 +649,8 @@  discard block
 block discarded – undo
637 649
 	/**
638 650
 	 * javascript declaration for calendar popup
639 651
 	 */
640
-	public static function initializeCalendarPopup() {
652
+	public static function initializeCalendarPopup()
653
+	{
641 654
 		global $controller;
642 655
 
643 656
 		$controller->addInlineJavascript('
@@ -677,7 +690,8 @@  discard block
 block discarded – undo
677 690
 	 *
678 691
 	 * @return string
679 692
 	 */
680
-	public static function printFindIndividualLink($element_id, $indiname = '', $tree = null) {
693
+	public static function printFindIndividualLink($element_id, $indiname = '', $tree = null)
694
+	{
681 695
 		global $WT_TREE;
682 696
 
683 697
 		if ($tree === null) {
@@ -694,7 +708,8 @@  discard block
 block discarded – undo
694 708
 	 *
695 709
 	 * @return string
696 710
 	 */
697
-	public static function printFindPlaceLink($element_id) {
711
+	public static function printFindPlaceLink($element_id)
712
+	{
698 713
 		return '<a href="#" onclick="findPlace(document.getElementById(\'' . $element_id . '\'), WT_GEDCOM); return false;" class="icon-button_place" title="' . I18N::translate('Find a place') . '"></a>';
699 714
 	}
700 715
 
@@ -705,7 +720,8 @@  discard block
 block discarded – undo
705 720
 	 *
706 721
 	 * @return string
707 722
 	 */
708
-	public static function printFindFamilyLink($element_id) {
723
+	public static function printFindFamilyLink($element_id)
724
+	{
709 725
 		return '<a href="#" onclick="findFamily(document.getElementById(\'' . $element_id . '\'), WT_GEDCOM); return false;" class="icon-button_family" title="' . I18N::translate('Find a family') . '"></a>';
710 726
 	}
711 727
 
@@ -716,7 +732,8 @@  discard block
 block discarded – undo
716 732
 	 *
717 733
 	 * @return string
718 734
 	 */
719
-	public static function printSpecialCharacterLink($element_id) {
735
+	public static function printSpecialCharacterLink($element_id)
736
+	{
720 737
 		return '<span onclick="findSpecialChar(document.getElementById(\'' . $element_id . '\')); if (window.updatewholename) { updatewholename(); } return false;" class="icon-button_keyboard" title="' . I18N::translate('Find a special character') . '"></span>';
721 738
 	}
722 739
 
@@ -726,7 +743,8 @@  discard block
 block discarded – undo
726 743
 	 * @param string $element_id
727 744
 	 * @param string[] $choices
728 745
 	 */
729
-	public static function printAutoPasteLink($element_id, $choices) {
746
+	public static function printAutoPasteLink($element_id, $choices)
747
+	{
730 748
 		echo '<small>';
731 749
 		foreach ($choices as $choice) {
732 750
 			echo '<span onclick="document.getElementById(\'', $element_id, '\').value=';
@@ -744,7 +762,8 @@  discard block
 block discarded – undo
744 762
 	 *
745 763
 	 * @return string
746 764
 	 */
747
-	public static function printFindSourceLink($element_id, $sourcename = '') {
765
+	public static function printFindSourceLink($element_id, $sourcename = '')
766
+	{
748 767
 		return '<a href="#" onclick="findSource(document.getElementById(\'' . $element_id . '\'), document.getElementById(\'' . $sourcename . '\'), WT_GEDCOM); return false;" class="icon-button_source" title="' . I18N::translate('Find a source') . '"></a>';
749 768
 	}
750 769
 
@@ -756,7 +775,8 @@  discard block
 block discarded – undo
756 775
 	 *
757 776
 	 * @return string
758 777
 	 */
759
-	public static function printFindNoteLink($element_id, $notename = '') {
778
+	public static function printFindNoteLink($element_id, $notename = '')
779
+	{
760 780
 		return '<a href="#" onclick="findnote(document.getElementById(\'' . $element_id . '\'), document.getElementById(\'' . $notename . '\'), \'WT_GEDCOM\'); return false;" class="icon-button_find" title="' . I18N::translate('Find a shared note') . '"></a>';
761 781
 	}
762 782
 
@@ -767,7 +787,8 @@  discard block
 block discarded – undo
767 787
 	 *
768 788
 	 * @return string
769 789
 	 */
770
-	public static function printFindRepositoryLink($element_id) {
790
+	public static function printFindRepositoryLink($element_id)
791
+	{
771 792
 		return '<a href="#" onclick="findRepository(document.getElementById(\'' . $element_id . '\'), WT_GEDCOM); return false;" class="icon-button_repository" title="' . I18N::translate('Find a repository') . '"></a>';
772 793
 	}
773 794
 
@@ -779,7 +800,8 @@  discard block
 block discarded – undo
779 800
 	 *
780 801
 	 * @return string
781 802
 	 */
782
-	public static function printFindMediaLink($element_id, $choose = '') {
803
+	public static function printFindMediaLink($element_id, $choose = '')
804
+	{
783 805
 		return '<a href="#" onclick="findMedia(document.getElementById(\'' . $element_id . '\'), \'' . $choose . '\', WT_GEDCOM); return false;" class="icon-button_media" title="' . I18N::translate('Find a media object') . '"></a>';
784 806
 	}
785 807
 
@@ -790,7 +812,8 @@  discard block
 block discarded – undo
790 812
 	 *
791 813
 	 * @return string
792 814
 	 */
793
-	public static function printFindFactLink($element_id) {
815
+	public static function printFindFactLink($element_id)
816
+	{
794 817
 		return '<a href="#" onclick="findFact(document.getElementById(\'' . $element_id . '\'), WT_GEDCOM); return false;" class="icon-button_find_facts" title="' . I18N::translate('Find a fact or event') . '"></a>';
795 818
 	}
796 819
 
@@ -801,7 +824,8 @@  discard block
 block discarded – undo
801 824
 	 *
802 825
 	 * @return string
803 826
 	 */
804
-	public static function getLdsSummary(Individual $individual) {
827
+	public static function getLdsSummary(Individual $individual)
828
+	{
805 829
 		$BAPL = $individual->getFacts('BAPL') ? 'B' : '_';
806 830
 		$ENDL = $individual->getFacts('ENDL') ? 'E' : '_';
807 831
 		$SLGC = $individual->getFacts('SLGC') ? 'C' : '_';
Please login to merge, or discard this patch.
app/Functions/FunctionsEdit.php 3 patches
Indentation   +1733 added lines, -1733 removed lines patch added patch discarded remove patch
@@ -53,953 +53,953 @@  discard block
 block discarded – undo
53 53
  * Class FunctionsEdit - common functions
54 54
  */
55 55
 class FunctionsEdit {
56
-	/**
57
-	 * Create a <select> control for a form.
58
-	 *
59
-	 * @param string $name
60
-	 * @param string[] $values
61
-	 * @param string|null $empty
62
-	 * @param string $selected
63
-	 * @param string $extra
64
-	 *
65
-	 * @return string
66
-	 */
67
-	public static function selectEditControl($name, $values, $empty, $selected, $extra = '') {
68
-		if (is_null($empty)) {
69
-			$html = '';
70
-		} else {
71
-			if (empty($selected)) {
72
-				$html = '<option value="" selected>' . Filter::escapeHtml($empty) . '</option>';
73
-			} else {
74
-				$html = '<option value="">' . Filter::escapeHtml($empty) . '</option>';
75
-			}
76
-		}
77
-		// A completely empty list would be invalid, and break various things
78
-		if (empty($values) && empty($html)) {
79
-			$html = '<option value=""></option>';
80
-		}
81
-		foreach ($values as $key => $value) {
82
-			// PHP array keys are cast to integers!  Cast them back
83
-			if ((string) $key === (string) $selected) {
84
-				$html .= '<option value="' . Filter::escapeHtml($key) . '" selected dir="auto">' . Filter::escapeHtml($value) . '</option>';
85
-			} else {
86
-				$html .= '<option value="' . Filter::escapeHtml($key) . '" dir="auto">' . Filter::escapeHtml($value) . '</option>';
87
-			}
88
-		}
89
-		if (substr($name, -2) === '[]') {
90
-			// id attribute is not used for arrays
91
-			return '<select name="' . $name . '" ' . $extra . '>' . $html . '</select>';
92
-		} else {
93
-			return '<select id="' . $name . '" name="' . $name . '" ' . $extra . '>' . $html . '</select>';
94
-		}
95
-	}
96
-
97
-	/**
98
-	 * Create a set of radio buttons for a form
99
-	 *
100
-	 * @param string $name The ID for the form element
101
-	 * @param string[] $values Array of value=>display items
102
-	 * @param string $selected The currently selected item
103
-	 * @param string $extra Additional markup for the label
104
-	 *
105
-	 * @return string
106
-	 */
107
-	public static function radioButtons($name, $values, $selected, $extra = '') {
108
-		$html = '';
109
-		foreach ($values as $key => $value) {
110
-			$html .=
111
-				'<label ' . $extra . '>' .
112
-				'<input type="radio" name="' . $name . '" value="' . Filter::escapeHtml($key) . '"';
113
-			// PHP array keys are cast to integers!  Cast them back
114
-			if ((string) $key === (string) $selected) {
115
-				$html .= ' checked';
116
-			}
117
-			$html .= '>' . Filter::escapeHtml($value) . '</label>';
118
-		}
119
-
120
-		return $html;
121
-	}
122
-
123
-	/**
124
-	 * Print an edit control for a Yes/No field
125
-	 *
126
-	 * @param string $name
127
-	 * @param bool $selected
128
-	 * @param string $extra
129
-	 *
130
-	 * @return string
131
-	 */
132
-	public static function editFieldYesNo($name, $selected = false, $extra = '') {
133
-		return self::radioButtons(
134
-			$name, array(I18N::translate('no'), I18N::translate('yes')), $selected, $extra
135
-		);
136
-	}
137
-
138
-	/**
139
-	 * Print an edit control for a checkbox.
140
-	 *
141
-	 * @param string $name
142
-	 * @param bool $is_checked
143
-	 * @param string $extra
144
-	 *
145
-	 * @return string
146
-	 */
147
-	public static function checkbox($name, $is_checked = false, $extra = '') {
148
-		return '<input type="checkbox" name="' . $name . '" value="1" ' . ($is_checked ? 'checked ' : '') . $extra . '>';
149
-	}
150
-
151
-	/**
152
-	 * Print an edit control for a checkbox, with a hidden field to store one of the two states.
153
-	 * By default, a checkbox is either set, or not sent.
154
-	 * This function gives us a three options, set, unset or not sent.
155
-	 * Useful for dynamically generated forms where we don't know what elements are present.
156
-	 *
157
-	 * @param string $name
158
-	 * @param int $is_checked 0 or 1
159
-	 * @param string $extra
160
-	 *
161
-	 * @return string
162
-	 */
163
-	public static function twoStateCheckbox($name, $is_checked = 0, $extra = '') {
164
-		return
165
-			'<input type="hidden" id="' . $name . '" name="' . $name . '" value="' . ($is_checked ? 1 : 0) . '">' .
166
-			'<input type="checkbox" name="' . $name . '-GUI-ONLY" value="1"' .
167
-			($is_checked ? ' checked' : '') .
168
-			' onclick="document.getElementById(\'' . $name . '\').value=(this.checked?1:0);" ' . $extra . '>';
169
-	}
170
-
171
-	/**
172
-	 * Function edit_language_checkboxes
173
-	 *
174
-	 * @param string $parameter_name
175
-	 * @param array $accepted_languages
176
-	 *
177
-	 * @return string
178
-	 */
179
-	public static function editLanguageCheckboxes($parameter_name, $accepted_languages) {
180
-		$html = '';
181
-		foreach (I18N::activeLocales() as $locale) {
182
-			$html .= '<div class="checkbox">';
183
-			$html .= '<label title="' . $locale->languageTag() . '">';
184
-			$html .= '<input type="checkbox" name="' . $parameter_name . '[]" value="' . $locale->languageTag() . '"';
185
-			$html .= in_array($locale->languageTag(), $accepted_languages) ? ' checked>' : '>';
186
-			$html .= $locale->endonym();
187
-			$html .= '</label>';
188
-			$html .= '</div>';
189
-		}
190
-
191
-		return $html;
192
-	}
193
-
194
-	/**
195
-	 * Print an edit control for access level.
196
-	 *
197
-	 * @param string $name
198
-	 * @param string $selected
199
-	 * @param string $extra
200
-	 *
201
-	 * @return string
202
-	 */
203
-	public static function editFieldAccessLevel($name, $selected = '', $extra = '') {
204
-		$ACCESS_LEVEL = array(
205
-			Auth::PRIV_PRIVATE => I18N::translate('Show to visitors'),
206
-			Auth::PRIV_USER    => I18N::translate('Show to members'),
207
-			Auth::PRIV_NONE    => I18N::translate('Show to managers'),
208
-			Auth::PRIV_HIDE    => I18N::translate('Hide from everyone'),
209
-		);
210
-
211
-		return self::selectEditControl($name, $ACCESS_LEVEL, null, $selected, $extra);
212
-	}
213
-
214
-	/**
215
-	 * Print an edit control for a RESN field.
216
-	 *
217
-	 * @param string $name
218
-	 * @param string $selected
219
-	 * @param string $extra
220
-	 *
221
-	 * @return string
222
-	 */
223
-	public static function editFieldRestriction($name, $selected = '', $extra = '') {
224
-		$RESN = array(
225
-			''             => '',
226
-			'none'         => I18N::translate('Show to visitors'), // Not valid GEDCOM, but very useful
227
-			'privacy'      => I18N::translate('Show to members'),
228
-			'confidential' => I18N::translate('Show to managers'),
229
-			'locked'       => I18N::translate('Only managers can edit'),
230
-		);
231
-
232
-		return self::selectEditControl($name, $RESN, null, $selected, $extra);
233
-	}
234
-
235
-	/**
236
-	 * Print an edit control for a contact method field.
237
-	 *
238
-	 * @param string $name
239
-	 * @param string $selected
240
-	 * @param string $extra
241
-	 *
242
-	 * @return string
243
-	 */
244
-	public static function editFieldContact($name, $selected = '', $extra = '') {
245
-		// Different ways to contact the users
246
-		$CONTACT_METHODS = array(
247
-			'messaging'  => I18N::translate('Internal messaging'),
248
-			'messaging2' => I18N::translate('Internal messaging with emails'),
249
-			'messaging3' => I18N::translate('webtrees sends emails with no storage'),
250
-			'mailto'     => I18N::translate('Mailto link'),
251
-			'none'       => I18N::translate('No contact'),
252
-		);
253
-
254
-		return self::selectEditControl($name, $CONTACT_METHODS, null, $selected, $extra);
255
-	}
256
-
257
-	/**
258
-	 * Print an edit control for a language field.
259
-	 *
260
-	 * @param string $name
261
-	 * @param string $selected
262
-	 * @param string $extra
263
-	 *
264
-	 * @return string
265
-	 */
266
-	public static function editFieldLanguage($name, $selected = '', $extra = '') {
267
-		$languages = array();
268
-		foreach (I18N::activeLocales() as $locale) {
269
-			$languages[$locale->languageTag()] = $locale->endonym();
270
-		}
271
-
272
-		return self::selectEditControl($name, $languages, null, $selected, $extra);
273
-	}
274
-
275
-	/**
276
-	 * Print an edit control for a range of integers.
277
-	 *
278
-	 * @param string $name
279
-	 * @param string $selected
280
-	 * @param int $min
281
-	 * @param int $max
282
-	 * @param string $extra
283
-	 *
284
-	 * @return string
285
-	 */
286
-	public static function editFieldInteger($name, $selected = '', $min, $max, $extra = '') {
287
-		$array = array();
288
-		for ($i = $min; $i <= $max; ++$i) {
289
-			$array[$i] = I18N::number($i);
290
-		}
291
-
292
-		return self::selectEditControl($name, $array, null, $selected, $extra);
293
-	}
294
-
295
-	/**
296
-	 * Print an edit control for a username.
297
-	 *
298
-	 * @param string $name
299
-	 * @param string $selected
300
-	 * @param string $extra
301
-	 *
302
-	 * @return string
303
-	 */
304
-	public static function editFieldUsername($name, $selected = '', $extra = '') {
305
-		$users = array();
306
-		foreach (User::all() as $user) {
307
-			$users[$user->getUserName()] = $user->getRealName() . ' - ' . $user->getUserName();
308
-		}
309
-		// The currently selected user may not exist
310
-		if ($selected && !array_key_exists($selected, $users)) {
311
-			$users[$selected] = $selected;
312
-		}
313
-
314
-		return self::selectEditControl($name, $users, '-', $selected, $extra);
315
-	}
316
-
317
-	/**
318
-	 * Print an edit control for a ADOP field.
319
-	 *
320
-	 * @param string          $name
321
-	 * @param string          $selected
322
-	 * @param string          $extra
323
-	 * @param Individual|null $individual
324
-	 *
325
-	 * @return string
326
-	 */
327
-	public static function editFieldAdoption($name, $selected = '', $extra = '', Individual $individual = null) {
328
-		return self::selectEditControl($name, GedcomCodeAdop::getValues($individual), null, $selected, $extra);
329
-	}
330
-
331
-	/**
332
-	 * Print an edit control for a PEDI field.
333
-	 *
334
-	 * @param string          $name
335
-	 * @param string          $selected
336
-	 * @param string          $extra
337
-	 * @param Individual|null $individual
338
-	 *
339
-	 * @return string
340
-	 */
341
-	public static function editFieldPedigree($name, $selected = '', $extra = '', Individual $individual = null) {
342
-		return self::selectEditControl($name, GedcomCodePedi::getValues($individual), '', $selected, $extra);
343
-	}
344
-
345
-	/**
346
-	 * Print an edit control for a NAME TYPE field.
347
-	 *
348
-	 * @param string          $name
349
-	 * @param string          $selected
350
-	 * @param string          $extra
351
-	 * @param Individual|null $individual
352
-	 *
353
-	 * @return string
354
-	 */
355
-	public static function editFieldNameType($name, $selected = '', $extra = '', Individual $individual = null) {
356
-		return self::selectEditControl($name, GedcomCodeName::getValues($individual), '', $selected, $extra);
357
-	}
358
-
359
-	/**
360
-	 * Print an edit control for a RELA field.
361
-	 *
362
-	 * @param string $name
363
-	 * @param string $selected
364
-	 * @param string $extra
365
-	 *
366
-	 * @return string
367
-	 */
368
-	public static function editFieldRelationship($name, $selected = '', $extra = '') {
369
-		$rela_codes = GedcomCodeRela::getValues();
370
-		// The user is allowed to specify values that aren't in the list.
371
-		if (!array_key_exists($selected, $rela_codes)) {
372
-			$rela_codes[$selected] = I18N::translate($selected);
373
-		}
374
-
375
-		return self::selectEditControl($name, $rela_codes, '', $selected, $extra);
376
-	}
377
-
378
-	/**
379
-	 * Remove all links from $gedrec to $xref, and any sub-tags.
380
-	 *
381
-	 * @param string $gedrec
382
-	 * @param string $xref
383
-	 *
384
-	 * @return string
385
-	 */
386
-	public static function removeLinks($gedrec, $xref) {
387
-		$gedrec = preg_replace('/\n1 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[2-9].*)*/', '', $gedrec);
388
-		$gedrec = preg_replace('/\n2 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[3-9].*)*/', '', $gedrec);
389
-		$gedrec = preg_replace('/\n3 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[4-9].*)*/', '', $gedrec);
390
-		$gedrec = preg_replace('/\n4 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[5-9].*)*/', '', $gedrec);
391
-		$gedrec = preg_replace('/\n5 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[6-9].*)*/', '', $gedrec);
392
-
393
-		return $gedrec;
394
-	}
395
-
396
-	/**
397
-	 * Generates javascript code for calendar popup in user’s language.
398
-	 *
399
-	 * @param string $id
400
-	 *
401
-	 * @return string
402
-	 */
403
-	public static function printCalendarPopup($id) {
404
-		return
405
-			' <a href="#" onclick="cal_toggleDate(\'caldiv' . $id . '\', \'' . $id . '\'); return false;" class="icon-button_calendar" title="' . I18N::translate('Select a date') . '"></a>' .
406
-			'<div id="caldiv' . $id . '" style="position:absolute;visibility:hidden;background-color:white;z-index:1000;"></div>';
407
-	}
408
-
409
-	/**
410
-	 * An HTML link to create a new media object.
411
-	 *
412
-	 * @param string $element_id
413
-	 *
414
-	 * @return string
415
-	 */
416
-	public static function printAddNewMediaLink($element_id) {
417
-		return '<a href="#" onclick="pastefield=document.getElementById(\'' . $element_id . '\'); window.open(\'addmedia.php?action=showmediaform\', \'_blank\', edit_window_specs); return false;" class="icon-button_addmedia" title="' . I18N::translate('Create a media object') . '"></a>';
418
-	}
419
-
420
-	/**
421
-	 * An HTML link to create a new repository.
422
-	 *
423
-	 * @param string $element_id
424
-	 *
425
-	 * @return string
426
-	 */
427
-	public static function printAddNewRepositoryLink($element_id) {
428
-		return '<a href="#" onclick="addnewrepository(document.getElementById(\'' . $element_id . '\')); return false;" class="icon-button_addrepository" title="' . I18N::translate('Create a repository') . '"></a>';
429
-	}
430
-
431
-	/**
432
-	 * An HTML link to create a new note.
433
-	 *
434
-	 * @param string $element_id
435
-	 *
436
-	 * @return string
437
-	 */
438
-	public static function printAddNewNoteLink($element_id) {
439
-		return '<a href="#" onclick="addnewnote(document.getElementById(\'' . $element_id . '\')); return false;" class="icon-button_addnote" title="' . I18N::translate('Create a shared note') . '"></a>';
440
-	}
441
-
442
-	/**
443
-	 * An HTML link to edit a note.
444
-	 *
445
-	 * @param string $note_id
446
-	 *
447
-	 * @return string
448
-	 */
449
-	public static function printEditNoteLink($note_id) {
450
-		return '<a href="#" onclick="edit_note(\'' . $note_id . '\'); return false;" class="icon-button_note" title="' . I18N::translate('Edit the shared note') . '"></a>';
451
-	}
452
-
453
-	/**
454
-	 * An HTML link to create a new source.
455
-	 *
456
-	 * @param string $element_id
457
-	 *
458
-	 * @return string
459
-	 */
460
-	public static function printAddNewSourceLink($element_id) {
461
-		return '<a href="#" onclick="addnewsource(document.getElementById(\'' . $element_id . '\')); return false;" class="icon-button_addsource" title="' . I18N::translate('Create a source') . '"></a>';
462
-	}
463
-
464
-	/**
465
-	 * add a new tag input field
466
-	 *
467
-	 * called for each fact to be edited on a form.
468
-	 * Fact level=0 means a new empty form : data are POSTed by name
469
-	 * else data are POSTed using arrays :
470
-	 * glevels[] : tag level
471
-	 *  islink[] : tag is a link
472
-	 *     tag[] : tag name
473
-	 *    text[] : tag value
474
-	 *
475
-	 * @param string $tag fact record to edit (eg 2 DATE xxxxx)
476
-	 * @param string $upperlevel optional upper level tag (eg BIRT)
477
-	 * @param string $label An optional label to echo instead of the default
478
-	 * @param string $extra optional text to display after the input field
479
-	 * @param Individual $person For male/female translations
480
-	 *
481
-	 * @return string
482
-	 */
483
-	public static function addSimpleTag($tag, $upperlevel = '', $label = '', $extra = null, Individual $person = null) {
484
-		global $tags, $main_fact, $xref, $bdm, $action, $WT_TREE;
485
-
486
-		// Keep track of SOUR fields, so we can reference them in subsequent PAGE fields.
487
-		static $source_element_id;
488
-
489
-		$subnamefacts = array('NPFX', 'GIVN', 'SPFX', 'SURN', 'NSFX', '_MARNM_SURN');
490
-		preg_match('/^(?:(\d+) (' . WT_REGEX_TAG . ') ?(.*))/', $tag, $match);
491
-		list(, $level, $fact, $value) = $match;
492
-		$level                        = (int) $level;
493
-
494
-		// element name : used to POST data
495
-		if ($level === 0) {
496
-			if ($upperlevel) {
497
-				$element_name = $upperlevel . '_' . $fact;
498
-			} else {
499
-				$element_name = $fact;
500
-			}
501
-		} else {
502
-			$element_name = 'text[]';
503
-		}
504
-		if ($level === 1) {
505
-			$main_fact = $fact;
506
-		}
507
-
508
-		// element id : used by javascript functions
509
-		if ($level === 0) {
510
-			$element_id = $fact;
511
-		} else {
512
-			$element_id = $fact . Uuid::uuid4();
513
-		}
514
-		if ($upperlevel) {
515
-			$element_id = $upperlevel . '_' . $fact . Uuid::uuid4();
516
-		}
517
-
518
-		// field value
519
-		$islink = (substr($value, 0, 1) === '@' && substr($value, 0, 2) !== '@#');
520
-		if ($islink) {
521
-			$value = trim(substr($tag, strlen($fact) + 3), '@');
522
-		} else {
523
-			$value = (string) substr($tag, strlen($fact) + 3);
524
-		}
525
-		if ($fact === 'REPO' || $fact === 'SOUR' || $fact === 'OBJE' || $fact === 'FAMC') {
526
-			$islink = true;
527
-		}
528
-
529
-		if ($fact === 'SHARED_NOTE_EDIT' || $fact === 'SHARED_NOTE') {
530
-			$islink = true;
531
-			$fact   = 'NOTE';
532
-		}
533
-
534
-		// label
535
-		echo '<tr id="', $element_id, '_tr"';
536
-		if ($fact === 'DATA' || $fact === 'MAP' || ($fact === 'LATI' || $fact === 'LONG') && $value === '') {
537
-			echo ' style="display:none;"';
538
-		}
539
-		echo '>';
540
-
541
-		if (in_array($fact, $subnamefacts) || $fact === 'LATI' || $fact === 'LONG') {
542
-			echo '<td class="optionbox wrap width25">';
543
-		} else {
544
-			echo '<td class="descriptionbox wrap width25">';
545
-		}
546
-
547
-		// tag name
548
-		if ($label) {
549
-			echo $label;
550
-		} elseif ($upperlevel) {
551
-			echo GedcomTag::getLabel($upperlevel . ':' . $fact);
552
-		} else {
553
-			echo GedcomTag::getLabel($fact);
554
-		}
555
-
556
-		// If using GEDFact-assistant window
557
-		if ($action === 'addnewnote_assisted') {
558
-			// Do not print on GEDFact Assistant window
559
-		} else {
560
-			// Not all facts have help text.
561
-			switch ($fact) {
562
-			case 'NAME':
563
-				if ($upperlevel !== 'REPO' && $upperlevel !== 'UNKNOWN') {
564
-					echo FunctionsPrint::helpLink($fact);
565
-				}
566
-				break;
567
-			case 'DATE':
568
-			case 'PLAC':
569
-			case 'RESN':
570
-			case 'ROMN':
571
-			case 'SURN':
572
-			case '_HEB':
573
-				echo FunctionsPrint::helpLink($fact);
574
-				break;
575
-			}
576
-		}
577
-		// tag level
578
-		if ($level > 0) {
579
-			echo '<input type="hidden" name="glevels[]" value="', $level, '">';
580
-			echo '<input type="hidden" name="islink[]" value="', $islink, '">';
581
-			echo '<input type="hidden" name="tag[]" value="', $fact, '">';
582
-		}
583
-		echo '</td>';
584
-
585
-		// value
586
-		echo '<td class="optionbox wrap">';
587
-
588
-		// retrieve linked NOTE
589
-		if ($fact === 'NOTE' && $islink) {
590
-			$note1 = Note::getInstance($value, $WT_TREE);
591
-			if ($note1) {
592
-				$noterec = $note1->getGedcom();
593
-				preg_match('/' . $value . '/i', $noterec, $notematch);
594
-				$value = $notematch[0];
595
-			}
596
-		}
597
-
598
-		// Show names for spouses in MARR/HUSB/AGE and MARR/WIFE/AGE
599
-		if ($fact === 'HUSB' || $fact === 'WIFE') {
600
-			$family = Family::getInstance($xref, $WT_TREE);
601
-			if ($family) {
602
-				$spouse_link = $family->getFirstFact($fact);
603
-				if ($spouse_link) {
604
-					$spouse = $spouse_link->getTarget();
605
-					if ($spouse) {
606
-						echo $spouse->getFullName();
607
-					}
608
-				}
609
-			}
610
-		}
611
-
612
-		if (in_array($fact, Config::emptyFacts()) && ($value === '' || $value === 'Y' || $value === 'y')) {
613
-			echo '<input type="hidden" id="', $element_id, '" name="', $element_name, '" value="', $value, '">';
614
-			if ($level <= 1) {
615
-				echo '<input type="checkbox" ';
616
-				if ($value) {
617
-					echo 'checked';
618
-				}
619
-				echo ' onclick="document.getElementById(\'' . $element_id . '\').value = (this.checked) ? \'Y\' : \'\';">';
620
-				echo I18N::translate('yes');
621
-			}
622
-
623
-			if ($fact === 'CENS' && $value === 'Y') {
624
-				echo self::censusDateSelector(WT_LOCALE, $xref);
625
-				if (Module::getModuleByName('GEDFact_assistant') && GedcomRecord::getInstance($xref, $WT_TREE) instanceof Individual) {
626
-					echo
627
-						'<div></div><a href="#" style="display: none;" id="assistant-link" onclick="return activateCensusAssistant();">' .
628
-						I18N::translate('Create a shared note using the census assistant') .
629
-						'</a></div>';
630
-				}
631
-			}
632
-
633
-		} elseif ($fact === 'TEMP') {
634
-			echo self::selectEditControl($element_name, GedcomCodeTemp::templeNames(), I18N::translate('No temple - living ordinance'), $value);
635
-		} elseif ($fact === 'ADOP') {
636
-			echo self::editFieldAdoption($element_name, $value, '', $person);
637
-		} elseif ($fact === 'PEDI') {
638
-			echo self::editFieldPedigree($element_name, $value, '', $person);
639
-		} elseif ($fact === 'STAT') {
640
-			echo self::selectEditControl($element_name, GedcomCodeStat::statusNames($upperlevel), '', $value);
641
-		} elseif ($fact === 'RELA') {
642
-			echo self::editFieldRelationship($element_name, strtolower($value));
643
-		} elseif ($fact === 'QUAY') {
644
-			echo self::selectEditControl($element_name, GedcomCodeQuay::getValues(), '', $value);
645
-		} elseif ($fact === '_WT_USER') {
646
-			echo self::editFieldUsername($element_name, $value);
647
-		} elseif ($fact === 'RESN') {
648
-			echo self::editFieldRestriction($element_name, $value);
649
-		} elseif ($fact === '_PRIM') {
650
-			echo '<select id="', $element_id, '" name="', $element_name, '" >';
651
-			echo '<option value=""></option>';
652
-			echo '<option value="Y" ';
653
-			if ($value === 'Y') {
654
-				echo ' selected';
655
-			}
656
-			echo '>', /* I18N: option in list box “always use this image” */
657
-			I18N::translate('always'), '</option>';
658
-			echo '<option value="N" ';
659
-			if ($value === 'N') {
660
-				echo 'selected';
661
-			}
662
-			echo '>', /* I18N: option in list box “never use this image” */
663
-			I18N::translate('never'), '</option>';
664
-			echo '</select>';
665
-			echo '<p class="small text-muted">', I18N::translate('Use this image for charts and on the individual’s page.'), '</p>';
666
-		} elseif ($fact === 'SEX') {
667
-			echo '<select id="', $element_id, '" name="', $element_name, '"><option value="M" ';
668
-			if ($value === 'M') {
669
-				echo 'selected';
670
-			}
671
-			echo '>', I18N::translate('Male'), '</option><option value="F" ';
672
-			if ($value === 'F') {
673
-				echo 'selected';
674
-			}
675
-			echo '>', I18N::translate('Female'), '</option><option value="U" ';
676
-			if ($value === 'U' || empty($value)) {
677
-				echo 'selected';
678
-			}
679
-			echo '>', I18N::translateContext('unknown gender', 'Unknown'), '</option></select>';
680
-		} elseif ($fact === 'TYPE' && $level === 3) {
681
-			//-- Build the selector for the Media 'TYPE' Fact
682
-			echo '<select name="text[]"><option selected value="" ></option>';
683
-			$selectedValue = strtolower($value);
684
-			if (!array_key_exists($selectedValue, GedcomTag::getFileFormTypes())) {
685
-				echo '<option selected value="', Filter::escapeHtml($value), '" >', Filter::escapeHtml($value), '</option>';
686
-			}
687
-			foreach (GedcomTag::getFileFormTypes() as $typeName => $typeValue) {
688
-				echo '<option value="', $typeName, '" ';
689
-				if ($selectedValue === $typeName) {
690
-					echo 'selected';
691
-				}
692
-				echo '>', $typeValue, '</option>';
693
-			}
694
-			echo '</select>';
695
-		} elseif (($fact === 'NAME' && $upperlevel !== 'REPO' && $upperlevel !== 'UNKNOWN') || $fact === '_MARNM') {
696
-			// Populated in javascript from sub-tags
697
-			echo '<input type="hidden" id="', $element_id, '" name="', $element_name, '" onchange="updateTextName(\'', $element_id, '\');" value="', Filter::escapeHtml($value), '" class="', $fact, '">';
698
-			echo '<span id="', $element_id, '_display" dir="auto">', Filter::escapeHtml($value), '</span>';
699
-			echo ' <a href="#edit_name" onclick="convertHidden(\'', $element_id, '\'); return false;" class="icon-edit_indi" title="' . I18N::translate('Edit the name') . '"></a>';
700
-		} else {
701
-			// textarea
702
-			if ($fact === 'TEXT' || $fact === 'ADDR' || ($fact === 'NOTE' && !$islink)) {
703
-				echo '<textarea id="', $element_id, '" name="', $element_name, '" dir="auto">', Filter::escapeHtml($value), '</textarea><br>';
704
-			} else {
705
-				// text
706
-				// If using GEDFact-assistant window
707
-				if ($action === 'addnewnote_assisted') {
708
-					echo '<input type="text" id="', $element_id, '" name="', $element_name, '" value="', Filter::escapeHtml($value), '" style="width:4.1em;" dir="ltr"';
709
-				} else {
710
-					echo '<input type="text" id="', $element_id, '" name="', $element_name, '" value="', Filter::escapeHtml($value), '" dir="ltr"';
711
-				}
712
-				echo ' class="', $fact, '"';
713
-				if (in_array($fact, $subnamefacts)) {
714
-					echo ' onblur="updatewholename();" onkeyup="updatewholename();"';
715
-				}
716
-
717
-				// Extra markup for specific fact types
718
-				switch ($fact) {
719
-				case 'ALIA':
720
-				case 'ASSO':
721
-				case '_ASSO':
722
-					echo ' data-autocomplete-type="ASSO" data-autocomplete-extra="input.DATE"';
723
-					break;
724
-				case 'DATE':
725
-					echo ' onblur="valid_date(this);" onmouseout="valid_date(this);"';
726
-					break;
727
-				case 'GIVN':
728
-					echo ' autofocus data-autocomplete-type="GIVN"';
729
-					break;
730
-				case 'LATI':
731
-					echo ' onblur="valid_lati_long(this, \'N\', \'S\');" onmouseout="valid_lati_long(this, \'N\', \'S\');"';
732
-					break;
733
-				case 'LONG':
734
-					echo ' onblur="valid_lati_long(this, \'E\', \'W\');" onmouseout="valid_lati_long(this, \'E\', \'W\');"';
735
-					break;
736
-				case 'NOTE':
737
-					// Shared notes. Inline notes are handled elsewhere.
738
-					echo ' data-autocomplete-type="NOTE"';
739
-					break;
740
-				case 'OBJE':
741
-					echo ' data-autocomplete-type="OBJE"';
742
-					break;
743
-				case 'PAGE':
744
-					echo ' data-autocomplete-type="PAGE" data-autocomplete-extra="#' . $source_element_id . '"';
745
-					break;
746
-				case 'PLAC':
747
-					echo ' data-autocomplete-type="PLAC"';
748
-					break;
749
-				case 'REPO':
750
-					echo ' data-autocomplete-type="REPO"';
751
-					break;
752
-				case 'SOUR':
753
-					$source_element_id = $element_id;
754
-					echo ' data-autocomplete-type="SOUR"';
755
-					break;
756
-				case 'SURN':
757
-				case '_MARNM_SURN':
758
-					echo ' data-autocomplete-type="SURN"';
759
-					break;
760
-				case 'TIME':
761
-					echo ' pattern="([0-1][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])?" dir="ltr" placeholder="' . /* I18N: Examples of valid time formats (hours:minutes:seconds) */
762
-						I18N::translate('hh:mm or hh:mm:ss') . '"';
763
-					break;
764
-				}
765
-				echo '>';
766
-			}
767
-
768
-			$tmp_array = array('TYPE', 'TIME', 'NOTE', 'SOUR', 'REPO', 'OBJE', 'ASSO', '_ASSO', 'AGE');
769
-
770
-			// split PLAC
771
-			if ($fact === 'PLAC') {
772
-				echo '<div id="', $element_id, '_pop" style="display: inline;">';
773
-				echo FunctionsPrint::printSpecialCharacterLink($element_id), ' ', FunctionsPrint::printFindPlaceLink($element_id);
774
-				echo '<span  onclick="jQuery(\'tr[id^=', $upperlevel, '_LATI],tr[id^=', $upperlevel, '_LONG],tr[id^=LATI],tr[id^=LONG]\').toggle(\'fast\'); return false;" class="icon-target" title="', GedcomTag::getLabel('LATI'), ' / ', GedcomTag::getLabel('LONG'), '"></span>';
775
-				echo '</div>';
776
-				if (Module::getModuleByName('places_assistant')) {
777
-					\PlacesAssistantModule::setup_place_subfields($element_id);
778
-					\PlacesAssistantModule::print_place_subfields($element_id);
779
-				}
780
-			} elseif (!in_array($fact, $tmp_array)) {
781
-				echo FunctionsPrint::printSpecialCharacterLink($element_id);
782
-			}
783
-		}
784
-		// MARRiage TYPE : hide text field and show a selection list
785
-		if ($fact === 'TYPE' && $level === 2 && $tags[0] === 'MARR') {
786
-			echo '<script>';
787
-			echo 'document.getElementById(\'', $element_id, '\').style.display=\'none\'';
788
-			echo '</script>';
789
-			echo '<select id="', $element_id, '_sel" onchange="document.getElementById(\'', $element_id, '\').value=this.value;" >';
790
-			foreach (array('Unknown', 'Civil', 'Religious', 'Partners') as $key) {
791
-				if ($key === 'Unknown') {
792
-					echo '<option value="" ';
793
-				} else {
794
-					echo '<option value="', $key, '" ';
795
-				}
796
-				$a = strtolower($key);
797
-				$b = strtolower($value);
798
-				if ($b !== '' && strpos($a, $b) !== false || strpos($b, $a) !== false) {
799
-					echo 'selected';
800
-				}
801
-				echo '>', GedcomTag::getLabel('MARR_' . strtoupper($key)), '</option>';
802
-			}
803
-			echo '</select>';
804
-		} elseif ($fact === 'TYPE' && $level === 0) {
805
-			// NAME TYPE : hide text field and show a selection list
806
-			$onchange = 'onchange="document.getElementById(\'' . $element_id . '\').value=this.value;"';
807
-			echo self::editFieldNameType($element_name, $value, $onchange, $person);
808
-			echo '<script>document.getElementById("', $element_id, '").style.display="none";</script>';
809
-		}
810
-
811
-		// popup links
812
-		switch ($fact) {
813
-		case 'DATE':
814
-			echo self::printCalendarPopup($element_id);
815
-			break;
816
-		case 'FAMC':
817
-		case 'FAMS':
818
-			echo FunctionsPrint::printFindFamilyLink($element_id);
819
-			break;
820
-		case 'ALIA':
821
-		case 'ASSO':
822
-		case '_ASSO':
823
-			echo FunctionsPrint::printFindIndividualLink($element_id, $element_id . '_description');
824
-			break;
825
-		case 'FILE':
826
-			FunctionsPrint::printFindMediaLink($element_id, '0file');
827
-			break;
828
-		case 'SOUR':
829
-			echo FunctionsPrint::printFindSourceLink($element_id, $element_id . '_description'), ' ', self::printAddNewSourceLink($element_id);
830
-			//-- checkboxes to apply '1 SOUR' to BIRT/MARR/DEAT as '2 SOUR'
831
-			if ($level === 1) {
832
-				echo '<br>';
833
-				switch ($WT_TREE->getPreference('PREFER_LEVEL2_SOURCES')) {
834
-				case '2': // records
835
-				$level1_checked = 'checked';
836
-				$level2_checked = '';
837
-				break;
838
-				case '1': // facts
839
-				$level1_checked = '';
840
-				$level2_checked = 'checked';
841
-				break;
842
-				case '0': // none
843
-				default:
844
-				$level1_checked = '';
845
-				$level2_checked = '';
846
-				break;
847
-				}
848
-					if (strpos($bdm, 'B') !== false) {
849
-						echo ' <label><input type="checkbox" name="SOUR_INDI" ', $level1_checked, ' value="1">', I18N::translate('Individual'), '</label>';
850
-						if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('QUICK_REQUIRED_FACTS'), $matches)) {
851
-							foreach ($matches[1] as $match) {
852
-								if (!in_array($match, explode('|', WT_EVENTS_DEAT))) {
853
-									echo ' <label><input type="checkbox" name="SOUR_', $match, '" ', $level2_checked, ' value="1">', GedcomTag::getLabel($match), '</label>';
854
-								}
855
-							}
856
-						}
857
-					}
858
-					if (strpos($bdm, 'D') !== false) {
859
-						if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('QUICK_REQUIRED_FACTS'), $matches)) {
860
-							foreach ($matches[1] as $match) {
861
-								if (in_array($match, explode('|', WT_EVENTS_DEAT))) {
862
-									echo ' <label><input type="checkbox" name="SOUR_', $match, '"', $level2_checked, ' value="1">', GedcomTag::getLabel($match), '</label>';
863
-								}
864
-							}
865
-						}
866
-					}
867
-					if (strpos($bdm, 'M') !== false) {
868
-						echo ' <label><input type="checkbox" name="SOUR_FAM" ', $level1_checked, ' value="1">', I18N::translate('Family'), '</label>';
869
-						if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('QUICK_REQUIRED_FAMFACTS'), $matches)) {
870
-							foreach ($matches[1] as $match) {
871
-								echo ' <label><input type="checkbox" name="SOUR_', $match, '"', $level2_checked, ' value="1">', GedcomTag::getLabel($match), '</label>';
872
-							}
873
-						}
874
-					}
875
-				}
876
-				break;
877
-		case 'REPO':
878
-			echo FunctionsPrint::printFindRepositoryLink($element_id), ' ', self::printAddNewRepositoryLink($element_id);
879
-			break;
880
-		case 'NOTE':
881
-			// Shared Notes Icons ========================================
882
-			if ($islink) {
883
-				// Print regular Shared Note icons ---------------------------
884
-				echo ' ', FunctionsPrint::printFindNoteLink($element_id, $element_id . '_description'), ' ', self::printAddNewNoteLink($element_id);
885
-				if ($value) {
886
-					echo ' ', self::printEditNoteLink($value);
887
-				}
888
-			}
889
-			break;
890
-		case 'OBJE':
891
-			echo FunctionsPrint::printFindMediaLink($element_id, '1media');
892
-			if (!$value) {
893
-				echo ' ', self::printAddNewMediaLink($element_id);
894
-				$value = 'new';
895
-			}
896
-			break;
897
-		}
898
-
899
-		echo '<div id="' . $element_id . '_description">';
900
-
901
-		// current value
902
-		if ($fact === 'DATE') {
903
-			$date = new Date($value);
904
-			echo $date->display();
905
-		}
906
-		if (($fact === 'ASSO' || $fact === '_ASSO') && $value === '') {
907
-			if ($level === 1) {
908
-				echo '<p class="small text-muted">' . I18N::translate('An associate is another individual who was involved with this individual, such as a friend or an employer.') . '</p>';
909
-			} else {
910
-				echo '<p class="small text-muted">' . I18N::translate('An associate is another individual who was involved with this fact or event, such as a witness or a priest.') . '</p>';
911
-			}
912
-		}
913
-
914
-		if ($value && $value !== 'new' && $islink) {
915
-			switch ($fact) {
916
-			case 'ALIA':
917
-			case 'ASSO':
918
-			case '_ASSO':
919
-				$tmp = Individual::getInstance($value, $WT_TREE);
920
-				if ($tmp) {
921
-					echo ' ', $tmp->getFullName();
922
-				}
923
-				break;
924
-			case 'SOUR':
925
-				$tmp = Source::getInstance($value, $WT_TREE);
926
-				if ($tmp) {
927
-					echo ' ', $tmp->getFullName();
928
-				}
929
-				break;
930
-			case 'NOTE':
931
-				$tmp = Note::getInstance($value, $WT_TREE);
932
-				if ($tmp) {
933
-					echo ' ', $tmp->getFullName();
934
-				}
935
-				break;
936
-			case 'OBJE':
937
-				$tmp = Media::getInstance($value, $WT_TREE);
938
-				if ($tmp) {
939
-					echo ' ', $tmp->getFullName();
940
-				}
941
-				break;
942
-			case 'REPO':
943
-				$tmp = Repository::getInstance($value, $WT_TREE);
944
-				if ($tmp) {
945
-					echo ' ', $tmp->getFullName();
946
-				}
947
-				break;
948
-			}
949
-		}
950
-
951
-		// pastable values
952
-		if ($fact === 'FORM' && $upperlevel === 'OBJE') {
953
-			FunctionsPrint::printAutoPasteLink($element_id, Config::fileFormats());
954
-		}
955
-		echo '</div>', $extra, '</td></tr>';
956
-
957
-		return $element_id;
958
-	}
959
-
960
-	/**
961
-	 * Genearate a <select> element, with the dates/places of all known censuses
962
-	 *
963
-	 *
964
-	 * @param string $locale - Sort the censuses for this locale
965
-	 * @param string $xref   - The individual for whom we are adding a census
966
-	 */
967
-	public static function censusDateSelector($locale, $xref) {
968
-		global $controller;
969
-
970
-		// Show more likely census details at the top of the list.
971
-		switch (WT_LOCALE) {
972
-		case 'cs':
973
-			$census_places = array(new CensusOfCzechRepublic);
974
-			break;
975
-		case 'en-AU':
976
-		case 'en-GB':
977
-			$census_places = array(new CensusOfEngland, new CensusOfWales, new CensusOfScotland);
978
-			break;
979
-		case 'en-US':
980
-			$census_places = array(new CensusOfUnitedStates);
981
-			break;
982
-		case 'fr':
983
-		case 'fr-CA':
984
-			$census_places = array(new CensusOfFrance);
985
-			break;
986
-		case 'da':
987
-			$census_places = array(new CensusOfDenmark);
988
-			break;
989
-		case 'de':
990
-			$census_places = array(new CensusOfDeutschland);
991
-			break;
992
-		default:
993
-			$census_places = array();
994
-			break;
995
-		}
996
-		foreach (Census::allCensusPlaces() as $census_place) {
997
-			if (!in_array($census_place, $census_places)) {
998
-				$census_places[] = $census_place;
999
-			}
1000
-		}
1001
-
1002
-		$controller->addInlineJavascript('
56
+    /**
57
+     * Create a <select> control for a form.
58
+     *
59
+     * @param string $name
60
+     * @param string[] $values
61
+     * @param string|null $empty
62
+     * @param string $selected
63
+     * @param string $extra
64
+     *
65
+     * @return string
66
+     */
67
+    public static function selectEditControl($name, $values, $empty, $selected, $extra = '') {
68
+        if (is_null($empty)) {
69
+            $html = '';
70
+        } else {
71
+            if (empty($selected)) {
72
+                $html = '<option value="" selected>' . Filter::escapeHtml($empty) . '</option>';
73
+            } else {
74
+                $html = '<option value="">' . Filter::escapeHtml($empty) . '</option>';
75
+            }
76
+        }
77
+        // A completely empty list would be invalid, and break various things
78
+        if (empty($values) && empty($html)) {
79
+            $html = '<option value=""></option>';
80
+        }
81
+        foreach ($values as $key => $value) {
82
+            // PHP array keys are cast to integers!  Cast them back
83
+            if ((string) $key === (string) $selected) {
84
+                $html .= '<option value="' . Filter::escapeHtml($key) . '" selected dir="auto">' . Filter::escapeHtml($value) . '</option>';
85
+            } else {
86
+                $html .= '<option value="' . Filter::escapeHtml($key) . '" dir="auto">' . Filter::escapeHtml($value) . '</option>';
87
+            }
88
+        }
89
+        if (substr($name, -2) === '[]') {
90
+            // id attribute is not used for arrays
91
+            return '<select name="' . $name . '" ' . $extra . '>' . $html . '</select>';
92
+        } else {
93
+            return '<select id="' . $name . '" name="' . $name . '" ' . $extra . '>' . $html . '</select>';
94
+        }
95
+    }
96
+
97
+    /**
98
+     * Create a set of radio buttons for a form
99
+     *
100
+     * @param string $name The ID for the form element
101
+     * @param string[] $values Array of value=>display items
102
+     * @param string $selected The currently selected item
103
+     * @param string $extra Additional markup for the label
104
+     *
105
+     * @return string
106
+     */
107
+    public static function radioButtons($name, $values, $selected, $extra = '') {
108
+        $html = '';
109
+        foreach ($values as $key => $value) {
110
+            $html .=
111
+                '<label ' . $extra . '>' .
112
+                '<input type="radio" name="' . $name . '" value="' . Filter::escapeHtml($key) . '"';
113
+            // PHP array keys are cast to integers!  Cast them back
114
+            if ((string) $key === (string) $selected) {
115
+                $html .= ' checked';
116
+            }
117
+            $html .= '>' . Filter::escapeHtml($value) . '</label>';
118
+        }
119
+
120
+        return $html;
121
+    }
122
+
123
+    /**
124
+     * Print an edit control for a Yes/No field
125
+     *
126
+     * @param string $name
127
+     * @param bool $selected
128
+     * @param string $extra
129
+     *
130
+     * @return string
131
+     */
132
+    public static function editFieldYesNo($name, $selected = false, $extra = '') {
133
+        return self::radioButtons(
134
+            $name, array(I18N::translate('no'), I18N::translate('yes')), $selected, $extra
135
+        );
136
+    }
137
+
138
+    /**
139
+     * Print an edit control for a checkbox.
140
+     *
141
+     * @param string $name
142
+     * @param bool $is_checked
143
+     * @param string $extra
144
+     *
145
+     * @return string
146
+     */
147
+    public static function checkbox($name, $is_checked = false, $extra = '') {
148
+        return '<input type="checkbox" name="' . $name . '" value="1" ' . ($is_checked ? 'checked ' : '') . $extra . '>';
149
+    }
150
+
151
+    /**
152
+     * Print an edit control for a checkbox, with a hidden field to store one of the two states.
153
+     * By default, a checkbox is either set, or not sent.
154
+     * This function gives us a three options, set, unset or not sent.
155
+     * Useful for dynamically generated forms where we don't know what elements are present.
156
+     *
157
+     * @param string $name
158
+     * @param int $is_checked 0 or 1
159
+     * @param string $extra
160
+     *
161
+     * @return string
162
+     */
163
+    public static function twoStateCheckbox($name, $is_checked = 0, $extra = '') {
164
+        return
165
+            '<input type="hidden" id="' . $name . '" name="' . $name . '" value="' . ($is_checked ? 1 : 0) . '">' .
166
+            '<input type="checkbox" name="' . $name . '-GUI-ONLY" value="1"' .
167
+            ($is_checked ? ' checked' : '') .
168
+            ' onclick="document.getElementById(\'' . $name . '\').value=(this.checked?1:0);" ' . $extra . '>';
169
+    }
170
+
171
+    /**
172
+     * Function edit_language_checkboxes
173
+     *
174
+     * @param string $parameter_name
175
+     * @param array $accepted_languages
176
+     *
177
+     * @return string
178
+     */
179
+    public static function editLanguageCheckboxes($parameter_name, $accepted_languages) {
180
+        $html = '';
181
+        foreach (I18N::activeLocales() as $locale) {
182
+            $html .= '<div class="checkbox">';
183
+            $html .= '<label title="' . $locale->languageTag() . '">';
184
+            $html .= '<input type="checkbox" name="' . $parameter_name . '[]" value="' . $locale->languageTag() . '"';
185
+            $html .= in_array($locale->languageTag(), $accepted_languages) ? ' checked>' : '>';
186
+            $html .= $locale->endonym();
187
+            $html .= '</label>';
188
+            $html .= '</div>';
189
+        }
190
+
191
+        return $html;
192
+    }
193
+
194
+    /**
195
+     * Print an edit control for access level.
196
+     *
197
+     * @param string $name
198
+     * @param string $selected
199
+     * @param string $extra
200
+     *
201
+     * @return string
202
+     */
203
+    public static function editFieldAccessLevel($name, $selected = '', $extra = '') {
204
+        $ACCESS_LEVEL = array(
205
+            Auth::PRIV_PRIVATE => I18N::translate('Show to visitors'),
206
+            Auth::PRIV_USER    => I18N::translate('Show to members'),
207
+            Auth::PRIV_NONE    => I18N::translate('Show to managers'),
208
+            Auth::PRIV_HIDE    => I18N::translate('Hide from everyone'),
209
+        );
210
+
211
+        return self::selectEditControl($name, $ACCESS_LEVEL, null, $selected, $extra);
212
+    }
213
+
214
+    /**
215
+     * Print an edit control for a RESN field.
216
+     *
217
+     * @param string $name
218
+     * @param string $selected
219
+     * @param string $extra
220
+     *
221
+     * @return string
222
+     */
223
+    public static function editFieldRestriction($name, $selected = '', $extra = '') {
224
+        $RESN = array(
225
+            ''             => '',
226
+            'none'         => I18N::translate('Show to visitors'), // Not valid GEDCOM, but very useful
227
+            'privacy'      => I18N::translate('Show to members'),
228
+            'confidential' => I18N::translate('Show to managers'),
229
+            'locked'       => I18N::translate('Only managers can edit'),
230
+        );
231
+
232
+        return self::selectEditControl($name, $RESN, null, $selected, $extra);
233
+    }
234
+
235
+    /**
236
+     * Print an edit control for a contact method field.
237
+     *
238
+     * @param string $name
239
+     * @param string $selected
240
+     * @param string $extra
241
+     *
242
+     * @return string
243
+     */
244
+    public static function editFieldContact($name, $selected = '', $extra = '') {
245
+        // Different ways to contact the users
246
+        $CONTACT_METHODS = array(
247
+            'messaging'  => I18N::translate('Internal messaging'),
248
+            'messaging2' => I18N::translate('Internal messaging with emails'),
249
+            'messaging3' => I18N::translate('webtrees sends emails with no storage'),
250
+            'mailto'     => I18N::translate('Mailto link'),
251
+            'none'       => I18N::translate('No contact'),
252
+        );
253
+
254
+        return self::selectEditControl($name, $CONTACT_METHODS, null, $selected, $extra);
255
+    }
256
+
257
+    /**
258
+     * Print an edit control for a language field.
259
+     *
260
+     * @param string $name
261
+     * @param string $selected
262
+     * @param string $extra
263
+     *
264
+     * @return string
265
+     */
266
+    public static function editFieldLanguage($name, $selected = '', $extra = '') {
267
+        $languages = array();
268
+        foreach (I18N::activeLocales() as $locale) {
269
+            $languages[$locale->languageTag()] = $locale->endonym();
270
+        }
271
+
272
+        return self::selectEditControl($name, $languages, null, $selected, $extra);
273
+    }
274
+
275
+    /**
276
+     * Print an edit control for a range of integers.
277
+     *
278
+     * @param string $name
279
+     * @param string $selected
280
+     * @param int $min
281
+     * @param int $max
282
+     * @param string $extra
283
+     *
284
+     * @return string
285
+     */
286
+    public static function editFieldInteger($name, $selected = '', $min, $max, $extra = '') {
287
+        $array = array();
288
+        for ($i = $min; $i <= $max; ++$i) {
289
+            $array[$i] = I18N::number($i);
290
+        }
291
+
292
+        return self::selectEditControl($name, $array, null, $selected, $extra);
293
+    }
294
+
295
+    /**
296
+     * Print an edit control for a username.
297
+     *
298
+     * @param string $name
299
+     * @param string $selected
300
+     * @param string $extra
301
+     *
302
+     * @return string
303
+     */
304
+    public static function editFieldUsername($name, $selected = '', $extra = '') {
305
+        $users = array();
306
+        foreach (User::all() as $user) {
307
+            $users[$user->getUserName()] = $user->getRealName() . ' - ' . $user->getUserName();
308
+        }
309
+        // The currently selected user may not exist
310
+        if ($selected && !array_key_exists($selected, $users)) {
311
+            $users[$selected] = $selected;
312
+        }
313
+
314
+        return self::selectEditControl($name, $users, '-', $selected, $extra);
315
+    }
316
+
317
+    /**
318
+     * Print an edit control for a ADOP field.
319
+     *
320
+     * @param string          $name
321
+     * @param string          $selected
322
+     * @param string          $extra
323
+     * @param Individual|null $individual
324
+     *
325
+     * @return string
326
+     */
327
+    public static function editFieldAdoption($name, $selected = '', $extra = '', Individual $individual = null) {
328
+        return self::selectEditControl($name, GedcomCodeAdop::getValues($individual), null, $selected, $extra);
329
+    }
330
+
331
+    /**
332
+     * Print an edit control for a PEDI field.
333
+     *
334
+     * @param string          $name
335
+     * @param string          $selected
336
+     * @param string          $extra
337
+     * @param Individual|null $individual
338
+     *
339
+     * @return string
340
+     */
341
+    public static function editFieldPedigree($name, $selected = '', $extra = '', Individual $individual = null) {
342
+        return self::selectEditControl($name, GedcomCodePedi::getValues($individual), '', $selected, $extra);
343
+    }
344
+
345
+    /**
346
+     * Print an edit control for a NAME TYPE field.
347
+     *
348
+     * @param string          $name
349
+     * @param string          $selected
350
+     * @param string          $extra
351
+     * @param Individual|null $individual
352
+     *
353
+     * @return string
354
+     */
355
+    public static function editFieldNameType($name, $selected = '', $extra = '', Individual $individual = null) {
356
+        return self::selectEditControl($name, GedcomCodeName::getValues($individual), '', $selected, $extra);
357
+    }
358
+
359
+    /**
360
+     * Print an edit control for a RELA field.
361
+     *
362
+     * @param string $name
363
+     * @param string $selected
364
+     * @param string $extra
365
+     *
366
+     * @return string
367
+     */
368
+    public static function editFieldRelationship($name, $selected = '', $extra = '') {
369
+        $rela_codes = GedcomCodeRela::getValues();
370
+        // The user is allowed to specify values that aren't in the list.
371
+        if (!array_key_exists($selected, $rela_codes)) {
372
+            $rela_codes[$selected] = I18N::translate($selected);
373
+        }
374
+
375
+        return self::selectEditControl($name, $rela_codes, '', $selected, $extra);
376
+    }
377
+
378
+    /**
379
+     * Remove all links from $gedrec to $xref, and any sub-tags.
380
+     *
381
+     * @param string $gedrec
382
+     * @param string $xref
383
+     *
384
+     * @return string
385
+     */
386
+    public static function removeLinks($gedrec, $xref) {
387
+        $gedrec = preg_replace('/\n1 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[2-9].*)*/', '', $gedrec);
388
+        $gedrec = preg_replace('/\n2 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[3-9].*)*/', '', $gedrec);
389
+        $gedrec = preg_replace('/\n3 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[4-9].*)*/', '', $gedrec);
390
+        $gedrec = preg_replace('/\n4 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[5-9].*)*/', '', $gedrec);
391
+        $gedrec = preg_replace('/\n5 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[6-9].*)*/', '', $gedrec);
392
+
393
+        return $gedrec;
394
+    }
395
+
396
+    /**
397
+     * Generates javascript code for calendar popup in user’s language.
398
+     *
399
+     * @param string $id
400
+     *
401
+     * @return string
402
+     */
403
+    public static function printCalendarPopup($id) {
404
+        return
405
+            ' <a href="#" onclick="cal_toggleDate(\'caldiv' . $id . '\', \'' . $id . '\'); return false;" class="icon-button_calendar" title="' . I18N::translate('Select a date') . '"></a>' .
406
+            '<div id="caldiv' . $id . '" style="position:absolute;visibility:hidden;background-color:white;z-index:1000;"></div>';
407
+    }
408
+
409
+    /**
410
+     * An HTML link to create a new media object.
411
+     *
412
+     * @param string $element_id
413
+     *
414
+     * @return string
415
+     */
416
+    public static function printAddNewMediaLink($element_id) {
417
+        return '<a href="#" onclick="pastefield=document.getElementById(\'' . $element_id . '\'); window.open(\'addmedia.php?action=showmediaform\', \'_blank\', edit_window_specs); return false;" class="icon-button_addmedia" title="' . I18N::translate('Create a media object') . '"></a>';
418
+    }
419
+
420
+    /**
421
+     * An HTML link to create a new repository.
422
+     *
423
+     * @param string $element_id
424
+     *
425
+     * @return string
426
+     */
427
+    public static function printAddNewRepositoryLink($element_id) {
428
+        return '<a href="#" onclick="addnewrepository(document.getElementById(\'' . $element_id . '\')); return false;" class="icon-button_addrepository" title="' . I18N::translate('Create a repository') . '"></a>';
429
+    }
430
+
431
+    /**
432
+     * An HTML link to create a new note.
433
+     *
434
+     * @param string $element_id
435
+     *
436
+     * @return string
437
+     */
438
+    public static function printAddNewNoteLink($element_id) {
439
+        return '<a href="#" onclick="addnewnote(document.getElementById(\'' . $element_id . '\')); return false;" class="icon-button_addnote" title="' . I18N::translate('Create a shared note') . '"></a>';
440
+    }
441
+
442
+    /**
443
+     * An HTML link to edit a note.
444
+     *
445
+     * @param string $note_id
446
+     *
447
+     * @return string
448
+     */
449
+    public static function printEditNoteLink($note_id) {
450
+        return '<a href="#" onclick="edit_note(\'' . $note_id . '\'); return false;" class="icon-button_note" title="' . I18N::translate('Edit the shared note') . '"></a>';
451
+    }
452
+
453
+    /**
454
+     * An HTML link to create a new source.
455
+     *
456
+     * @param string $element_id
457
+     *
458
+     * @return string
459
+     */
460
+    public static function printAddNewSourceLink($element_id) {
461
+        return '<a href="#" onclick="addnewsource(document.getElementById(\'' . $element_id . '\')); return false;" class="icon-button_addsource" title="' . I18N::translate('Create a source') . '"></a>';
462
+    }
463
+
464
+    /**
465
+     * add a new tag input field
466
+     *
467
+     * called for each fact to be edited on a form.
468
+     * Fact level=0 means a new empty form : data are POSTed by name
469
+     * else data are POSTed using arrays :
470
+     * glevels[] : tag level
471
+     *  islink[] : tag is a link
472
+     *     tag[] : tag name
473
+     *    text[] : tag value
474
+     *
475
+     * @param string $tag fact record to edit (eg 2 DATE xxxxx)
476
+     * @param string $upperlevel optional upper level tag (eg BIRT)
477
+     * @param string $label An optional label to echo instead of the default
478
+     * @param string $extra optional text to display after the input field
479
+     * @param Individual $person For male/female translations
480
+     *
481
+     * @return string
482
+     */
483
+    public static function addSimpleTag($tag, $upperlevel = '', $label = '', $extra = null, Individual $person = null) {
484
+        global $tags, $main_fact, $xref, $bdm, $action, $WT_TREE;
485
+
486
+        // Keep track of SOUR fields, so we can reference them in subsequent PAGE fields.
487
+        static $source_element_id;
488
+
489
+        $subnamefacts = array('NPFX', 'GIVN', 'SPFX', 'SURN', 'NSFX', '_MARNM_SURN');
490
+        preg_match('/^(?:(\d+) (' . WT_REGEX_TAG . ') ?(.*))/', $tag, $match);
491
+        list(, $level, $fact, $value) = $match;
492
+        $level                        = (int) $level;
493
+
494
+        // element name : used to POST data
495
+        if ($level === 0) {
496
+            if ($upperlevel) {
497
+                $element_name = $upperlevel . '_' . $fact;
498
+            } else {
499
+                $element_name = $fact;
500
+            }
501
+        } else {
502
+            $element_name = 'text[]';
503
+        }
504
+        if ($level === 1) {
505
+            $main_fact = $fact;
506
+        }
507
+
508
+        // element id : used by javascript functions
509
+        if ($level === 0) {
510
+            $element_id = $fact;
511
+        } else {
512
+            $element_id = $fact . Uuid::uuid4();
513
+        }
514
+        if ($upperlevel) {
515
+            $element_id = $upperlevel . '_' . $fact . Uuid::uuid4();
516
+        }
517
+
518
+        // field value
519
+        $islink = (substr($value, 0, 1) === '@' && substr($value, 0, 2) !== '@#');
520
+        if ($islink) {
521
+            $value = trim(substr($tag, strlen($fact) + 3), '@');
522
+        } else {
523
+            $value = (string) substr($tag, strlen($fact) + 3);
524
+        }
525
+        if ($fact === 'REPO' || $fact === 'SOUR' || $fact === 'OBJE' || $fact === 'FAMC') {
526
+            $islink = true;
527
+        }
528
+
529
+        if ($fact === 'SHARED_NOTE_EDIT' || $fact === 'SHARED_NOTE') {
530
+            $islink = true;
531
+            $fact   = 'NOTE';
532
+        }
533
+
534
+        // label
535
+        echo '<tr id="', $element_id, '_tr"';
536
+        if ($fact === 'DATA' || $fact === 'MAP' || ($fact === 'LATI' || $fact === 'LONG') && $value === '') {
537
+            echo ' style="display:none;"';
538
+        }
539
+        echo '>';
540
+
541
+        if (in_array($fact, $subnamefacts) || $fact === 'LATI' || $fact === 'LONG') {
542
+            echo '<td class="optionbox wrap width25">';
543
+        } else {
544
+            echo '<td class="descriptionbox wrap width25">';
545
+        }
546
+
547
+        // tag name
548
+        if ($label) {
549
+            echo $label;
550
+        } elseif ($upperlevel) {
551
+            echo GedcomTag::getLabel($upperlevel . ':' . $fact);
552
+        } else {
553
+            echo GedcomTag::getLabel($fact);
554
+        }
555
+
556
+        // If using GEDFact-assistant window
557
+        if ($action === 'addnewnote_assisted') {
558
+            // Do not print on GEDFact Assistant window
559
+        } else {
560
+            // Not all facts have help text.
561
+            switch ($fact) {
562
+            case 'NAME':
563
+                if ($upperlevel !== 'REPO' && $upperlevel !== 'UNKNOWN') {
564
+                    echo FunctionsPrint::helpLink($fact);
565
+                }
566
+                break;
567
+            case 'DATE':
568
+            case 'PLAC':
569
+            case 'RESN':
570
+            case 'ROMN':
571
+            case 'SURN':
572
+            case '_HEB':
573
+                echo FunctionsPrint::helpLink($fact);
574
+                break;
575
+            }
576
+        }
577
+        // tag level
578
+        if ($level > 0) {
579
+            echo '<input type="hidden" name="glevels[]" value="', $level, '">';
580
+            echo '<input type="hidden" name="islink[]" value="', $islink, '">';
581
+            echo '<input type="hidden" name="tag[]" value="', $fact, '">';
582
+        }
583
+        echo '</td>';
584
+
585
+        // value
586
+        echo '<td class="optionbox wrap">';
587
+
588
+        // retrieve linked NOTE
589
+        if ($fact === 'NOTE' && $islink) {
590
+            $note1 = Note::getInstance($value, $WT_TREE);
591
+            if ($note1) {
592
+                $noterec = $note1->getGedcom();
593
+                preg_match('/' . $value . '/i', $noterec, $notematch);
594
+                $value = $notematch[0];
595
+            }
596
+        }
597
+
598
+        // Show names for spouses in MARR/HUSB/AGE and MARR/WIFE/AGE
599
+        if ($fact === 'HUSB' || $fact === 'WIFE') {
600
+            $family = Family::getInstance($xref, $WT_TREE);
601
+            if ($family) {
602
+                $spouse_link = $family->getFirstFact($fact);
603
+                if ($spouse_link) {
604
+                    $spouse = $spouse_link->getTarget();
605
+                    if ($spouse) {
606
+                        echo $spouse->getFullName();
607
+                    }
608
+                }
609
+            }
610
+        }
611
+
612
+        if (in_array($fact, Config::emptyFacts()) && ($value === '' || $value === 'Y' || $value === 'y')) {
613
+            echo '<input type="hidden" id="', $element_id, '" name="', $element_name, '" value="', $value, '">';
614
+            if ($level <= 1) {
615
+                echo '<input type="checkbox" ';
616
+                if ($value) {
617
+                    echo 'checked';
618
+                }
619
+                echo ' onclick="document.getElementById(\'' . $element_id . '\').value = (this.checked) ? \'Y\' : \'\';">';
620
+                echo I18N::translate('yes');
621
+            }
622
+
623
+            if ($fact === 'CENS' && $value === 'Y') {
624
+                echo self::censusDateSelector(WT_LOCALE, $xref);
625
+                if (Module::getModuleByName('GEDFact_assistant') && GedcomRecord::getInstance($xref, $WT_TREE) instanceof Individual) {
626
+                    echo
627
+                        '<div></div><a href="#" style="display: none;" id="assistant-link" onclick="return activateCensusAssistant();">' .
628
+                        I18N::translate('Create a shared note using the census assistant') .
629
+                        '</a></div>';
630
+                }
631
+            }
632
+
633
+        } elseif ($fact === 'TEMP') {
634
+            echo self::selectEditControl($element_name, GedcomCodeTemp::templeNames(), I18N::translate('No temple - living ordinance'), $value);
635
+        } elseif ($fact === 'ADOP') {
636
+            echo self::editFieldAdoption($element_name, $value, '', $person);
637
+        } elseif ($fact === 'PEDI') {
638
+            echo self::editFieldPedigree($element_name, $value, '', $person);
639
+        } elseif ($fact === 'STAT') {
640
+            echo self::selectEditControl($element_name, GedcomCodeStat::statusNames($upperlevel), '', $value);
641
+        } elseif ($fact === 'RELA') {
642
+            echo self::editFieldRelationship($element_name, strtolower($value));
643
+        } elseif ($fact === 'QUAY') {
644
+            echo self::selectEditControl($element_name, GedcomCodeQuay::getValues(), '', $value);
645
+        } elseif ($fact === '_WT_USER') {
646
+            echo self::editFieldUsername($element_name, $value);
647
+        } elseif ($fact === 'RESN') {
648
+            echo self::editFieldRestriction($element_name, $value);
649
+        } elseif ($fact === '_PRIM') {
650
+            echo '<select id="', $element_id, '" name="', $element_name, '" >';
651
+            echo '<option value=""></option>';
652
+            echo '<option value="Y" ';
653
+            if ($value === 'Y') {
654
+                echo ' selected';
655
+            }
656
+            echo '>', /* I18N: option in list box “always use this image” */
657
+            I18N::translate('always'), '</option>';
658
+            echo '<option value="N" ';
659
+            if ($value === 'N') {
660
+                echo 'selected';
661
+            }
662
+            echo '>', /* I18N: option in list box “never use this image” */
663
+            I18N::translate('never'), '</option>';
664
+            echo '</select>';
665
+            echo '<p class="small text-muted">', I18N::translate('Use this image for charts and on the individual’s page.'), '</p>';
666
+        } elseif ($fact === 'SEX') {
667
+            echo '<select id="', $element_id, '" name="', $element_name, '"><option value="M" ';
668
+            if ($value === 'M') {
669
+                echo 'selected';
670
+            }
671
+            echo '>', I18N::translate('Male'), '</option><option value="F" ';
672
+            if ($value === 'F') {
673
+                echo 'selected';
674
+            }
675
+            echo '>', I18N::translate('Female'), '</option><option value="U" ';
676
+            if ($value === 'U' || empty($value)) {
677
+                echo 'selected';
678
+            }
679
+            echo '>', I18N::translateContext('unknown gender', 'Unknown'), '</option></select>';
680
+        } elseif ($fact === 'TYPE' && $level === 3) {
681
+            //-- Build the selector for the Media 'TYPE' Fact
682
+            echo '<select name="text[]"><option selected value="" ></option>';
683
+            $selectedValue = strtolower($value);
684
+            if (!array_key_exists($selectedValue, GedcomTag::getFileFormTypes())) {
685
+                echo '<option selected value="', Filter::escapeHtml($value), '" >', Filter::escapeHtml($value), '</option>';
686
+            }
687
+            foreach (GedcomTag::getFileFormTypes() as $typeName => $typeValue) {
688
+                echo '<option value="', $typeName, '" ';
689
+                if ($selectedValue === $typeName) {
690
+                    echo 'selected';
691
+                }
692
+                echo '>', $typeValue, '</option>';
693
+            }
694
+            echo '</select>';
695
+        } elseif (($fact === 'NAME' && $upperlevel !== 'REPO' && $upperlevel !== 'UNKNOWN') || $fact === '_MARNM') {
696
+            // Populated in javascript from sub-tags
697
+            echo '<input type="hidden" id="', $element_id, '" name="', $element_name, '" onchange="updateTextName(\'', $element_id, '\');" value="', Filter::escapeHtml($value), '" class="', $fact, '">';
698
+            echo '<span id="', $element_id, '_display" dir="auto">', Filter::escapeHtml($value), '</span>';
699
+            echo ' <a href="#edit_name" onclick="convertHidden(\'', $element_id, '\'); return false;" class="icon-edit_indi" title="' . I18N::translate('Edit the name') . '"></a>';
700
+        } else {
701
+            // textarea
702
+            if ($fact === 'TEXT' || $fact === 'ADDR' || ($fact === 'NOTE' && !$islink)) {
703
+                echo '<textarea id="', $element_id, '" name="', $element_name, '" dir="auto">', Filter::escapeHtml($value), '</textarea><br>';
704
+            } else {
705
+                // text
706
+                // If using GEDFact-assistant window
707
+                if ($action === 'addnewnote_assisted') {
708
+                    echo '<input type="text" id="', $element_id, '" name="', $element_name, '" value="', Filter::escapeHtml($value), '" style="width:4.1em;" dir="ltr"';
709
+                } else {
710
+                    echo '<input type="text" id="', $element_id, '" name="', $element_name, '" value="', Filter::escapeHtml($value), '" dir="ltr"';
711
+                }
712
+                echo ' class="', $fact, '"';
713
+                if (in_array($fact, $subnamefacts)) {
714
+                    echo ' onblur="updatewholename();" onkeyup="updatewholename();"';
715
+                }
716
+
717
+                // Extra markup for specific fact types
718
+                switch ($fact) {
719
+                case 'ALIA':
720
+                case 'ASSO':
721
+                case '_ASSO':
722
+                    echo ' data-autocomplete-type="ASSO" data-autocomplete-extra="input.DATE"';
723
+                    break;
724
+                case 'DATE':
725
+                    echo ' onblur="valid_date(this);" onmouseout="valid_date(this);"';
726
+                    break;
727
+                case 'GIVN':
728
+                    echo ' autofocus data-autocomplete-type="GIVN"';
729
+                    break;
730
+                case 'LATI':
731
+                    echo ' onblur="valid_lati_long(this, \'N\', \'S\');" onmouseout="valid_lati_long(this, \'N\', \'S\');"';
732
+                    break;
733
+                case 'LONG':
734
+                    echo ' onblur="valid_lati_long(this, \'E\', \'W\');" onmouseout="valid_lati_long(this, \'E\', \'W\');"';
735
+                    break;
736
+                case 'NOTE':
737
+                    // Shared notes. Inline notes are handled elsewhere.
738
+                    echo ' data-autocomplete-type="NOTE"';
739
+                    break;
740
+                case 'OBJE':
741
+                    echo ' data-autocomplete-type="OBJE"';
742
+                    break;
743
+                case 'PAGE':
744
+                    echo ' data-autocomplete-type="PAGE" data-autocomplete-extra="#' . $source_element_id . '"';
745
+                    break;
746
+                case 'PLAC':
747
+                    echo ' data-autocomplete-type="PLAC"';
748
+                    break;
749
+                case 'REPO':
750
+                    echo ' data-autocomplete-type="REPO"';
751
+                    break;
752
+                case 'SOUR':
753
+                    $source_element_id = $element_id;
754
+                    echo ' data-autocomplete-type="SOUR"';
755
+                    break;
756
+                case 'SURN':
757
+                case '_MARNM_SURN':
758
+                    echo ' data-autocomplete-type="SURN"';
759
+                    break;
760
+                case 'TIME':
761
+                    echo ' pattern="([0-1][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])?" dir="ltr" placeholder="' . /* I18N: Examples of valid time formats (hours:minutes:seconds) */
762
+                        I18N::translate('hh:mm or hh:mm:ss') . '"';
763
+                    break;
764
+                }
765
+                echo '>';
766
+            }
767
+
768
+            $tmp_array = array('TYPE', 'TIME', 'NOTE', 'SOUR', 'REPO', 'OBJE', 'ASSO', '_ASSO', 'AGE');
769
+
770
+            // split PLAC
771
+            if ($fact === 'PLAC') {
772
+                echo '<div id="', $element_id, '_pop" style="display: inline;">';
773
+                echo FunctionsPrint::printSpecialCharacterLink($element_id), ' ', FunctionsPrint::printFindPlaceLink($element_id);
774
+                echo '<span  onclick="jQuery(\'tr[id^=', $upperlevel, '_LATI],tr[id^=', $upperlevel, '_LONG],tr[id^=LATI],tr[id^=LONG]\').toggle(\'fast\'); return false;" class="icon-target" title="', GedcomTag::getLabel('LATI'), ' / ', GedcomTag::getLabel('LONG'), '"></span>';
775
+                echo '</div>';
776
+                if (Module::getModuleByName('places_assistant')) {
777
+                    \PlacesAssistantModule::setup_place_subfields($element_id);
778
+                    \PlacesAssistantModule::print_place_subfields($element_id);
779
+                }
780
+            } elseif (!in_array($fact, $tmp_array)) {
781
+                echo FunctionsPrint::printSpecialCharacterLink($element_id);
782
+            }
783
+        }
784
+        // MARRiage TYPE : hide text field and show a selection list
785
+        if ($fact === 'TYPE' && $level === 2 && $tags[0] === 'MARR') {
786
+            echo '<script>';
787
+            echo 'document.getElementById(\'', $element_id, '\').style.display=\'none\'';
788
+            echo '</script>';
789
+            echo '<select id="', $element_id, '_sel" onchange="document.getElementById(\'', $element_id, '\').value=this.value;" >';
790
+            foreach (array('Unknown', 'Civil', 'Religious', 'Partners') as $key) {
791
+                if ($key === 'Unknown') {
792
+                    echo '<option value="" ';
793
+                } else {
794
+                    echo '<option value="', $key, '" ';
795
+                }
796
+                $a = strtolower($key);
797
+                $b = strtolower($value);
798
+                if ($b !== '' && strpos($a, $b) !== false || strpos($b, $a) !== false) {
799
+                    echo 'selected';
800
+                }
801
+                echo '>', GedcomTag::getLabel('MARR_' . strtoupper($key)), '</option>';
802
+            }
803
+            echo '</select>';
804
+        } elseif ($fact === 'TYPE' && $level === 0) {
805
+            // NAME TYPE : hide text field and show a selection list
806
+            $onchange = 'onchange="document.getElementById(\'' . $element_id . '\').value=this.value;"';
807
+            echo self::editFieldNameType($element_name, $value, $onchange, $person);
808
+            echo '<script>document.getElementById("', $element_id, '").style.display="none";</script>';
809
+        }
810
+
811
+        // popup links
812
+        switch ($fact) {
813
+        case 'DATE':
814
+            echo self::printCalendarPopup($element_id);
815
+            break;
816
+        case 'FAMC':
817
+        case 'FAMS':
818
+            echo FunctionsPrint::printFindFamilyLink($element_id);
819
+            break;
820
+        case 'ALIA':
821
+        case 'ASSO':
822
+        case '_ASSO':
823
+            echo FunctionsPrint::printFindIndividualLink($element_id, $element_id . '_description');
824
+            break;
825
+        case 'FILE':
826
+            FunctionsPrint::printFindMediaLink($element_id, '0file');
827
+            break;
828
+        case 'SOUR':
829
+            echo FunctionsPrint::printFindSourceLink($element_id, $element_id . '_description'), ' ', self::printAddNewSourceLink($element_id);
830
+            //-- checkboxes to apply '1 SOUR' to BIRT/MARR/DEAT as '2 SOUR'
831
+            if ($level === 1) {
832
+                echo '<br>';
833
+                switch ($WT_TREE->getPreference('PREFER_LEVEL2_SOURCES')) {
834
+                case '2': // records
835
+                $level1_checked = 'checked';
836
+                $level2_checked = '';
837
+                break;
838
+                case '1': // facts
839
+                $level1_checked = '';
840
+                $level2_checked = 'checked';
841
+                break;
842
+                case '0': // none
843
+                default:
844
+                $level1_checked = '';
845
+                $level2_checked = '';
846
+                break;
847
+                }
848
+                    if (strpos($bdm, 'B') !== false) {
849
+                        echo ' <label><input type="checkbox" name="SOUR_INDI" ', $level1_checked, ' value="1">', I18N::translate('Individual'), '</label>';
850
+                        if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('QUICK_REQUIRED_FACTS'), $matches)) {
851
+                            foreach ($matches[1] as $match) {
852
+                                if (!in_array($match, explode('|', WT_EVENTS_DEAT))) {
853
+                                    echo ' <label><input type="checkbox" name="SOUR_', $match, '" ', $level2_checked, ' value="1">', GedcomTag::getLabel($match), '</label>';
854
+                                }
855
+                            }
856
+                        }
857
+                    }
858
+                    if (strpos($bdm, 'D') !== false) {
859
+                        if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('QUICK_REQUIRED_FACTS'), $matches)) {
860
+                            foreach ($matches[1] as $match) {
861
+                                if (in_array($match, explode('|', WT_EVENTS_DEAT))) {
862
+                                    echo ' <label><input type="checkbox" name="SOUR_', $match, '"', $level2_checked, ' value="1">', GedcomTag::getLabel($match), '</label>';
863
+                                }
864
+                            }
865
+                        }
866
+                    }
867
+                    if (strpos($bdm, 'M') !== false) {
868
+                        echo ' <label><input type="checkbox" name="SOUR_FAM" ', $level1_checked, ' value="1">', I18N::translate('Family'), '</label>';
869
+                        if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('QUICK_REQUIRED_FAMFACTS'), $matches)) {
870
+                            foreach ($matches[1] as $match) {
871
+                                echo ' <label><input type="checkbox" name="SOUR_', $match, '"', $level2_checked, ' value="1">', GedcomTag::getLabel($match), '</label>';
872
+                            }
873
+                        }
874
+                    }
875
+                }
876
+                break;
877
+        case 'REPO':
878
+            echo FunctionsPrint::printFindRepositoryLink($element_id), ' ', self::printAddNewRepositoryLink($element_id);
879
+            break;
880
+        case 'NOTE':
881
+            // Shared Notes Icons ========================================
882
+            if ($islink) {
883
+                // Print regular Shared Note icons ---------------------------
884
+                echo ' ', FunctionsPrint::printFindNoteLink($element_id, $element_id . '_description'), ' ', self::printAddNewNoteLink($element_id);
885
+                if ($value) {
886
+                    echo ' ', self::printEditNoteLink($value);
887
+                }
888
+            }
889
+            break;
890
+        case 'OBJE':
891
+            echo FunctionsPrint::printFindMediaLink($element_id, '1media');
892
+            if (!$value) {
893
+                echo ' ', self::printAddNewMediaLink($element_id);
894
+                $value = 'new';
895
+            }
896
+            break;
897
+        }
898
+
899
+        echo '<div id="' . $element_id . '_description">';
900
+
901
+        // current value
902
+        if ($fact === 'DATE') {
903
+            $date = new Date($value);
904
+            echo $date->display();
905
+        }
906
+        if (($fact === 'ASSO' || $fact === '_ASSO') && $value === '') {
907
+            if ($level === 1) {
908
+                echo '<p class="small text-muted">' . I18N::translate('An associate is another individual who was involved with this individual, such as a friend or an employer.') . '</p>';
909
+            } else {
910
+                echo '<p class="small text-muted">' . I18N::translate('An associate is another individual who was involved with this fact or event, such as a witness or a priest.') . '</p>';
911
+            }
912
+        }
913
+
914
+        if ($value && $value !== 'new' && $islink) {
915
+            switch ($fact) {
916
+            case 'ALIA':
917
+            case 'ASSO':
918
+            case '_ASSO':
919
+                $tmp = Individual::getInstance($value, $WT_TREE);
920
+                if ($tmp) {
921
+                    echo ' ', $tmp->getFullName();
922
+                }
923
+                break;
924
+            case 'SOUR':
925
+                $tmp = Source::getInstance($value, $WT_TREE);
926
+                if ($tmp) {
927
+                    echo ' ', $tmp->getFullName();
928
+                }
929
+                break;
930
+            case 'NOTE':
931
+                $tmp = Note::getInstance($value, $WT_TREE);
932
+                if ($tmp) {
933
+                    echo ' ', $tmp->getFullName();
934
+                }
935
+                break;
936
+            case 'OBJE':
937
+                $tmp = Media::getInstance($value, $WT_TREE);
938
+                if ($tmp) {
939
+                    echo ' ', $tmp->getFullName();
940
+                }
941
+                break;
942
+            case 'REPO':
943
+                $tmp = Repository::getInstance($value, $WT_TREE);
944
+                if ($tmp) {
945
+                    echo ' ', $tmp->getFullName();
946
+                }
947
+                break;
948
+            }
949
+        }
950
+
951
+        // pastable values
952
+        if ($fact === 'FORM' && $upperlevel === 'OBJE') {
953
+            FunctionsPrint::printAutoPasteLink($element_id, Config::fileFormats());
954
+        }
955
+        echo '</div>', $extra, '</td></tr>';
956
+
957
+        return $element_id;
958
+    }
959
+
960
+    /**
961
+     * Genearate a <select> element, with the dates/places of all known censuses
962
+     *
963
+     *
964
+     * @param string $locale - Sort the censuses for this locale
965
+     * @param string $xref   - The individual for whom we are adding a census
966
+     */
967
+    public static function censusDateSelector($locale, $xref) {
968
+        global $controller;
969
+
970
+        // Show more likely census details at the top of the list.
971
+        switch (WT_LOCALE) {
972
+        case 'cs':
973
+            $census_places = array(new CensusOfCzechRepublic);
974
+            break;
975
+        case 'en-AU':
976
+        case 'en-GB':
977
+            $census_places = array(new CensusOfEngland, new CensusOfWales, new CensusOfScotland);
978
+            break;
979
+        case 'en-US':
980
+            $census_places = array(new CensusOfUnitedStates);
981
+            break;
982
+        case 'fr':
983
+        case 'fr-CA':
984
+            $census_places = array(new CensusOfFrance);
985
+            break;
986
+        case 'da':
987
+            $census_places = array(new CensusOfDenmark);
988
+            break;
989
+        case 'de':
990
+            $census_places = array(new CensusOfDeutschland);
991
+            break;
992
+        default:
993
+            $census_places = array();
994
+            break;
995
+        }
996
+        foreach (Census::allCensusPlaces() as $census_place) {
997
+            if (!in_array($census_place, $census_places)) {
998
+                $census_places[] = $census_place;
999
+            }
1000
+        }
1001
+
1002
+        $controller->addInlineJavascript('
1003 1003
 				function selectCensus(el) {
1004 1004
 					var option = jQuery(":selected", el);
1005 1005
 					jQuery("input.DATE", jQuery(el).closest("table")).val(option.val());
@@ -1025,790 +1025,790 @@  discard block
 block discarded – undo
1025 1025
 				}
1026 1026
 			');
1027 1027
 
1028
-		$options = '<option value="">' . I18N::translate('Census date') . '</option>';
1029
-
1030
-		foreach ($census_places as $census_place) {
1031
-			$options .= '<option value=""></option>';
1032
-			foreach ($census_place->allCensusDates() as $census) {
1033
-				$date            = new Date($census->censusDate());
1034
-				$year            = $date->minimumDate()->format('%Y');
1035
-				$place_hierarchy = explode(', ', $census->censusPlace());
1036
-				$options .= '<option value="' . $census->censusDate() . '" data-place="' . $census->censusPlace() . '" data-census="' . get_class($census) . '">' . $place_hierarchy[0] . ' ' . $year . '</option>';
1037
-			}
1038
-		}
1039
-
1040
-		return
1041
-			'<input type="hidden" id="pid_array" name="pid_array" value="">' .
1042
-			'<select class="census-assistant-selector" onchange="selectCensus(this);">' . $options . '</select>';
1043
-	}
1044
-
1045
-	/**
1046
-	 * Prints collapsable fields to add ASSO/RELA, SOUR, OBJE, etc.
1047
-	 *
1048
-	 * @param string $tag
1049
-	 * @param int $level
1050
-	 * @param string $parent_tag
1051
-	 */
1052
-	public static function printAddLayer($tag, $level = 2, $parent_tag = '') {
1053
-		global $WT_TREE;
1054
-
1055
-		switch ($tag) {
1056
-		case 'SOUR':
1057
-			echo '<a href="#" onclick="return expand_layer(\'newsource\');"><i id="newsource_img" class="icon-plus"></i> ', I18N::translate('Add a source citation'), '</a>';
1058
-			echo '<br>';
1059
-			echo '<div id="newsource" style="display: none;">';
1060
-			echo '<table class="facts_table">';
1061
-			// 2 SOUR
1062
-			self::addSimpleTag($level . ' SOUR @');
1063
-			// 3 PAGE
1064
-			self::addSimpleTag(($level + 1) . ' PAGE');
1065
-			// 3 DATA
1066
-			self::addSimpleTag(($level + 1) . ' DATA');
1067
-			// 4 TEXT
1068
-			self::addSimpleTag(($level + 2) . ' TEXT');
1069
-			if ($WT_TREE->getPreference('FULL_SOURCES')) {
1070
-				// 4 DATE
1071
-				self::addSimpleTag(($level + 2) . ' DATE', '', GedcomTag::getLabel('DATA:DATE'));
1072
-				// 3 QUAY
1073
-				self::addSimpleTag(($level + 1) . ' QUAY');
1074
-			}
1075
-			// 3 OBJE
1076
-			self::addSimpleTag(($level + 1) . ' OBJE');
1077
-			// 3 SHARED_NOTE
1078
-			self::addSimpleTag(($level + 1) . ' SHARED_NOTE');
1079
-			echo '</table></div>';
1080
-			break;
1081
-
1082
-		case 'ASSO':
1083
-		case 'ASSO2':
1084
-			//-- Add a new ASSOciate
1085
-			if ($tag === 'ASSO') {
1086
-				echo "<a href=\"#\" onclick=\"return expand_layer('newasso');\"><i id=\"newasso_img\" class=\"icon-plus\"></i> ", I18N::translate('Add an associate'), '</a>';
1087
-				echo '<br>';
1088
-				echo '<div id="newasso" style="display: none;">';
1089
-			} else {
1090
-				echo "<a href=\"#\" onclick=\"return expand_layer('newasso2');\"><i id=\"newasso2_img\" class=\"icon-plus\"></i> ", I18N::translate('Add an associate'), '</a>';
1091
-				echo '<br>';
1092
-				echo '<div id="newasso2" style="display: none;">';
1093
-			}
1094
-			echo '<table class="facts_table">';
1095
-			// 2 ASSO
1096
-			self::addSimpleTag($level . ' _ASSO @');
1097
-			// 3 RELA
1098
-			self::addSimpleTag(($level + 1) . ' RELA');
1099
-			// 3 NOTE
1100
-			self::addSimpleTag(($level + 1) . ' NOTE');
1101
-			// 3 SHARED_NOTE
1102
-			self::addSimpleTag(($level + 1) . ' SHARED_NOTE');
1103
-			echo '</table></div>';
1104
-			break;
1105
-
1106
-		case 'NOTE':
1107
-			//-- Retrieve existing note or add new note to fact
1108
-			echo "<a href=\"#\" onclick=\"return expand_layer('newnote');\"><i id=\"newnote_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a note'), '</a>';
1109
-			echo '<br>';
1110
-			echo '<div id="newnote" style="display: none;">';
1111
-			echo '<table class="facts_table">';
1112
-			// 2 NOTE
1113
-			self::addSimpleTag($level . ' NOTE');
1114
-			echo '</table></div>';
1115
-			break;
1116
-
1117
-		case 'SHARED_NOTE':
1118
-			echo "<a href=\"#\" onclick=\"return expand_layer('newshared_note');\"><i id=\"newshared_note_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a shared note'), '</a>';
1119
-			echo '<br>';
1120
-			echo '<div id="newshared_note" style="display: none;">';
1121
-			echo '<table class="facts_table">';
1122
-			// 2 SHARED NOTE
1123
-			self::addSimpleTag($level . ' SHARED_NOTE', $parent_tag);
1124
-			echo '</table></div>';
1125
-			break;
1126
-
1127
-		case 'OBJE':
1128
-			if ($WT_TREE->getPreference('MEDIA_UPLOAD') >= Auth::accessLevel($WT_TREE)) {
1129
-				echo "<a href=\"#\" onclick=\"return expand_layer('newobje');\"><i id=\"newobje_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a media object'), '</a>';
1130
-				echo '<br>';
1131
-				echo '<div id="newobje" style="display: none;">';
1132
-				echo '<table class="facts_table">';
1133
-				self::addSimpleTag($level . ' OBJE');
1134
-				echo '</table></div>';
1135
-			}
1136
-			break;
1137
-
1138
-		case 'RESN':
1139
-			echo "<a href=\"#\" onclick=\"return expand_layer('newresn');\"><i id=\"newresn_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a restriction'), '</a>';
1140
-			echo '<br>';
1141
-			echo '<div id="newresn" style="display: none;">';
1142
-			echo '<table class="facts_table">';
1143
-			// 2 RESN
1144
-			self::addSimpleTag($level . ' RESN');
1145
-			echo '</table></div>';
1146
-			break;
1147
-		}
1148
-	}
1149
-
1150
-	/**
1151
-	 * Add some empty tags to create a new fact.
1152
-	 *
1153
-	 * @param string $fact
1154
-	 */
1155
-	public static function addSimpleTags($fact) {
1156
-		global $WT_TREE;
1157
-
1158
-		// For new individuals, these facts default to "Y"
1159
-		if ($fact === 'MARR') {
1160
-			self::addSimpleTag('0 ' . $fact . ' Y');
1161
-		} else {
1162
-			self::addSimpleTag('0 ' . $fact);
1163
-		}
1164
-
1165
-		if (!in_array($fact, Config::nonDateFacts())) {
1166
-			self::addSimpleTag('0 DATE', $fact, GedcomTag::getLabel($fact . ':DATE'));
1167
-		}
1168
-
1169
-		if (!in_array($fact, Config::nonPlaceFacts())) {
1170
-			self::addSimpleTag('0 PLAC', $fact, GedcomTag::getLabel($fact . ':PLAC'));
1171
-
1172
-			if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_PLAC_FACTS'), $match)) {
1173
-				foreach ($match[1] as $tag) {
1174
-					self::addSimpleTag('0 ' . $tag, $fact, GedcomTag::getLabel($fact . ':PLAC:' . $tag));
1175
-				}
1176
-			}
1177
-			self::addSimpleTag('0 MAP', $fact);
1178
-			self::addSimpleTag('0 LATI', $fact);
1179
-			self::addSimpleTag('0 LONG', $fact);
1180
-		}
1181
-	}
1182
-
1183
-	/**
1184
-	 * Assemble the pieces of a newly created record into gedcom
1185
-	 *
1186
-	 * @return string
1187
-	 */
1188
-	public static function addNewName() {
1189
-		global $WT_TREE;
1190
-
1191
-		$gedrec = "\n1 NAME " . Filter::post('NAME');
1192
-
1193
-		$tags = array('NPFX', 'GIVN', 'SPFX', 'SURN', 'NSFX');
1194
-
1195
-		if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_NAME_FACTS'), $match)) {
1196
-			$tags = array_merge($tags, $match[1]);
1197
-		}
1198
-
1199
-		// Paternal and Polish and Lithuanian surname traditions can also create a _MARNM
1200
-		$SURNAME_TRADITION = $WT_TREE->getPreference('SURNAME_TRADITION');
1201
-		if ($SURNAME_TRADITION === 'paternal' || $SURNAME_TRADITION === 'polish' || $SURNAME_TRADITION === 'lithuanian') {
1202
-			$tags[] = '_MARNM';
1203
-		}
1204
-
1205
-		foreach (array_unique($tags) as $tag) {
1206
-			$TAG = Filter::post($tag);
1207
-			if ($TAG) {
1208
-				$gedrec .= "\n2 {$tag} {$TAG}";
1209
-			}
1210
-		}
1211
-
1212
-		return $gedrec;
1213
-	}
1214
-
1215
-	/**
1216
-	 * Create a form to add a sex record.
1217
-	 *
1218
-	 * @return string
1219
-	 */
1220
-	public static function addNewSex() {
1221
-		switch (Filter::post('SEX', '[MF]', 'U')) {
1222
-		case 'M':
1223
-			return "\n1 SEX M";
1224
-		case 'F':
1225
-			return "\n1 SEX F";
1226
-		default:
1227
-			return "\n1 SEX U";
1228
-		}
1229
-	}
1230
-
1231
-	/**
1232
-	 * Create a form to add a new fact.
1233
-	 *
1234
-	 * @param string $fact
1235
-	 *
1236
-	 * @return string
1237
-	 */
1238
-	public static function addNewFact($fact) {
1239
-		global $WT_TREE;
1240
-
1241
-		$FACT = Filter::post($fact);
1242
-		$DATE = Filter::post($fact . '_DATE');
1243
-		$PLAC = Filter::post($fact . '_PLAC');
1244
-		if ($DATE || $PLAC || $FACT && $FACT !== 'Y') {
1245
-			if ($FACT && $FACT !== 'Y') {
1246
-				$gedrec = "\n1 " . $fact . ' ' . $FACT;
1247
-			} else {
1248
-				$gedrec = "\n1 " . $fact;
1249
-			}
1250
-			if ($DATE) {
1251
-				$gedrec .= "\n2 DATE " . $DATE;
1252
-			}
1253
-			if ($PLAC) {
1254
-				$gedrec .= "\n2 PLAC " . $PLAC;
1255
-
1256
-				if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_PLAC_FACTS'), $match)) {
1257
-					foreach ($match[1] as $tag) {
1258
-						$TAG = Filter::post($fact . '_' . $tag);
1259
-						if ($TAG) {
1260
-							$gedrec .= "\n3 " . $tag . ' ' . $TAG;
1261
-						}
1262
-					}
1263
-				}
1264
-				$LATI = Filter::post($fact . '_LATI');
1265
-				$LONG = Filter::post($fact . '_LONG');
1266
-				if ($LATI || $LONG) {
1267
-					$gedrec .= "\n3 MAP\n4 LATI " . $LATI . "\n4 LONG " . $LONG;
1268
-				}
1269
-			}
1270
-			if (Filter::postBool('SOUR_' . $fact)) {
1271
-				return self::updateSource($gedrec, 2);
1272
-			} else {
1273
-				return $gedrec;
1274
-			}
1275
-		} elseif ($FACT === 'Y') {
1276
-			if (Filter::postBool('SOUR_' . $fact)) {
1277
-				return self::updateSource("\n1 " . $fact . ' Y', 2);
1278
-			} else {
1279
-				return "\n1 " . $fact . ' Y';
1280
-			}
1281
-		} else {
1282
-			return '';
1283
-		}
1284
-	}
1285
-
1286
-	/**
1287
-	 * This function splits the $glevels, $tag, $islink, and $text arrays so that the
1288
-	 * entries associated with a SOUR record are separate from everything else.
1289
-	 *
1290
-	 * Input arrays:
1291
-	 * - $glevels[] - an array of the gedcom level for each line that was edited
1292
-	 * - $tag[] - an array of the tags for each gedcom line that was edited
1293
-	 * - $islink[] - an array of 1 or 0 values to indicate when the text is a link element
1294
-	 * - $text[] - an array of the text data for each line
1295
-	 *
1296
-	 * Output arrays:
1297
-	 * ** For the SOUR record:
1298
-	 * - $glevelsSOUR[] - an array of the gedcom level for each line that was edited
1299
-	 * - $tagSOUR[] - an array of the tags for each gedcom line that was edited
1300
-	 * - $islinkSOUR[] - an array of 1 or 0 values to indicate when the text is a link element
1301
-	 * - $textSOUR[] - an array of the text data for each line
1302
-	 * ** For the remaining records:
1303
-	 * - $glevelsRest[] - an array of the gedcom level for each line that was edited
1304
-	 * - $tagRest[] - an array of the tags for each gedcom line that was edited
1305
-	 * - $islinkRest[] - an array of 1 or 0 values to indicate when the text is a link element
1306
-	 * - $textRest[] - an array of the text data for each line
1307
-	 */
1308
-	public static function splitSource() {
1309
-		global $glevels, $tag, $islink, $text;
1310
-		global $glevelsSOUR, $tagSOUR, $islinkSOUR, $textSOUR;
1311
-		global $glevelsRest, $tagRest, $islinkRest, $textRest;
1312
-
1313
-		$glevelsSOUR = array();
1314
-		$tagSOUR     = array();
1315
-		$islinkSOUR  = array();
1316
-		$textSOUR    = array();
1317
-
1318
-		$glevelsRest = array();
1319
-		$tagRest     = array();
1320
-		$islinkRest  = array();
1321
-		$textRest    = array();
1322
-
1323
-		$inSOUR = false;
1324
-
1325
-		for ($i = 0; $i < count($glevels); $i++) {
1326
-			if ($inSOUR) {
1327
-				if ($levelSOUR < $glevels[$i]) {
1328
-					$dest = 'S';
1329
-				} else {
1330
-					$inSOUR = false;
1331
-					$dest   = 'R';
1332
-				}
1333
-			} else {
1334
-				if ($tag[$i] === 'SOUR') {
1335
-					$inSOUR    = true;
1336
-					$levelSOUR = $glevels[$i];
1337
-					$dest      = 'S';
1338
-				} else {
1339
-					$dest = 'R';
1340
-				}
1341
-			}
1342
-			if ($dest === 'S') {
1343
-				$glevelsSOUR[] = $glevels[$i];
1344
-				$tagSOUR[]     = $tag[$i];
1345
-				$islinkSOUR[]  = $islink[$i];
1346
-				$textSOUR[]    = $text[$i];
1347
-			} else {
1348
-				$glevelsRest[] = $glevels[$i];
1349
-				$tagRest[]     = $tag[$i];
1350
-				$islinkRest[]  = $islink[$i];
1351
-				$textRest[]    = $text[$i];
1352
-			}
1353
-		}
1354
-	}
1355
-
1356
-	/**
1357
-	 * Add new GEDCOM lines from the $xxxSOUR interface update arrays, which
1358
-	 * were produced by the splitSOUR() function.
1359
-	 * See the FunctionsEdit::handle_updatesges() function for details.
1360
-	 *
1361
-	 * @param string $inputRec
1362
-	 * @param string $levelOverride
1363
-	 *
1364
-	 * @return string
1365
-	 */
1366
-	public static function updateSource($inputRec, $levelOverride = 'no') {
1367
-		global $glevels, $tag, $islink, $text;
1368
-		global $glevelsSOUR, $tagSOUR, $islinkSOUR, $textSOUR;
1369
-
1370
-		if (count($tagSOUR) === 0) {
1371
-			return $inputRec; // No update required
1372
-		}
1373
-
1374
-		// Save original interface update arrays before replacing them with the xxxSOUR ones
1375
-		$glevelsSave = $glevels;
1376
-		$tagSave     = $tag;
1377
-		$islinkSave  = $islink;
1378
-		$textSave    = $text;
1379
-
1380
-		$glevels = $glevelsSOUR;
1381
-		$tag     = $tagSOUR;
1382
-		$islink  = $islinkSOUR;
1383
-		$text    = $textSOUR;
1384
-
1385
-		$myRecord = self::handleUpdates($inputRec, $levelOverride); // Now do the update
1386
-
1387
-		// Restore the original interface update arrays (just in case ...)
1388
-		$glevels = $glevelsSave;
1389
-		$tag     = $tagSave;
1390
-		$islink  = $islinkSave;
1391
-		$text    = $textSave;
1392
-
1393
-		return $myRecord;
1394
-	}
1395
-
1396
-	/**
1397
-	 * Add new GEDCOM lines from the $xxxRest interface update arrays, which
1398
-	 * were produced by the splitSOUR() function.
1399
-	 * See the FunctionsEdit::handle_updatesges() function for details.
1400
-	 *
1401
-	 * @param string $inputRec
1402
-	 * @param string $levelOverride
1403
-	 *
1404
-	 * @return string
1405
-	 */
1406
-	public static function updateRest($inputRec, $levelOverride = 'no') {
1407
-		global $glevels, $tag, $islink, $text;
1408
-		global $glevelsRest, $tagRest, $islinkRest, $textRest;
1409
-
1410
-		if (count($tagRest) === 0) {
1411
-			return $inputRec; // No update required
1412
-		}
1413
-
1414
-		// Save original interface update arrays before replacing them with the xxxRest ones
1415
-		$glevelsSave = $glevels;
1416
-		$tagSave     = $tag;
1417
-		$islinkSave  = $islink;
1418
-		$textSave    = $text;
1419
-
1420
-		$glevels = $glevelsRest;
1421
-		$tag     = $tagRest;
1422
-		$islink  = $islinkRest;
1423
-		$text    = $textRest;
1424
-
1425
-		$myRecord = self::handleUpdates($inputRec, $levelOverride); // Now do the update
1426
-
1427
-		// Restore the original interface update arrays (just in case ...)
1428
-		$glevels = $glevelsSave;
1429
-		$tag     = $tagSave;
1430
-		$islink  = $islinkSave;
1431
-		$text    = $textSave;
1432
-
1433
-		return $myRecord;
1434
-	}
1435
-
1436
-	/**
1437
-	 * Add new gedcom lines from interface update arrays
1438
-	 * The edit_interface and FunctionsEdit::add_simple_tag function produce the following
1439
-	 * arrays incoming from the $_POST form
1440
-	 * - $glevels[] - an array of the gedcom level for each line that was edited
1441
-	 * - $tag[] - an array of the tags for each gedcom line that was edited
1442
-	 * - $islink[] - an array of 1 or 0 values to tell whether the text is a link element and should be surrounded by @@
1443
-	 * - $text[] - an array of the text data for each line
1444
-	 * With these arrays you can recreate the gedcom lines like this
1445
-	 * <code>$glevel[0].' '.$tag[0].' '.$text[0]</code>
1446
-	 * There will be an index in each of these arrays for each line of the gedcom
1447
-	 * fact that is being edited.
1448
-	 * If the $text[] array is empty for the given line, then it means that the
1449
-	 * user removed that line during editing or that the line is supposed to be
1450
-	 * empty (1 DEAT, 1 BIRT) for example. To know if the line should be removed
1451
-	 * there is a section of code that looks ahead to the next lines to see if there
1452
-	 * are sub lines. For example we don't want to remove the 1 DEAT line if it has
1453
-	 * a 2 PLAC or 2 DATE line following it. If there are no sub lines, then the line
1454
-	 * can be safely removed.
1455
-	 *
1456
-	 * @param string $newged the new gedcom record to add the lines to
1457
-	 * @param string $levelOverride Override GEDCOM level specified in $glevels[0]
1458
-	 *
1459
-	 * @return string The updated gedcom record
1460
-	 */
1461
-	public static function handleUpdates($newged, $levelOverride = 'no') {
1462
-		global $glevels, $islink, $tag, $uploaded_files, $text;
1463
-
1464
-		if ($levelOverride === 'no' || count($glevels) === 0) {
1465
-			$levelAdjust = 0;
1466
-		} else {
1467
-			$levelAdjust = $levelOverride - $glevels[0];
1468
-		}
1469
-
1470
-		for ($j = 0; $j < count($glevels); $j++) {
1471
-
1472
-			// Look for empty SOUR reference with non-empty sub-records.
1473
-			// This can happen when the SOUR entry is deleted but its sub-records
1474
-			// were incorrectly left intact.
1475
-			// The sub-records should be deleted.
1476
-			if ($tag[$j] === 'SOUR' && ($text[$j] === '@@' || $text[$j] === '')) {
1477
-				$text[$j] = '';
1478
-				$k        = $j + 1;
1479
-				while (($k < count($glevels)) && ($glevels[$k] > $glevels[$j])) {
1480
-					$text[$k] = '';
1481
-					$k++;
1482
-				}
1483
-			}
1484
-
1485
-			if (trim($text[$j]) !== '') {
1486
-				$pass = true;
1487
-			} else {
1488
-				//-- for facts with empty values they must have sub records
1489
-				//-- this section checks if they have subrecords
1490
-				$k    = $j + 1;
1491
-				$pass = false;
1492
-				while (($k < count($glevels)) && ($glevels[$k] > $glevels[$j])) {
1493
-					if ($text[$k] !== '') {
1494
-						if (($tag[$j] !== 'OBJE') || ($tag[$k] === 'FILE')) {
1495
-							$pass = true;
1496
-							break;
1497
-						}
1498
-					}
1499
-					if (($tag[$k] === 'FILE') && (count($uploaded_files) > 0)) {
1500
-						$filename = array_shift($uploaded_files);
1501
-						if (!empty($filename)) {
1502
-							$text[$k] = $filename;
1503
-							$pass     = true;
1504
-							break;
1505
-						}
1506
-					}
1507
-					$k++;
1508
-				}
1509
-			}
1510
-
1511
-			//-- if the value is not empty or it has sub lines
1512
-			//--- then write the line to the gedcom record
1513
-			//-- we have to let some emtpy text lines pass through... (DEAT, BIRT, etc)
1514
-			if ($pass) {
1515
-				$newline = $glevels[$j] + $levelAdjust . ' ' . $tag[$j];
1516
-				if ($text[$j] !== '') {
1517
-					if ($islink[$j]) {
1518
-						$newline .= ' @' . $text[$j] . '@';
1519
-					} else {
1520
-						$newline .= ' ' . $text[$j];
1521
-					}
1522
-				}
1523
-				$newged .= "\n" . str_replace("\n", "\n" . (1 + substr($newline, 0, 1)) . ' CONT ', $newline);
1524
-			}
1525
-		}
1526
-
1527
-		return $newged;
1528
-	}
1529
-
1530
-	/**
1531
-	 * builds the form for adding new facts
1532
-	 *
1533
-	 * @param string $fact the new fact we are adding
1534
-	 */
1535
-	public static function createAddForm($fact) {
1536
-		global $tags, $WT_TREE;
1537
-
1538
-		$tags = array();
1539
-
1540
-		// handle  MARRiage TYPE
1541
-		if (substr($fact, 0, 5) === 'MARR_') {
1542
-			$tags[0] = 'MARR';
1543
-			self::addSimpleTag('1 MARR');
1544
-			self::insertMissingSubtags($fact);
1545
-		} else {
1546
-			$tags[0] = $fact;
1547
-			if ($fact === '_UID') {
1548
-				$fact .= ' ' . GedcomTag::createUid();
1549
-			}
1550
-			// These new level 1 tags need to be turned into links
1551
-			if (in_array($fact, array('ALIA', 'ASSO'))) {
1552
-				$fact .= ' @';
1553
-			}
1554
-			if (in_array($fact, Config::emptyFacts())) {
1555
-				self::addSimpleTag('1 ' . $fact . ' Y');
1556
-			} else {
1557
-				self::addSimpleTag('1 ' . $fact);
1558
-			}
1559
-			self::insertMissingSubtags($tags[0]);
1560
-			//-- handle the special SOURce case for level 1 sources [ 1759246 ]
1561
-			if ($fact === 'SOUR') {
1562
-				self::addSimpleTag('2 PAGE');
1563
-				self::addSimpleTag('3 TEXT');
1564
-				if ($WT_TREE->getPreference('FULL_SOURCES')) {
1565
-					self::addSimpleTag('3 DATE', '', GedcomTag::getLabel('DATA:DATE'));
1566
-					self::addSimpleTag('2 QUAY');
1567
-				}
1568
-			}
1569
-		}
1570
-	}
1571
-
1572
-	/**
1573
-	 * Create a form to edit a Fact object.
1574
-	 *
1575
-	 * @param Fact $fact
1576
-	 *
1577
-	 * @return string
1578
-	 */
1579
-	public static function createEditForm(Fact $fact) {
1580
-		global $tags;
1581
-
1582
-		$record = $fact->getParent();
1583
-
1584
-		$tags     = array();
1585
-		$gedlines = explode("\n", $fact->getGedcom());
1586
-
1587
-		$linenum = 0;
1588
-		$fields  = explode(' ', $gedlines[$linenum]);
1589
-		$glevel  = $fields[0];
1590
-		$level   = $glevel;
1591
-
1592
-		$type       = $fact->getTag();
1593
-		$level0type = $record::RECORD_TYPE;
1594
-		$level1type = $type;
1595
-
1596
-		$i           = $linenum;
1597
-		$inSource    = false;
1598
-		$levelSource = 0;
1599
-		$add_date    = true;
1600
-
1601
-		// List of tags we would expect at the next level
1602
-		// NB add_missing_subtags() already takes care of the simple cases
1603
-		// where a level 1 tag is missing a level 2 tag. Here we only need to
1604
-		// handle the more complicated cases.
1605
-		$expected_subtags = array(
1606
-			'SOUR' => array('PAGE', 'DATA'),
1607
-			'DATA' => array('TEXT'),
1608
-			'PLAC' => array('MAP'),
1609
-			'MAP'  => array('LATI', 'LONG'),
1610
-		);
1611
-		if ($record->getTree()->getPreference('FULL_SOURCES')) {
1612
-			$expected_subtags['SOUR'][] = 'QUAY';
1613
-			$expected_subtags['DATA'][] = 'DATE';
1614
-		}
1615
-		if (GedcomCodeTemp::isTagLDS($level1type)) {
1616
-			$expected_subtags['STAT'] = array('DATE');
1617
-		}
1618
-		if (in_array($level1type, Config::dateAndTime())) {
1619
-			$expected_subtags['DATE'] = array('TIME'); // TIME is NOT a valid 5.5.1 tag
1620
-		}
1621
-		if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $record->getTree()->getPreference('ADVANCED_PLAC_FACTS'), $match)) {
1622
-			$expected_subtags['PLAC'] = array_merge($match[1], $expected_subtags['PLAC']);
1623
-		}
1624
-
1625
-		$stack = array();
1626
-		// Loop on existing tags :
1627
-		while (true) {
1628
-			// Keep track of our hierarchy, e.g. 1=>BIRT, 2=>PLAC, 3=>FONE
1629
-			$stack[$level] = $type;
1630
-			// Merge them together, e.g. BIRT:PLAC:FONE
1631
-			$label = implode(':', array_slice($stack, 0, $level));
1632
-
1633
-			$text = '';
1634
-			for ($j = 2; $j < count($fields); $j++) {
1635
-				if ($j > 2) {
1636
-					$text .= ' ';
1637
-				}
1638
-				$text .= $fields[$j];
1639
-			}
1640
-			$text = rtrim($text);
1641
-			while (($i + 1 < count($gedlines)) && (preg_match("/" . ($level + 1) . ' CONT ?(.*)/', $gedlines[$i + 1], $cmatch) > 0)) {
1642
-				$text .= "\n" . $cmatch[1];
1643
-				$i++;
1644
-			}
1645
-
1646
-			if ($type === 'SOUR') {
1647
-				$inSource    = true;
1648
-				$levelSource = $level;
1649
-			} elseif ($levelSource >= $level) {
1650
-				$inSource = false;
1651
-			}
1652
-
1653
-			if ($type !== 'CONT') {
1654
-				$tags[]    = $type;
1655
-				$subrecord = $level . ' ' . $type . ' ' . $text;
1656
-				if ($inSource && $type === 'DATE') {
1657
-					self::addSimpleTag($subrecord, '', GedcomTag::getLabel($label, $record));
1658
-				} elseif (!$inSource && $type === 'DATE') {
1659
-					self::addSimpleTag($subrecord, $level1type, GedcomTag::getLabel($label, $record));
1660
-					if ($level === '2') {
1661
-						// We already have a date - no need to add one.
1662
-						$add_date = false;
1663
-					}
1664
-				} elseif ($type === 'STAT') {
1665
-					self::addSimpleTag($subrecord, $level1type, GedcomTag::getLabel($label, $record));
1666
-				} else {
1667
-					self::addSimpleTag($subrecord, $level0type, GedcomTag::getLabel($label, $record));
1668
-				}
1669
-			}
1670
-
1671
-			// Get a list of tags present at the next level
1672
-			$subtags = array();
1673
-			for ($ii = $i + 1; isset($gedlines[$ii]) && preg_match('/^(\d+) (\S+)/', $gedlines[$ii], $mm) && $mm[1] > $level; ++$ii) {
1674
-				if ($mm[1] == $level + 1) {
1675
-					$subtags[] = $mm[2];
1676
-				}
1677
-			}
1678
-
1679
-			// Insert missing tags
1680
-			if (!empty($expected_subtags[$type])) {
1681
-				foreach ($expected_subtags[$type] as $subtag) {
1682
-					if (!in_array($subtag, $subtags)) {
1683
-						self::addSimpleTag(($level + 1) . ' ' . $subtag, '', GedcomTag::getLabel($label . ':' . $subtag));
1684
-						if (!empty($expected_subtags[$subtag])) {
1685
-							foreach ($expected_subtags[$subtag] as $subsubtag) {
1686
-								self::addSimpleTag(($level + 2) . ' ' . $subsubtag, '', GedcomTag::getLabel($label . ':' . $subtag . ':' . $subsubtag));
1687
-							}
1688
-						}
1689
-					}
1690
-				}
1691
-			}
1692
-
1693
-			$i++;
1694
-			if (isset($gedlines[$i])) {
1695
-				$fields = explode(' ', $gedlines[$i]);
1696
-				$level  = $fields[0];
1697
-				if (isset($fields[1])) {
1698
-					$type = trim($fields[1]);
1699
-				} else {
1700
-					$level = 0;
1701
-				}
1702
-			} else {
1703
-				$level = 0;
1704
-			}
1705
-			if ($level <= $glevel) {
1706
-				break;
1707
-			}
1708
-		}
1709
-
1710
-		if ($level1type !== '_PRIM') {
1711
-			self::insertMissingSubtags($level1type, $add_date);
1712
-		}
1713
-
1714
-		return $level1type;
1715
-	}
1716
-
1717
-	/**
1718
-	 * Populates the global $tags array with any missing sub-tags.
1719
-	 *
1720
-	 * @param string $level1tag the type of the level 1 gedcom record
1721
-	 * @param bool $add_date
1722
-	 */
1723
-	public static function insertMissingSubtags($level1tag, $add_date = false) {
1724
-		global $tags, $WT_TREE;
1725
-
1726
-		// handle  MARRiage TYPE
1727
-		$type_val = '';
1728
-		if (substr($level1tag, 0, 5) === 'MARR_') {
1729
-			$type_val  = substr($level1tag, 5);
1730
-			$level1tag = 'MARR';
1731
-		}
1732
-
1733
-		foreach (Config::levelTwoTags() as $key => $value) {
1734
-			if ($key === 'DATE' && in_array($level1tag, Config::nonDateFacts()) || $key === 'PLAC' && in_array($level1tag, Config::nonPlaceFacts())) {
1735
-				continue;
1736
-			}
1737
-			if (in_array($level1tag, $value) && !in_array($key, $tags)) {
1738
-				if ($key === 'TYPE') {
1739
-					self::addSimpleTag('2 TYPE ' . $type_val, $level1tag);
1740
-				} elseif ($level1tag === '_TODO' && $key === 'DATE') {
1741
-					self::addSimpleTag('2 ' . $key . ' ' . strtoupper(date('d M Y')), $level1tag);
1742
-				} elseif ($level1tag === '_TODO' && $key === '_WT_USER') {
1743
-					self::addSimpleTag('2 ' . $key . ' ' . Auth::user()->getUserName(), $level1tag);
1744
-				} elseif ($level1tag === 'TITL' && strstr($WT_TREE->getPreference('ADVANCED_NAME_FACTS'), $key) !== false) {
1745
-					self::addSimpleTag('2 ' . $key, $level1tag);
1746
-				} elseif ($level1tag === 'NAME' && strstr($WT_TREE->getPreference('ADVANCED_NAME_FACTS'), $key) !== false) {
1747
-					self::addSimpleTag('2 ' . $key, $level1tag);
1748
-				} elseif ($level1tag !== 'TITL' && $level1tag !== 'NAME') {
1749
-					self::addSimpleTag('2 ' . $key, $level1tag);
1750
-				}
1751
-				// Add level 3/4 tags as appropriate
1752
-				switch ($key) {
1753
-				case 'PLAC':
1754
-					if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_PLAC_FACTS'), $match)) {
1755
-						foreach ($match[1] as $tag) {
1756
-							self::addSimpleTag('3 ' . $tag, '', GedcomTag::getLabel($level1tag . ':PLAC:' . $tag));
1757
-						}
1758
-					}
1759
-					self::addSimpleTag('3 MAP');
1760
-					self::addSimpleTag('4 LATI');
1761
-					self::addSimpleTag('4 LONG');
1762
-					break;
1763
-				case 'FILE':
1764
-					self::addSimpleTag('3 FORM');
1765
-					break;
1766
-				case 'EVEN':
1767
-					self::addSimpleTag('3 DATE');
1768
-					self::addSimpleTag('3 PLAC');
1769
-					break;
1770
-				case 'STAT':
1771
-					if (GedcomCodeTemp::isTagLDS($level1tag)) {
1772
-						self::addSimpleTag('3 DATE', '', GedcomTag::getLabel('STAT:DATE'));
1773
-					}
1774
-					break;
1775
-				case 'DATE':
1776
-					// TIME is NOT a valid 5.5.1 tag
1777
-					if (in_array($level1tag, Config::dateAndTime())) {
1778
-						self::addSimpleTag('3 TIME');
1779
-					}
1780
-					break;
1781
-				case 'HUSB':
1782
-				case 'WIFE':
1783
-					self::addSimpleTag('3 AGE');
1784
-					break;
1785
-				case 'FAMC':
1786
-					if ($level1tag === 'ADOP') {
1787
-						self::addSimpleTag('3 ADOP BOTH');
1788
-					}
1789
-					break;
1790
-				}
1791
-			} elseif ($key === 'DATE' && $add_date) {
1792
-				self::addSimpleTag('2 DATE', $level1tag, GedcomTag::getLabel($level1tag . ':DATE'));
1793
-			}
1794
-		}
1795
-		// Do something (anything!) with unrecognized custom tags
1796
-		if (substr($level1tag, 0, 1) === '_' && $level1tag !== '_UID' && $level1tag !== '_TODO') {
1797
-			foreach (array('DATE', 'PLAC', 'ADDR', 'AGNC', 'TYPE', 'AGE') as $tag) {
1798
-				if (!in_array($tag, $tags)) {
1799
-					self::addSimpleTag('2 ' . $tag);
1800
-					if ($tag === 'PLAC') {
1801
-						if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_PLAC_FACTS'), $match)) {
1802
-							foreach ($match[1] as $ptag) {
1803
-								self::addSimpleTag('3 ' . $ptag, '', GedcomTag::getLabel($level1tag . ':PLAC:' . $ptag));
1804
-							}
1805
-						}
1806
-						self::addSimpleTag('3 MAP');
1807
-						self::addSimpleTag('4 LATI');
1808
-						self::addSimpleTag('4 LONG');
1809
-					}
1810
-				}
1811
-			}
1812
-		}
1813
-	}
1028
+        $options = '<option value="">' . I18N::translate('Census date') . '</option>';
1029
+
1030
+        foreach ($census_places as $census_place) {
1031
+            $options .= '<option value=""></option>';
1032
+            foreach ($census_place->allCensusDates() as $census) {
1033
+                $date            = new Date($census->censusDate());
1034
+                $year            = $date->minimumDate()->format('%Y');
1035
+                $place_hierarchy = explode(', ', $census->censusPlace());
1036
+                $options .= '<option value="' . $census->censusDate() . '" data-place="' . $census->censusPlace() . '" data-census="' . get_class($census) . '">' . $place_hierarchy[0] . ' ' . $year . '</option>';
1037
+            }
1038
+        }
1039
+
1040
+        return
1041
+            '<input type="hidden" id="pid_array" name="pid_array" value="">' .
1042
+            '<select class="census-assistant-selector" onchange="selectCensus(this);">' . $options . '</select>';
1043
+    }
1044
+
1045
+    /**
1046
+     * Prints collapsable fields to add ASSO/RELA, SOUR, OBJE, etc.
1047
+     *
1048
+     * @param string $tag
1049
+     * @param int $level
1050
+     * @param string $parent_tag
1051
+     */
1052
+    public static function printAddLayer($tag, $level = 2, $parent_tag = '') {
1053
+        global $WT_TREE;
1054
+
1055
+        switch ($tag) {
1056
+        case 'SOUR':
1057
+            echo '<a href="#" onclick="return expand_layer(\'newsource\');"><i id="newsource_img" class="icon-plus"></i> ', I18N::translate('Add a source citation'), '</a>';
1058
+            echo '<br>';
1059
+            echo '<div id="newsource" style="display: none;">';
1060
+            echo '<table class="facts_table">';
1061
+            // 2 SOUR
1062
+            self::addSimpleTag($level . ' SOUR @');
1063
+            // 3 PAGE
1064
+            self::addSimpleTag(($level + 1) . ' PAGE');
1065
+            // 3 DATA
1066
+            self::addSimpleTag(($level + 1) . ' DATA');
1067
+            // 4 TEXT
1068
+            self::addSimpleTag(($level + 2) . ' TEXT');
1069
+            if ($WT_TREE->getPreference('FULL_SOURCES')) {
1070
+                // 4 DATE
1071
+                self::addSimpleTag(($level + 2) . ' DATE', '', GedcomTag::getLabel('DATA:DATE'));
1072
+                // 3 QUAY
1073
+                self::addSimpleTag(($level + 1) . ' QUAY');
1074
+            }
1075
+            // 3 OBJE
1076
+            self::addSimpleTag(($level + 1) . ' OBJE');
1077
+            // 3 SHARED_NOTE
1078
+            self::addSimpleTag(($level + 1) . ' SHARED_NOTE');
1079
+            echo '</table></div>';
1080
+            break;
1081
+
1082
+        case 'ASSO':
1083
+        case 'ASSO2':
1084
+            //-- Add a new ASSOciate
1085
+            if ($tag === 'ASSO') {
1086
+                echo "<a href=\"#\" onclick=\"return expand_layer('newasso');\"><i id=\"newasso_img\" class=\"icon-plus\"></i> ", I18N::translate('Add an associate'), '</a>';
1087
+                echo '<br>';
1088
+                echo '<div id="newasso" style="display: none;">';
1089
+            } else {
1090
+                echo "<a href=\"#\" onclick=\"return expand_layer('newasso2');\"><i id=\"newasso2_img\" class=\"icon-plus\"></i> ", I18N::translate('Add an associate'), '</a>';
1091
+                echo '<br>';
1092
+                echo '<div id="newasso2" style="display: none;">';
1093
+            }
1094
+            echo '<table class="facts_table">';
1095
+            // 2 ASSO
1096
+            self::addSimpleTag($level . ' _ASSO @');
1097
+            // 3 RELA
1098
+            self::addSimpleTag(($level + 1) . ' RELA');
1099
+            // 3 NOTE
1100
+            self::addSimpleTag(($level + 1) . ' NOTE');
1101
+            // 3 SHARED_NOTE
1102
+            self::addSimpleTag(($level + 1) . ' SHARED_NOTE');
1103
+            echo '</table></div>';
1104
+            break;
1105
+
1106
+        case 'NOTE':
1107
+            //-- Retrieve existing note or add new note to fact
1108
+            echo "<a href=\"#\" onclick=\"return expand_layer('newnote');\"><i id=\"newnote_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a note'), '</a>';
1109
+            echo '<br>';
1110
+            echo '<div id="newnote" style="display: none;">';
1111
+            echo '<table class="facts_table">';
1112
+            // 2 NOTE
1113
+            self::addSimpleTag($level . ' NOTE');
1114
+            echo '</table></div>';
1115
+            break;
1116
+
1117
+        case 'SHARED_NOTE':
1118
+            echo "<a href=\"#\" onclick=\"return expand_layer('newshared_note');\"><i id=\"newshared_note_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a shared note'), '</a>';
1119
+            echo '<br>';
1120
+            echo '<div id="newshared_note" style="display: none;">';
1121
+            echo '<table class="facts_table">';
1122
+            // 2 SHARED NOTE
1123
+            self::addSimpleTag($level . ' SHARED_NOTE', $parent_tag);
1124
+            echo '</table></div>';
1125
+            break;
1126
+
1127
+        case 'OBJE':
1128
+            if ($WT_TREE->getPreference('MEDIA_UPLOAD') >= Auth::accessLevel($WT_TREE)) {
1129
+                echo "<a href=\"#\" onclick=\"return expand_layer('newobje');\"><i id=\"newobje_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a media object'), '</a>';
1130
+                echo '<br>';
1131
+                echo '<div id="newobje" style="display: none;">';
1132
+                echo '<table class="facts_table">';
1133
+                self::addSimpleTag($level . ' OBJE');
1134
+                echo '</table></div>';
1135
+            }
1136
+            break;
1137
+
1138
+        case 'RESN':
1139
+            echo "<a href=\"#\" onclick=\"return expand_layer('newresn');\"><i id=\"newresn_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a restriction'), '</a>';
1140
+            echo '<br>';
1141
+            echo '<div id="newresn" style="display: none;">';
1142
+            echo '<table class="facts_table">';
1143
+            // 2 RESN
1144
+            self::addSimpleTag($level . ' RESN');
1145
+            echo '</table></div>';
1146
+            break;
1147
+        }
1148
+    }
1149
+
1150
+    /**
1151
+     * Add some empty tags to create a new fact.
1152
+     *
1153
+     * @param string $fact
1154
+     */
1155
+    public static function addSimpleTags($fact) {
1156
+        global $WT_TREE;
1157
+
1158
+        // For new individuals, these facts default to "Y"
1159
+        if ($fact === 'MARR') {
1160
+            self::addSimpleTag('0 ' . $fact . ' Y');
1161
+        } else {
1162
+            self::addSimpleTag('0 ' . $fact);
1163
+        }
1164
+
1165
+        if (!in_array($fact, Config::nonDateFacts())) {
1166
+            self::addSimpleTag('0 DATE', $fact, GedcomTag::getLabel($fact . ':DATE'));
1167
+        }
1168
+
1169
+        if (!in_array($fact, Config::nonPlaceFacts())) {
1170
+            self::addSimpleTag('0 PLAC', $fact, GedcomTag::getLabel($fact . ':PLAC'));
1171
+
1172
+            if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_PLAC_FACTS'), $match)) {
1173
+                foreach ($match[1] as $tag) {
1174
+                    self::addSimpleTag('0 ' . $tag, $fact, GedcomTag::getLabel($fact . ':PLAC:' . $tag));
1175
+                }
1176
+            }
1177
+            self::addSimpleTag('0 MAP', $fact);
1178
+            self::addSimpleTag('0 LATI', $fact);
1179
+            self::addSimpleTag('0 LONG', $fact);
1180
+        }
1181
+    }
1182
+
1183
+    /**
1184
+     * Assemble the pieces of a newly created record into gedcom
1185
+     *
1186
+     * @return string
1187
+     */
1188
+    public static function addNewName() {
1189
+        global $WT_TREE;
1190
+
1191
+        $gedrec = "\n1 NAME " . Filter::post('NAME');
1192
+
1193
+        $tags = array('NPFX', 'GIVN', 'SPFX', 'SURN', 'NSFX');
1194
+
1195
+        if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_NAME_FACTS'), $match)) {
1196
+            $tags = array_merge($tags, $match[1]);
1197
+        }
1198
+
1199
+        // Paternal and Polish and Lithuanian surname traditions can also create a _MARNM
1200
+        $SURNAME_TRADITION = $WT_TREE->getPreference('SURNAME_TRADITION');
1201
+        if ($SURNAME_TRADITION === 'paternal' || $SURNAME_TRADITION === 'polish' || $SURNAME_TRADITION === 'lithuanian') {
1202
+            $tags[] = '_MARNM';
1203
+        }
1204
+
1205
+        foreach (array_unique($tags) as $tag) {
1206
+            $TAG = Filter::post($tag);
1207
+            if ($TAG) {
1208
+                $gedrec .= "\n2 {$tag} {$TAG}";
1209
+            }
1210
+        }
1211
+
1212
+        return $gedrec;
1213
+    }
1214
+
1215
+    /**
1216
+     * Create a form to add a sex record.
1217
+     *
1218
+     * @return string
1219
+     */
1220
+    public static function addNewSex() {
1221
+        switch (Filter::post('SEX', '[MF]', 'U')) {
1222
+        case 'M':
1223
+            return "\n1 SEX M";
1224
+        case 'F':
1225
+            return "\n1 SEX F";
1226
+        default:
1227
+            return "\n1 SEX U";
1228
+        }
1229
+    }
1230
+
1231
+    /**
1232
+     * Create a form to add a new fact.
1233
+     *
1234
+     * @param string $fact
1235
+     *
1236
+     * @return string
1237
+     */
1238
+    public static function addNewFact($fact) {
1239
+        global $WT_TREE;
1240
+
1241
+        $FACT = Filter::post($fact);
1242
+        $DATE = Filter::post($fact . '_DATE');
1243
+        $PLAC = Filter::post($fact . '_PLAC');
1244
+        if ($DATE || $PLAC || $FACT && $FACT !== 'Y') {
1245
+            if ($FACT && $FACT !== 'Y') {
1246
+                $gedrec = "\n1 " . $fact . ' ' . $FACT;
1247
+            } else {
1248
+                $gedrec = "\n1 " . $fact;
1249
+            }
1250
+            if ($DATE) {
1251
+                $gedrec .= "\n2 DATE " . $DATE;
1252
+            }
1253
+            if ($PLAC) {
1254
+                $gedrec .= "\n2 PLAC " . $PLAC;
1255
+
1256
+                if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_PLAC_FACTS'), $match)) {
1257
+                    foreach ($match[1] as $tag) {
1258
+                        $TAG = Filter::post($fact . '_' . $tag);
1259
+                        if ($TAG) {
1260
+                            $gedrec .= "\n3 " . $tag . ' ' . $TAG;
1261
+                        }
1262
+                    }
1263
+                }
1264
+                $LATI = Filter::post($fact . '_LATI');
1265
+                $LONG = Filter::post($fact . '_LONG');
1266
+                if ($LATI || $LONG) {
1267
+                    $gedrec .= "\n3 MAP\n4 LATI " . $LATI . "\n4 LONG " . $LONG;
1268
+                }
1269
+            }
1270
+            if (Filter::postBool('SOUR_' . $fact)) {
1271
+                return self::updateSource($gedrec, 2);
1272
+            } else {
1273
+                return $gedrec;
1274
+            }
1275
+        } elseif ($FACT === 'Y') {
1276
+            if (Filter::postBool('SOUR_' . $fact)) {
1277
+                return self::updateSource("\n1 " . $fact . ' Y', 2);
1278
+            } else {
1279
+                return "\n1 " . $fact . ' Y';
1280
+            }
1281
+        } else {
1282
+            return '';
1283
+        }
1284
+    }
1285
+
1286
+    /**
1287
+     * This function splits the $glevels, $tag, $islink, and $text arrays so that the
1288
+     * entries associated with a SOUR record are separate from everything else.
1289
+     *
1290
+     * Input arrays:
1291
+     * - $glevels[] - an array of the gedcom level for each line that was edited
1292
+     * - $tag[] - an array of the tags for each gedcom line that was edited
1293
+     * - $islink[] - an array of 1 or 0 values to indicate when the text is a link element
1294
+     * - $text[] - an array of the text data for each line
1295
+     *
1296
+     * Output arrays:
1297
+     * ** For the SOUR record:
1298
+     * - $glevelsSOUR[] - an array of the gedcom level for each line that was edited
1299
+     * - $tagSOUR[] - an array of the tags for each gedcom line that was edited
1300
+     * - $islinkSOUR[] - an array of 1 or 0 values to indicate when the text is a link element
1301
+     * - $textSOUR[] - an array of the text data for each line
1302
+     * ** For the remaining records:
1303
+     * - $glevelsRest[] - an array of the gedcom level for each line that was edited
1304
+     * - $tagRest[] - an array of the tags for each gedcom line that was edited
1305
+     * - $islinkRest[] - an array of 1 or 0 values to indicate when the text is a link element
1306
+     * - $textRest[] - an array of the text data for each line
1307
+     */
1308
+    public static function splitSource() {
1309
+        global $glevels, $tag, $islink, $text;
1310
+        global $glevelsSOUR, $tagSOUR, $islinkSOUR, $textSOUR;
1311
+        global $glevelsRest, $tagRest, $islinkRest, $textRest;
1312
+
1313
+        $glevelsSOUR = array();
1314
+        $tagSOUR     = array();
1315
+        $islinkSOUR  = array();
1316
+        $textSOUR    = array();
1317
+
1318
+        $glevelsRest = array();
1319
+        $tagRest     = array();
1320
+        $islinkRest  = array();
1321
+        $textRest    = array();
1322
+
1323
+        $inSOUR = false;
1324
+
1325
+        for ($i = 0; $i < count($glevels); $i++) {
1326
+            if ($inSOUR) {
1327
+                if ($levelSOUR < $glevels[$i]) {
1328
+                    $dest = 'S';
1329
+                } else {
1330
+                    $inSOUR = false;
1331
+                    $dest   = 'R';
1332
+                }
1333
+            } else {
1334
+                if ($tag[$i] === 'SOUR') {
1335
+                    $inSOUR    = true;
1336
+                    $levelSOUR = $glevels[$i];
1337
+                    $dest      = 'S';
1338
+                } else {
1339
+                    $dest = 'R';
1340
+                }
1341
+            }
1342
+            if ($dest === 'S') {
1343
+                $glevelsSOUR[] = $glevels[$i];
1344
+                $tagSOUR[]     = $tag[$i];
1345
+                $islinkSOUR[]  = $islink[$i];
1346
+                $textSOUR[]    = $text[$i];
1347
+            } else {
1348
+                $glevelsRest[] = $glevels[$i];
1349
+                $tagRest[]     = $tag[$i];
1350
+                $islinkRest[]  = $islink[$i];
1351
+                $textRest[]    = $text[$i];
1352
+            }
1353
+        }
1354
+    }
1355
+
1356
+    /**
1357
+     * Add new GEDCOM lines from the $xxxSOUR interface update arrays, which
1358
+     * were produced by the splitSOUR() function.
1359
+     * See the FunctionsEdit::handle_updatesges() function for details.
1360
+     *
1361
+     * @param string $inputRec
1362
+     * @param string $levelOverride
1363
+     *
1364
+     * @return string
1365
+     */
1366
+    public static function updateSource($inputRec, $levelOverride = 'no') {
1367
+        global $glevels, $tag, $islink, $text;
1368
+        global $glevelsSOUR, $tagSOUR, $islinkSOUR, $textSOUR;
1369
+
1370
+        if (count($tagSOUR) === 0) {
1371
+            return $inputRec; // No update required
1372
+        }
1373
+
1374
+        // Save original interface update arrays before replacing them with the xxxSOUR ones
1375
+        $glevelsSave = $glevels;
1376
+        $tagSave     = $tag;
1377
+        $islinkSave  = $islink;
1378
+        $textSave    = $text;
1379
+
1380
+        $glevels = $glevelsSOUR;
1381
+        $tag     = $tagSOUR;
1382
+        $islink  = $islinkSOUR;
1383
+        $text    = $textSOUR;
1384
+
1385
+        $myRecord = self::handleUpdates($inputRec, $levelOverride); // Now do the update
1386
+
1387
+        // Restore the original interface update arrays (just in case ...)
1388
+        $glevels = $glevelsSave;
1389
+        $tag     = $tagSave;
1390
+        $islink  = $islinkSave;
1391
+        $text    = $textSave;
1392
+
1393
+        return $myRecord;
1394
+    }
1395
+
1396
+    /**
1397
+     * Add new GEDCOM lines from the $xxxRest interface update arrays, which
1398
+     * were produced by the splitSOUR() function.
1399
+     * See the FunctionsEdit::handle_updatesges() function for details.
1400
+     *
1401
+     * @param string $inputRec
1402
+     * @param string $levelOverride
1403
+     *
1404
+     * @return string
1405
+     */
1406
+    public static function updateRest($inputRec, $levelOverride = 'no') {
1407
+        global $glevels, $tag, $islink, $text;
1408
+        global $glevelsRest, $tagRest, $islinkRest, $textRest;
1409
+
1410
+        if (count($tagRest) === 0) {
1411
+            return $inputRec; // No update required
1412
+        }
1413
+
1414
+        // Save original interface update arrays before replacing them with the xxxRest ones
1415
+        $glevelsSave = $glevels;
1416
+        $tagSave     = $tag;
1417
+        $islinkSave  = $islink;
1418
+        $textSave    = $text;
1419
+
1420
+        $glevels = $glevelsRest;
1421
+        $tag     = $tagRest;
1422
+        $islink  = $islinkRest;
1423
+        $text    = $textRest;
1424
+
1425
+        $myRecord = self::handleUpdates($inputRec, $levelOverride); // Now do the update
1426
+
1427
+        // Restore the original interface update arrays (just in case ...)
1428
+        $glevels = $glevelsSave;
1429
+        $tag     = $tagSave;
1430
+        $islink  = $islinkSave;
1431
+        $text    = $textSave;
1432
+
1433
+        return $myRecord;
1434
+    }
1435
+
1436
+    /**
1437
+     * Add new gedcom lines from interface update arrays
1438
+     * The edit_interface and FunctionsEdit::add_simple_tag function produce the following
1439
+     * arrays incoming from the $_POST form
1440
+     * - $glevels[] - an array of the gedcom level for each line that was edited
1441
+     * - $tag[] - an array of the tags for each gedcom line that was edited
1442
+     * - $islink[] - an array of 1 or 0 values to tell whether the text is a link element and should be surrounded by @@
1443
+     * - $text[] - an array of the text data for each line
1444
+     * With these arrays you can recreate the gedcom lines like this
1445
+     * <code>$glevel[0].' '.$tag[0].' '.$text[0]</code>
1446
+     * There will be an index in each of these arrays for each line of the gedcom
1447
+     * fact that is being edited.
1448
+     * If the $text[] array is empty for the given line, then it means that the
1449
+     * user removed that line during editing or that the line is supposed to be
1450
+     * empty (1 DEAT, 1 BIRT) for example. To know if the line should be removed
1451
+     * there is a section of code that looks ahead to the next lines to see if there
1452
+     * are sub lines. For example we don't want to remove the 1 DEAT line if it has
1453
+     * a 2 PLAC or 2 DATE line following it. If there are no sub lines, then the line
1454
+     * can be safely removed.
1455
+     *
1456
+     * @param string $newged the new gedcom record to add the lines to
1457
+     * @param string $levelOverride Override GEDCOM level specified in $glevels[0]
1458
+     *
1459
+     * @return string The updated gedcom record
1460
+     */
1461
+    public static function handleUpdates($newged, $levelOverride = 'no') {
1462
+        global $glevels, $islink, $tag, $uploaded_files, $text;
1463
+
1464
+        if ($levelOverride === 'no' || count($glevels) === 0) {
1465
+            $levelAdjust = 0;
1466
+        } else {
1467
+            $levelAdjust = $levelOverride - $glevels[0];
1468
+        }
1469
+
1470
+        for ($j = 0; $j < count($glevels); $j++) {
1471
+
1472
+            // Look for empty SOUR reference with non-empty sub-records.
1473
+            // This can happen when the SOUR entry is deleted but its sub-records
1474
+            // were incorrectly left intact.
1475
+            // The sub-records should be deleted.
1476
+            if ($tag[$j] === 'SOUR' && ($text[$j] === '@@' || $text[$j] === '')) {
1477
+                $text[$j] = '';
1478
+                $k        = $j + 1;
1479
+                while (($k < count($glevels)) && ($glevels[$k] > $glevels[$j])) {
1480
+                    $text[$k] = '';
1481
+                    $k++;
1482
+                }
1483
+            }
1484
+
1485
+            if (trim($text[$j]) !== '') {
1486
+                $pass = true;
1487
+            } else {
1488
+                //-- for facts with empty values they must have sub records
1489
+                //-- this section checks if they have subrecords
1490
+                $k    = $j + 1;
1491
+                $pass = false;
1492
+                while (($k < count($glevels)) && ($glevels[$k] > $glevels[$j])) {
1493
+                    if ($text[$k] !== '') {
1494
+                        if (($tag[$j] !== 'OBJE') || ($tag[$k] === 'FILE')) {
1495
+                            $pass = true;
1496
+                            break;
1497
+                        }
1498
+                    }
1499
+                    if (($tag[$k] === 'FILE') && (count($uploaded_files) > 0)) {
1500
+                        $filename = array_shift($uploaded_files);
1501
+                        if (!empty($filename)) {
1502
+                            $text[$k] = $filename;
1503
+                            $pass     = true;
1504
+                            break;
1505
+                        }
1506
+                    }
1507
+                    $k++;
1508
+                }
1509
+            }
1510
+
1511
+            //-- if the value is not empty or it has sub lines
1512
+            //--- then write the line to the gedcom record
1513
+            //-- we have to let some emtpy text lines pass through... (DEAT, BIRT, etc)
1514
+            if ($pass) {
1515
+                $newline = $glevels[$j] + $levelAdjust . ' ' . $tag[$j];
1516
+                if ($text[$j] !== '') {
1517
+                    if ($islink[$j]) {
1518
+                        $newline .= ' @' . $text[$j] . '@';
1519
+                    } else {
1520
+                        $newline .= ' ' . $text[$j];
1521
+                    }
1522
+                }
1523
+                $newged .= "\n" . str_replace("\n", "\n" . (1 + substr($newline, 0, 1)) . ' CONT ', $newline);
1524
+            }
1525
+        }
1526
+
1527
+        return $newged;
1528
+    }
1529
+
1530
+    /**
1531
+     * builds the form for adding new facts
1532
+     *
1533
+     * @param string $fact the new fact we are adding
1534
+     */
1535
+    public static function createAddForm($fact) {
1536
+        global $tags, $WT_TREE;
1537
+
1538
+        $tags = array();
1539
+
1540
+        // handle  MARRiage TYPE
1541
+        if (substr($fact, 0, 5) === 'MARR_') {
1542
+            $tags[0] = 'MARR';
1543
+            self::addSimpleTag('1 MARR');
1544
+            self::insertMissingSubtags($fact);
1545
+        } else {
1546
+            $tags[0] = $fact;
1547
+            if ($fact === '_UID') {
1548
+                $fact .= ' ' . GedcomTag::createUid();
1549
+            }
1550
+            // These new level 1 tags need to be turned into links
1551
+            if (in_array($fact, array('ALIA', 'ASSO'))) {
1552
+                $fact .= ' @';
1553
+            }
1554
+            if (in_array($fact, Config::emptyFacts())) {
1555
+                self::addSimpleTag('1 ' . $fact . ' Y');
1556
+            } else {
1557
+                self::addSimpleTag('1 ' . $fact);
1558
+            }
1559
+            self::insertMissingSubtags($tags[0]);
1560
+            //-- handle the special SOURce case for level 1 sources [ 1759246 ]
1561
+            if ($fact === 'SOUR') {
1562
+                self::addSimpleTag('2 PAGE');
1563
+                self::addSimpleTag('3 TEXT');
1564
+                if ($WT_TREE->getPreference('FULL_SOURCES')) {
1565
+                    self::addSimpleTag('3 DATE', '', GedcomTag::getLabel('DATA:DATE'));
1566
+                    self::addSimpleTag('2 QUAY');
1567
+                }
1568
+            }
1569
+        }
1570
+    }
1571
+
1572
+    /**
1573
+     * Create a form to edit a Fact object.
1574
+     *
1575
+     * @param Fact $fact
1576
+     *
1577
+     * @return string
1578
+     */
1579
+    public static function createEditForm(Fact $fact) {
1580
+        global $tags;
1581
+
1582
+        $record = $fact->getParent();
1583
+
1584
+        $tags     = array();
1585
+        $gedlines = explode("\n", $fact->getGedcom());
1586
+
1587
+        $linenum = 0;
1588
+        $fields  = explode(' ', $gedlines[$linenum]);
1589
+        $glevel  = $fields[0];
1590
+        $level   = $glevel;
1591
+
1592
+        $type       = $fact->getTag();
1593
+        $level0type = $record::RECORD_TYPE;
1594
+        $level1type = $type;
1595
+
1596
+        $i           = $linenum;
1597
+        $inSource    = false;
1598
+        $levelSource = 0;
1599
+        $add_date    = true;
1600
+
1601
+        // List of tags we would expect at the next level
1602
+        // NB add_missing_subtags() already takes care of the simple cases
1603
+        // where a level 1 tag is missing a level 2 tag. Here we only need to
1604
+        // handle the more complicated cases.
1605
+        $expected_subtags = array(
1606
+            'SOUR' => array('PAGE', 'DATA'),
1607
+            'DATA' => array('TEXT'),
1608
+            'PLAC' => array('MAP'),
1609
+            'MAP'  => array('LATI', 'LONG'),
1610
+        );
1611
+        if ($record->getTree()->getPreference('FULL_SOURCES')) {
1612
+            $expected_subtags['SOUR'][] = 'QUAY';
1613
+            $expected_subtags['DATA'][] = 'DATE';
1614
+        }
1615
+        if (GedcomCodeTemp::isTagLDS($level1type)) {
1616
+            $expected_subtags['STAT'] = array('DATE');
1617
+        }
1618
+        if (in_array($level1type, Config::dateAndTime())) {
1619
+            $expected_subtags['DATE'] = array('TIME'); // TIME is NOT a valid 5.5.1 tag
1620
+        }
1621
+        if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $record->getTree()->getPreference('ADVANCED_PLAC_FACTS'), $match)) {
1622
+            $expected_subtags['PLAC'] = array_merge($match[1], $expected_subtags['PLAC']);
1623
+        }
1624
+
1625
+        $stack = array();
1626
+        // Loop on existing tags :
1627
+        while (true) {
1628
+            // Keep track of our hierarchy, e.g. 1=>BIRT, 2=>PLAC, 3=>FONE
1629
+            $stack[$level] = $type;
1630
+            // Merge them together, e.g. BIRT:PLAC:FONE
1631
+            $label = implode(':', array_slice($stack, 0, $level));
1632
+
1633
+            $text = '';
1634
+            for ($j = 2; $j < count($fields); $j++) {
1635
+                if ($j > 2) {
1636
+                    $text .= ' ';
1637
+                }
1638
+                $text .= $fields[$j];
1639
+            }
1640
+            $text = rtrim($text);
1641
+            while (($i + 1 < count($gedlines)) && (preg_match("/" . ($level + 1) . ' CONT ?(.*)/', $gedlines[$i + 1], $cmatch) > 0)) {
1642
+                $text .= "\n" . $cmatch[1];
1643
+                $i++;
1644
+            }
1645
+
1646
+            if ($type === 'SOUR') {
1647
+                $inSource    = true;
1648
+                $levelSource = $level;
1649
+            } elseif ($levelSource >= $level) {
1650
+                $inSource = false;
1651
+            }
1652
+
1653
+            if ($type !== 'CONT') {
1654
+                $tags[]    = $type;
1655
+                $subrecord = $level . ' ' . $type . ' ' . $text;
1656
+                if ($inSource && $type === 'DATE') {
1657
+                    self::addSimpleTag($subrecord, '', GedcomTag::getLabel($label, $record));
1658
+                } elseif (!$inSource && $type === 'DATE') {
1659
+                    self::addSimpleTag($subrecord, $level1type, GedcomTag::getLabel($label, $record));
1660
+                    if ($level === '2') {
1661
+                        // We already have a date - no need to add one.
1662
+                        $add_date = false;
1663
+                    }
1664
+                } elseif ($type === 'STAT') {
1665
+                    self::addSimpleTag($subrecord, $level1type, GedcomTag::getLabel($label, $record));
1666
+                } else {
1667
+                    self::addSimpleTag($subrecord, $level0type, GedcomTag::getLabel($label, $record));
1668
+                }
1669
+            }
1670
+
1671
+            // Get a list of tags present at the next level
1672
+            $subtags = array();
1673
+            for ($ii = $i + 1; isset($gedlines[$ii]) && preg_match('/^(\d+) (\S+)/', $gedlines[$ii], $mm) && $mm[1] > $level; ++$ii) {
1674
+                if ($mm[1] == $level + 1) {
1675
+                    $subtags[] = $mm[2];
1676
+                }
1677
+            }
1678
+
1679
+            // Insert missing tags
1680
+            if (!empty($expected_subtags[$type])) {
1681
+                foreach ($expected_subtags[$type] as $subtag) {
1682
+                    if (!in_array($subtag, $subtags)) {
1683
+                        self::addSimpleTag(($level + 1) . ' ' . $subtag, '', GedcomTag::getLabel($label . ':' . $subtag));
1684
+                        if (!empty($expected_subtags[$subtag])) {
1685
+                            foreach ($expected_subtags[$subtag] as $subsubtag) {
1686
+                                self::addSimpleTag(($level + 2) . ' ' . $subsubtag, '', GedcomTag::getLabel($label . ':' . $subtag . ':' . $subsubtag));
1687
+                            }
1688
+                        }
1689
+                    }
1690
+                }
1691
+            }
1692
+
1693
+            $i++;
1694
+            if (isset($gedlines[$i])) {
1695
+                $fields = explode(' ', $gedlines[$i]);
1696
+                $level  = $fields[0];
1697
+                if (isset($fields[1])) {
1698
+                    $type = trim($fields[1]);
1699
+                } else {
1700
+                    $level = 0;
1701
+                }
1702
+            } else {
1703
+                $level = 0;
1704
+            }
1705
+            if ($level <= $glevel) {
1706
+                break;
1707
+            }
1708
+        }
1709
+
1710
+        if ($level1type !== '_PRIM') {
1711
+            self::insertMissingSubtags($level1type, $add_date);
1712
+        }
1713
+
1714
+        return $level1type;
1715
+    }
1716
+
1717
+    /**
1718
+     * Populates the global $tags array with any missing sub-tags.
1719
+     *
1720
+     * @param string $level1tag the type of the level 1 gedcom record
1721
+     * @param bool $add_date
1722
+     */
1723
+    public static function insertMissingSubtags($level1tag, $add_date = false) {
1724
+        global $tags, $WT_TREE;
1725
+
1726
+        // handle  MARRiage TYPE
1727
+        $type_val = '';
1728
+        if (substr($level1tag, 0, 5) === 'MARR_') {
1729
+            $type_val  = substr($level1tag, 5);
1730
+            $level1tag = 'MARR';
1731
+        }
1732
+
1733
+        foreach (Config::levelTwoTags() as $key => $value) {
1734
+            if ($key === 'DATE' && in_array($level1tag, Config::nonDateFacts()) || $key === 'PLAC' && in_array($level1tag, Config::nonPlaceFacts())) {
1735
+                continue;
1736
+            }
1737
+            if (in_array($level1tag, $value) && !in_array($key, $tags)) {
1738
+                if ($key === 'TYPE') {
1739
+                    self::addSimpleTag('2 TYPE ' . $type_val, $level1tag);
1740
+                } elseif ($level1tag === '_TODO' && $key === 'DATE') {
1741
+                    self::addSimpleTag('2 ' . $key . ' ' . strtoupper(date('d M Y')), $level1tag);
1742
+                } elseif ($level1tag === '_TODO' && $key === '_WT_USER') {
1743
+                    self::addSimpleTag('2 ' . $key . ' ' . Auth::user()->getUserName(), $level1tag);
1744
+                } elseif ($level1tag === 'TITL' && strstr($WT_TREE->getPreference('ADVANCED_NAME_FACTS'), $key) !== false) {
1745
+                    self::addSimpleTag('2 ' . $key, $level1tag);
1746
+                } elseif ($level1tag === 'NAME' && strstr($WT_TREE->getPreference('ADVANCED_NAME_FACTS'), $key) !== false) {
1747
+                    self::addSimpleTag('2 ' . $key, $level1tag);
1748
+                } elseif ($level1tag !== 'TITL' && $level1tag !== 'NAME') {
1749
+                    self::addSimpleTag('2 ' . $key, $level1tag);
1750
+                }
1751
+                // Add level 3/4 tags as appropriate
1752
+                switch ($key) {
1753
+                case 'PLAC':
1754
+                    if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_PLAC_FACTS'), $match)) {
1755
+                        foreach ($match[1] as $tag) {
1756
+                            self::addSimpleTag('3 ' . $tag, '', GedcomTag::getLabel($level1tag . ':PLAC:' . $tag));
1757
+                        }
1758
+                    }
1759
+                    self::addSimpleTag('3 MAP');
1760
+                    self::addSimpleTag('4 LATI');
1761
+                    self::addSimpleTag('4 LONG');
1762
+                    break;
1763
+                case 'FILE':
1764
+                    self::addSimpleTag('3 FORM');
1765
+                    break;
1766
+                case 'EVEN':
1767
+                    self::addSimpleTag('3 DATE');
1768
+                    self::addSimpleTag('3 PLAC');
1769
+                    break;
1770
+                case 'STAT':
1771
+                    if (GedcomCodeTemp::isTagLDS($level1tag)) {
1772
+                        self::addSimpleTag('3 DATE', '', GedcomTag::getLabel('STAT:DATE'));
1773
+                    }
1774
+                    break;
1775
+                case 'DATE':
1776
+                    // TIME is NOT a valid 5.5.1 tag
1777
+                    if (in_array($level1tag, Config::dateAndTime())) {
1778
+                        self::addSimpleTag('3 TIME');
1779
+                    }
1780
+                    break;
1781
+                case 'HUSB':
1782
+                case 'WIFE':
1783
+                    self::addSimpleTag('3 AGE');
1784
+                    break;
1785
+                case 'FAMC':
1786
+                    if ($level1tag === 'ADOP') {
1787
+                        self::addSimpleTag('3 ADOP BOTH');
1788
+                    }
1789
+                    break;
1790
+                }
1791
+            } elseif ($key === 'DATE' && $add_date) {
1792
+                self::addSimpleTag('2 DATE', $level1tag, GedcomTag::getLabel($level1tag . ':DATE'));
1793
+            }
1794
+        }
1795
+        // Do something (anything!) with unrecognized custom tags
1796
+        if (substr($level1tag, 0, 1) === '_' && $level1tag !== '_UID' && $level1tag !== '_TODO') {
1797
+            foreach (array('DATE', 'PLAC', 'ADDR', 'AGNC', 'TYPE', 'AGE') as $tag) {
1798
+                if (!in_array($tag, $tags)) {
1799
+                    self::addSimpleTag('2 ' . $tag);
1800
+                    if ($tag === 'PLAC') {
1801
+                        if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_PLAC_FACTS'), $match)) {
1802
+                            foreach ($match[1] as $ptag) {
1803
+                                self::addSimpleTag('3 ' . $ptag, '', GedcomTag::getLabel($level1tag . ':PLAC:' . $ptag));
1804
+                            }
1805
+                        }
1806
+                        self::addSimpleTag('3 MAP');
1807
+                        self::addSimpleTag('4 LATI');
1808
+                        self::addSimpleTag('4 LONG');
1809
+                    }
1810
+                }
1811
+            }
1812
+        }
1813
+    }
1814 1814
 }
Please login to merge, or discard this patch.
Switch Indentation   +302 added lines, -302 removed lines patch added patch discarded remove patch
@@ -559,19 +559,19 @@  discard block
 block discarded – undo
559 559
 		} else {
560 560
 			// Not all facts have help text.
561 561
 			switch ($fact) {
562
-			case 'NAME':
563
-				if ($upperlevel !== 'REPO' && $upperlevel !== 'UNKNOWN') {
564
-					echo FunctionsPrint::helpLink($fact);
565
-				}
566
-				break;
567
-			case 'DATE':
568
-			case 'PLAC':
569
-			case 'RESN':
570
-			case 'ROMN':
571
-			case 'SURN':
572
-			case '_HEB':
573
-				echo FunctionsPrint::helpLink($fact);
574
-				break;
562
+			    case 'NAME':
563
+				    if ($upperlevel !== 'REPO' && $upperlevel !== 'UNKNOWN') {
564
+					    echo FunctionsPrint::helpLink($fact);
565
+				    }
566
+				    break;
567
+			    case 'DATE':
568
+			    case 'PLAC':
569
+			    case 'RESN':
570
+			    case 'ROMN':
571
+			    case 'SURN':
572
+			    case '_HEB':
573
+				    echo FunctionsPrint::helpLink($fact);
574
+				    break;
575 575
 			}
576 576
 		}
577 577
 		// tag level
@@ -716,51 +716,51 @@  discard block
 block discarded – undo
716 716
 
717 717
 				// Extra markup for specific fact types
718 718
 				switch ($fact) {
719
-				case 'ALIA':
720
-				case 'ASSO':
721
-				case '_ASSO':
722
-					echo ' data-autocomplete-type="ASSO" data-autocomplete-extra="input.DATE"';
723
-					break;
724
-				case 'DATE':
725
-					echo ' onblur="valid_date(this);" onmouseout="valid_date(this);"';
726
-					break;
727
-				case 'GIVN':
728
-					echo ' autofocus data-autocomplete-type="GIVN"';
729
-					break;
730
-				case 'LATI':
731
-					echo ' onblur="valid_lati_long(this, \'N\', \'S\');" onmouseout="valid_lati_long(this, \'N\', \'S\');"';
732
-					break;
733
-				case 'LONG':
734
-					echo ' onblur="valid_lati_long(this, \'E\', \'W\');" onmouseout="valid_lati_long(this, \'E\', \'W\');"';
735
-					break;
736
-				case 'NOTE':
737
-					// Shared notes. Inline notes are handled elsewhere.
738
-					echo ' data-autocomplete-type="NOTE"';
739
-					break;
740
-				case 'OBJE':
741
-					echo ' data-autocomplete-type="OBJE"';
742
-					break;
743
-				case 'PAGE':
744
-					echo ' data-autocomplete-type="PAGE" data-autocomplete-extra="#' . $source_element_id . '"';
745
-					break;
746
-				case 'PLAC':
747
-					echo ' data-autocomplete-type="PLAC"';
748
-					break;
749
-				case 'REPO':
750
-					echo ' data-autocomplete-type="REPO"';
751
-					break;
752
-				case 'SOUR':
753
-					$source_element_id = $element_id;
754
-					echo ' data-autocomplete-type="SOUR"';
755
-					break;
756
-				case 'SURN':
757
-				case '_MARNM_SURN':
758
-					echo ' data-autocomplete-type="SURN"';
759
-					break;
760
-				case 'TIME':
761
-					echo ' pattern="([0-1][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])?" dir="ltr" placeholder="' . /* I18N: Examples of valid time formats (hours:minutes:seconds) */
762
-						I18N::translate('hh:mm or hh:mm:ss') . '"';
763
-					break;
719
+				    case 'ALIA':
720
+				    case 'ASSO':
721
+				    case '_ASSO':
722
+					    echo ' data-autocomplete-type="ASSO" data-autocomplete-extra="input.DATE"';
723
+					    break;
724
+				    case 'DATE':
725
+					    echo ' onblur="valid_date(this);" onmouseout="valid_date(this);"';
726
+					    break;
727
+				    case 'GIVN':
728
+					    echo ' autofocus data-autocomplete-type="GIVN"';
729
+					    break;
730
+				    case 'LATI':
731
+					    echo ' onblur="valid_lati_long(this, \'N\', \'S\');" onmouseout="valid_lati_long(this, \'N\', \'S\');"';
732
+					    break;
733
+				    case 'LONG':
734
+					    echo ' onblur="valid_lati_long(this, \'E\', \'W\');" onmouseout="valid_lati_long(this, \'E\', \'W\');"';
735
+					    break;
736
+				    case 'NOTE':
737
+					    // Shared notes. Inline notes are handled elsewhere.
738
+					    echo ' data-autocomplete-type="NOTE"';
739
+					    break;
740
+				    case 'OBJE':
741
+					    echo ' data-autocomplete-type="OBJE"';
742
+					    break;
743
+				    case 'PAGE':
744
+					    echo ' data-autocomplete-type="PAGE" data-autocomplete-extra="#' . $source_element_id . '"';
745
+					    break;
746
+				    case 'PLAC':
747
+					    echo ' data-autocomplete-type="PLAC"';
748
+					    break;
749
+				    case 'REPO':
750
+					    echo ' data-autocomplete-type="REPO"';
751
+					    break;
752
+				    case 'SOUR':
753
+					    $source_element_id = $element_id;
754
+					    echo ' data-autocomplete-type="SOUR"';
755
+					    break;
756
+				    case 'SURN':
757
+				    case '_MARNM_SURN':
758
+					    echo ' data-autocomplete-type="SURN"';
759
+					    break;
760
+				    case 'TIME':
761
+					    echo ' pattern="([0-1][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])?" dir="ltr" placeholder="' . /* I18N: Examples of valid time formats (hours:minutes:seconds) */
762
+						    I18N::translate('hh:mm or hh:mm:ss') . '"';
763
+					    break;
764 764
 				}
765 765
 				echo '>';
766 766
 			}
@@ -810,41 +810,41 @@  discard block
 block discarded – undo
810 810
 
811 811
 		// popup links
812 812
 		switch ($fact) {
813
-		case 'DATE':
814
-			echo self::printCalendarPopup($element_id);
815
-			break;
816
-		case 'FAMC':
817
-		case 'FAMS':
818
-			echo FunctionsPrint::printFindFamilyLink($element_id);
819
-			break;
820
-		case 'ALIA':
821
-		case 'ASSO':
822
-		case '_ASSO':
823
-			echo FunctionsPrint::printFindIndividualLink($element_id, $element_id . '_description');
824
-			break;
825
-		case 'FILE':
826
-			FunctionsPrint::printFindMediaLink($element_id, '0file');
827
-			break;
828
-		case 'SOUR':
829
-			echo FunctionsPrint::printFindSourceLink($element_id, $element_id . '_description'), ' ', self::printAddNewSourceLink($element_id);
830
-			//-- checkboxes to apply '1 SOUR' to BIRT/MARR/DEAT as '2 SOUR'
831
-			if ($level === 1) {
832
-				echo '<br>';
833
-				switch ($WT_TREE->getPreference('PREFER_LEVEL2_SOURCES')) {
834
-				case '2': // records
835
-				$level1_checked = 'checked';
836
-				$level2_checked = '';
837
-				break;
838
-				case '1': // facts
839
-				$level1_checked = '';
840
-				$level2_checked = 'checked';
841
-				break;
842
-				case '0': // none
843
-				default:
844
-				$level1_checked = '';
845
-				$level2_checked = '';
846
-				break;
847
-				}
813
+		    case 'DATE':
814
+			    echo self::printCalendarPopup($element_id);
815
+			    break;
816
+		    case 'FAMC':
817
+		    case 'FAMS':
818
+			    echo FunctionsPrint::printFindFamilyLink($element_id);
819
+			    break;
820
+		    case 'ALIA':
821
+		    case 'ASSO':
822
+		    case '_ASSO':
823
+			    echo FunctionsPrint::printFindIndividualLink($element_id, $element_id . '_description');
824
+			    break;
825
+		    case 'FILE':
826
+			    FunctionsPrint::printFindMediaLink($element_id, '0file');
827
+			    break;
828
+		    case 'SOUR':
829
+			    echo FunctionsPrint::printFindSourceLink($element_id, $element_id . '_description'), ' ', self::printAddNewSourceLink($element_id);
830
+			    //-- checkboxes to apply '1 SOUR' to BIRT/MARR/DEAT as '2 SOUR'
831
+			    if ($level === 1) {
832
+				    echo '<br>';
833
+				    switch ($WT_TREE->getPreference('PREFER_LEVEL2_SOURCES')) {
834
+				        case '2': // records
835
+				        $level1_checked = 'checked';
836
+				        $level2_checked = '';
837
+				        break;
838
+				        case '1': // facts
839
+				        $level1_checked = '';
840
+				        $level2_checked = 'checked';
841
+				        break;
842
+				        case '0': // none
843
+				        default:
844
+				        $level1_checked = '';
845
+				        $level2_checked = '';
846
+				        break;
847
+				    }
848 848
 					if (strpos($bdm, 'B') !== false) {
849 849
 						echo ' <label><input type="checkbox" name="SOUR_INDI" ', $level1_checked, ' value="1">', I18N::translate('Individual'), '</label>';
850 850
 						if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('QUICK_REQUIRED_FACTS'), $matches)) {
@@ -874,26 +874,26 @@  discard block
 block discarded – undo
874 874
 					}
875 875
 				}
876 876
 				break;
877
-		case 'REPO':
878
-			echo FunctionsPrint::printFindRepositoryLink($element_id), ' ', self::printAddNewRepositoryLink($element_id);
879
-			break;
880
-		case 'NOTE':
881
-			// Shared Notes Icons ========================================
882
-			if ($islink) {
883
-				// Print regular Shared Note icons ---------------------------
884
-				echo ' ', FunctionsPrint::printFindNoteLink($element_id, $element_id . '_description'), ' ', self::printAddNewNoteLink($element_id);
885
-				if ($value) {
886
-					echo ' ', self::printEditNoteLink($value);
887
-				}
888
-			}
889
-			break;
890
-		case 'OBJE':
891
-			echo FunctionsPrint::printFindMediaLink($element_id, '1media');
892
-			if (!$value) {
893
-				echo ' ', self::printAddNewMediaLink($element_id);
894
-				$value = 'new';
895
-			}
896
-			break;
877
+		    case 'REPO':
878
+			    echo FunctionsPrint::printFindRepositoryLink($element_id), ' ', self::printAddNewRepositoryLink($element_id);
879
+			    break;
880
+		    case 'NOTE':
881
+			    // Shared Notes Icons ========================================
882
+			    if ($islink) {
883
+				    // Print regular Shared Note icons ---------------------------
884
+				    echo ' ', FunctionsPrint::printFindNoteLink($element_id, $element_id . '_description'), ' ', self::printAddNewNoteLink($element_id);
885
+				    if ($value) {
886
+					    echo ' ', self::printEditNoteLink($value);
887
+				    }
888
+			    }
889
+			    break;
890
+		    case 'OBJE':
891
+			    echo FunctionsPrint::printFindMediaLink($element_id, '1media');
892
+			    if (!$value) {
893
+				    echo ' ', self::printAddNewMediaLink($element_id);
894
+				    $value = 'new';
895
+			    }
896
+			    break;
897 897
 		}
898 898
 
899 899
 		echo '<div id="' . $element_id . '_description">';
@@ -913,38 +913,38 @@  discard block
 block discarded – undo
913 913
 
914 914
 		if ($value && $value !== 'new' && $islink) {
915 915
 			switch ($fact) {
916
-			case 'ALIA':
917
-			case 'ASSO':
918
-			case '_ASSO':
919
-				$tmp = Individual::getInstance($value, $WT_TREE);
920
-				if ($tmp) {
921
-					echo ' ', $tmp->getFullName();
922
-				}
923
-				break;
924
-			case 'SOUR':
925
-				$tmp = Source::getInstance($value, $WT_TREE);
926
-				if ($tmp) {
927
-					echo ' ', $tmp->getFullName();
928
-				}
929
-				break;
930
-			case 'NOTE':
931
-				$tmp = Note::getInstance($value, $WT_TREE);
932
-				if ($tmp) {
933
-					echo ' ', $tmp->getFullName();
934
-				}
935
-				break;
936
-			case 'OBJE':
937
-				$tmp = Media::getInstance($value, $WT_TREE);
938
-				if ($tmp) {
939
-					echo ' ', $tmp->getFullName();
940
-				}
941
-				break;
942
-			case 'REPO':
943
-				$tmp = Repository::getInstance($value, $WT_TREE);
944
-				if ($tmp) {
945
-					echo ' ', $tmp->getFullName();
946
-				}
947
-				break;
916
+			    case 'ALIA':
917
+			    case 'ASSO':
918
+			    case '_ASSO':
919
+				    $tmp = Individual::getInstance($value, $WT_TREE);
920
+				    if ($tmp) {
921
+					    echo ' ', $tmp->getFullName();
922
+				    }
923
+				    break;
924
+			    case 'SOUR':
925
+				    $tmp = Source::getInstance($value, $WT_TREE);
926
+				    if ($tmp) {
927
+					    echo ' ', $tmp->getFullName();
928
+				    }
929
+				    break;
930
+			    case 'NOTE':
931
+				    $tmp = Note::getInstance($value, $WT_TREE);
932
+				    if ($tmp) {
933
+					    echo ' ', $tmp->getFullName();
934
+				    }
935
+				    break;
936
+			    case 'OBJE':
937
+				    $tmp = Media::getInstance($value, $WT_TREE);
938
+				    if ($tmp) {
939
+					    echo ' ', $tmp->getFullName();
940
+				    }
941
+				    break;
942
+			    case 'REPO':
943
+				    $tmp = Repository::getInstance($value, $WT_TREE);
944
+				    if ($tmp) {
945
+					    echo ' ', $tmp->getFullName();
946
+				    }
947
+				    break;
948 948
 			}
949 949
 		}
950 950
 
@@ -969,29 +969,29 @@  discard block
 block discarded – undo
969 969
 
970 970
 		// Show more likely census details at the top of the list.
971 971
 		switch (WT_LOCALE) {
972
-		case 'cs':
973
-			$census_places = array(new CensusOfCzechRepublic);
974
-			break;
975
-		case 'en-AU':
976
-		case 'en-GB':
977
-			$census_places = array(new CensusOfEngland, new CensusOfWales, new CensusOfScotland);
978
-			break;
979
-		case 'en-US':
980
-			$census_places = array(new CensusOfUnitedStates);
981
-			break;
982
-		case 'fr':
983
-		case 'fr-CA':
984
-			$census_places = array(new CensusOfFrance);
985
-			break;
986
-		case 'da':
987
-			$census_places = array(new CensusOfDenmark);
988
-			break;
989
-		case 'de':
990
-			$census_places = array(new CensusOfDeutschland);
991
-			break;
992
-		default:
993
-			$census_places = array();
994
-			break;
972
+		    case 'cs':
973
+			    $census_places = array(new CensusOfCzechRepublic);
974
+			    break;
975
+		    case 'en-AU':
976
+		    case 'en-GB':
977
+			    $census_places = array(new CensusOfEngland, new CensusOfWales, new CensusOfScotland);
978
+			    break;
979
+		    case 'en-US':
980
+			    $census_places = array(new CensusOfUnitedStates);
981
+			    break;
982
+		    case 'fr':
983
+		    case 'fr-CA':
984
+			    $census_places = array(new CensusOfFrance);
985
+			    break;
986
+		    case 'da':
987
+			    $census_places = array(new CensusOfDenmark);
988
+			    break;
989
+		    case 'de':
990
+			    $census_places = array(new CensusOfDeutschland);
991
+			    break;
992
+		    default:
993
+			    $census_places = array();
994
+			    break;
995 995
 		}
996 996
 		foreach (Census::allCensusPlaces() as $census_place) {
997 997
 			if (!in_array($census_place, $census_places)) {
@@ -1053,97 +1053,97 @@  discard block
 block discarded – undo
1053 1053
 		global $WT_TREE;
1054 1054
 
1055 1055
 		switch ($tag) {
1056
-		case 'SOUR':
1057
-			echo '<a href="#" onclick="return expand_layer(\'newsource\');"><i id="newsource_img" class="icon-plus"></i> ', I18N::translate('Add a source citation'), '</a>';
1058
-			echo '<br>';
1059
-			echo '<div id="newsource" style="display: none;">';
1060
-			echo '<table class="facts_table">';
1061
-			// 2 SOUR
1062
-			self::addSimpleTag($level . ' SOUR @');
1063
-			// 3 PAGE
1064
-			self::addSimpleTag(($level + 1) . ' PAGE');
1065
-			// 3 DATA
1066
-			self::addSimpleTag(($level + 1) . ' DATA');
1067
-			// 4 TEXT
1068
-			self::addSimpleTag(($level + 2) . ' TEXT');
1069
-			if ($WT_TREE->getPreference('FULL_SOURCES')) {
1070
-				// 4 DATE
1071
-				self::addSimpleTag(($level + 2) . ' DATE', '', GedcomTag::getLabel('DATA:DATE'));
1072
-				// 3 QUAY
1073
-				self::addSimpleTag(($level + 1) . ' QUAY');
1074
-			}
1075
-			// 3 OBJE
1076
-			self::addSimpleTag(($level + 1) . ' OBJE');
1077
-			// 3 SHARED_NOTE
1078
-			self::addSimpleTag(($level + 1) . ' SHARED_NOTE');
1079
-			echo '</table></div>';
1080
-			break;
1081
-
1082
-		case 'ASSO':
1083
-		case 'ASSO2':
1084
-			//-- Add a new ASSOciate
1085
-			if ($tag === 'ASSO') {
1086
-				echo "<a href=\"#\" onclick=\"return expand_layer('newasso');\"><i id=\"newasso_img\" class=\"icon-plus\"></i> ", I18N::translate('Add an associate'), '</a>';
1087
-				echo '<br>';
1088
-				echo '<div id="newasso" style="display: none;">';
1089
-			} else {
1090
-				echo "<a href=\"#\" onclick=\"return expand_layer('newasso2');\"><i id=\"newasso2_img\" class=\"icon-plus\"></i> ", I18N::translate('Add an associate'), '</a>';
1091
-				echo '<br>';
1092
-				echo '<div id="newasso2" style="display: none;">';
1093
-			}
1094
-			echo '<table class="facts_table">';
1095
-			// 2 ASSO
1096
-			self::addSimpleTag($level . ' _ASSO @');
1097
-			// 3 RELA
1098
-			self::addSimpleTag(($level + 1) . ' RELA');
1099
-			// 3 NOTE
1100
-			self::addSimpleTag(($level + 1) . ' NOTE');
1101
-			// 3 SHARED_NOTE
1102
-			self::addSimpleTag(($level + 1) . ' SHARED_NOTE');
1103
-			echo '</table></div>';
1104
-			break;
1105
-
1106
-		case 'NOTE':
1107
-			//-- Retrieve existing note or add new note to fact
1108
-			echo "<a href=\"#\" onclick=\"return expand_layer('newnote');\"><i id=\"newnote_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a note'), '</a>';
1109
-			echo '<br>';
1110
-			echo '<div id="newnote" style="display: none;">';
1111
-			echo '<table class="facts_table">';
1112
-			// 2 NOTE
1113
-			self::addSimpleTag($level . ' NOTE');
1114
-			echo '</table></div>';
1115
-			break;
1116
-
1117
-		case 'SHARED_NOTE':
1118
-			echo "<a href=\"#\" onclick=\"return expand_layer('newshared_note');\"><i id=\"newshared_note_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a shared note'), '</a>';
1119
-			echo '<br>';
1120
-			echo '<div id="newshared_note" style="display: none;">';
1121
-			echo '<table class="facts_table">';
1122
-			// 2 SHARED NOTE
1123
-			self::addSimpleTag($level . ' SHARED_NOTE', $parent_tag);
1124
-			echo '</table></div>';
1125
-			break;
1126
-
1127
-		case 'OBJE':
1128
-			if ($WT_TREE->getPreference('MEDIA_UPLOAD') >= Auth::accessLevel($WT_TREE)) {
1129
-				echo "<a href=\"#\" onclick=\"return expand_layer('newobje');\"><i id=\"newobje_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a media object'), '</a>';
1130
-				echo '<br>';
1131
-				echo '<div id="newobje" style="display: none;">';
1132
-				echo '<table class="facts_table">';
1133
-				self::addSimpleTag($level . ' OBJE');
1134
-				echo '</table></div>';
1135
-			}
1136
-			break;
1137
-
1138
-		case 'RESN':
1139
-			echo "<a href=\"#\" onclick=\"return expand_layer('newresn');\"><i id=\"newresn_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a restriction'), '</a>';
1140
-			echo '<br>';
1141
-			echo '<div id="newresn" style="display: none;">';
1142
-			echo '<table class="facts_table">';
1143
-			// 2 RESN
1144
-			self::addSimpleTag($level . ' RESN');
1145
-			echo '</table></div>';
1146
-			break;
1056
+		    case 'SOUR':
1057
+			    echo '<a href="#" onclick="return expand_layer(\'newsource\');"><i id="newsource_img" class="icon-plus"></i> ', I18N::translate('Add a source citation'), '</a>';
1058
+			    echo '<br>';
1059
+			    echo '<div id="newsource" style="display: none;">';
1060
+			    echo '<table class="facts_table">';
1061
+			    // 2 SOUR
1062
+			    self::addSimpleTag($level . ' SOUR @');
1063
+			    // 3 PAGE
1064
+			    self::addSimpleTag(($level + 1) . ' PAGE');
1065
+			    // 3 DATA
1066
+			    self::addSimpleTag(($level + 1) . ' DATA');
1067
+			    // 4 TEXT
1068
+			    self::addSimpleTag(($level + 2) . ' TEXT');
1069
+			    if ($WT_TREE->getPreference('FULL_SOURCES')) {
1070
+				    // 4 DATE
1071
+				    self::addSimpleTag(($level + 2) . ' DATE', '', GedcomTag::getLabel('DATA:DATE'));
1072
+				    // 3 QUAY
1073
+				    self::addSimpleTag(($level + 1) . ' QUAY');
1074
+			    }
1075
+			    // 3 OBJE
1076
+			    self::addSimpleTag(($level + 1) . ' OBJE');
1077
+			    // 3 SHARED_NOTE
1078
+			    self::addSimpleTag(($level + 1) . ' SHARED_NOTE');
1079
+			    echo '</table></div>';
1080
+			    break;
1081
+
1082
+		    case 'ASSO':
1083
+		    case 'ASSO2':
1084
+			    //-- Add a new ASSOciate
1085
+			    if ($tag === 'ASSO') {
1086
+				    echo "<a href=\"#\" onclick=\"return expand_layer('newasso');\"><i id=\"newasso_img\" class=\"icon-plus\"></i> ", I18N::translate('Add an associate'), '</a>';
1087
+				    echo '<br>';
1088
+				    echo '<div id="newasso" style="display: none;">';
1089
+			    } else {
1090
+				    echo "<a href=\"#\" onclick=\"return expand_layer('newasso2');\"><i id=\"newasso2_img\" class=\"icon-plus\"></i> ", I18N::translate('Add an associate'), '</a>';
1091
+				    echo '<br>';
1092
+				    echo '<div id="newasso2" style="display: none;">';
1093
+			    }
1094
+			    echo '<table class="facts_table">';
1095
+			    // 2 ASSO
1096
+			    self::addSimpleTag($level . ' _ASSO @');
1097
+			    // 3 RELA
1098
+			    self::addSimpleTag(($level + 1) . ' RELA');
1099
+			    // 3 NOTE
1100
+			    self::addSimpleTag(($level + 1) . ' NOTE');
1101
+			    // 3 SHARED_NOTE
1102
+			    self::addSimpleTag(($level + 1) . ' SHARED_NOTE');
1103
+			    echo '</table></div>';
1104
+			    break;
1105
+
1106
+		    case 'NOTE':
1107
+			    //-- Retrieve existing note or add new note to fact
1108
+			    echo "<a href=\"#\" onclick=\"return expand_layer('newnote');\"><i id=\"newnote_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a note'), '</a>';
1109
+			    echo '<br>';
1110
+			    echo '<div id="newnote" style="display: none;">';
1111
+			    echo '<table class="facts_table">';
1112
+			    // 2 NOTE
1113
+			    self::addSimpleTag($level . ' NOTE');
1114
+			    echo '</table></div>';
1115
+			    break;
1116
+
1117
+		    case 'SHARED_NOTE':
1118
+			    echo "<a href=\"#\" onclick=\"return expand_layer('newshared_note');\"><i id=\"newshared_note_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a shared note'), '</a>';
1119
+			    echo '<br>';
1120
+			    echo '<div id="newshared_note" style="display: none;">';
1121
+			    echo '<table class="facts_table">';
1122
+			    // 2 SHARED NOTE
1123
+			    self::addSimpleTag($level . ' SHARED_NOTE', $parent_tag);
1124
+			    echo '</table></div>';
1125
+			    break;
1126
+
1127
+		    case 'OBJE':
1128
+			    if ($WT_TREE->getPreference('MEDIA_UPLOAD') >= Auth::accessLevel($WT_TREE)) {
1129
+				    echo "<a href=\"#\" onclick=\"return expand_layer('newobje');\"><i id=\"newobje_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a media object'), '</a>';
1130
+				    echo '<br>';
1131
+				    echo '<div id="newobje" style="display: none;">';
1132
+				    echo '<table class="facts_table">';
1133
+				    self::addSimpleTag($level . ' OBJE');
1134
+				    echo '</table></div>';
1135
+			    }
1136
+			    break;
1137
+
1138
+		    case 'RESN':
1139
+			    echo "<a href=\"#\" onclick=\"return expand_layer('newresn');\"><i id=\"newresn_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a restriction'), '</a>';
1140
+			    echo '<br>';
1141
+			    echo '<div id="newresn" style="display: none;">';
1142
+			    echo '<table class="facts_table">';
1143
+			    // 2 RESN
1144
+			    self::addSimpleTag($level . ' RESN');
1145
+			    echo '</table></div>';
1146
+			    break;
1147 1147
 		}
1148 1148
 	}
1149 1149
 
@@ -1219,12 +1219,12 @@  discard block
 block discarded – undo
1219 1219
 	 */
1220 1220
 	public static function addNewSex() {
1221 1221
 		switch (Filter::post('SEX', '[MF]', 'U')) {
1222
-		case 'M':
1223
-			return "\n1 SEX M";
1224
-		case 'F':
1225
-			return "\n1 SEX F";
1226
-		default:
1227
-			return "\n1 SEX U";
1222
+		    case 'M':
1223
+			    return "\n1 SEX M";
1224
+		    case 'F':
1225
+			    return "\n1 SEX F";
1226
+		    default:
1227
+			    return "\n1 SEX U";
1228 1228
 		}
1229 1229
 	}
1230 1230
 
@@ -1750,43 +1750,43 @@  discard block
 block discarded – undo
1750 1750
 				}
1751 1751
 				// Add level 3/4 tags as appropriate
1752 1752
 				switch ($key) {
1753
-				case 'PLAC':
1754
-					if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_PLAC_FACTS'), $match)) {
1755
-						foreach ($match[1] as $tag) {
1756
-							self::addSimpleTag('3 ' . $tag, '', GedcomTag::getLabel($level1tag . ':PLAC:' . $tag));
1757
-						}
1758
-					}
1759
-					self::addSimpleTag('3 MAP');
1760
-					self::addSimpleTag('4 LATI');
1761
-					self::addSimpleTag('4 LONG');
1762
-					break;
1763
-				case 'FILE':
1764
-					self::addSimpleTag('3 FORM');
1765
-					break;
1766
-				case 'EVEN':
1767
-					self::addSimpleTag('3 DATE');
1768
-					self::addSimpleTag('3 PLAC');
1769
-					break;
1770
-				case 'STAT':
1771
-					if (GedcomCodeTemp::isTagLDS($level1tag)) {
1772
-						self::addSimpleTag('3 DATE', '', GedcomTag::getLabel('STAT:DATE'));
1773
-					}
1774
-					break;
1775
-				case 'DATE':
1776
-					// TIME is NOT a valid 5.5.1 tag
1777
-					if (in_array($level1tag, Config::dateAndTime())) {
1778
-						self::addSimpleTag('3 TIME');
1779
-					}
1780
-					break;
1781
-				case 'HUSB':
1782
-				case 'WIFE':
1783
-					self::addSimpleTag('3 AGE');
1784
-					break;
1785
-				case 'FAMC':
1786
-					if ($level1tag === 'ADOP') {
1787
-						self::addSimpleTag('3 ADOP BOTH');
1788
-					}
1789
-					break;
1753
+				    case 'PLAC':
1754
+					    if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_PLAC_FACTS'), $match)) {
1755
+						    foreach ($match[1] as $tag) {
1756
+							    self::addSimpleTag('3 ' . $tag, '', GedcomTag::getLabel($level1tag . ':PLAC:' . $tag));
1757
+						    }
1758
+					    }
1759
+					    self::addSimpleTag('3 MAP');
1760
+					    self::addSimpleTag('4 LATI');
1761
+					    self::addSimpleTag('4 LONG');
1762
+					    break;
1763
+				    case 'FILE':
1764
+					    self::addSimpleTag('3 FORM');
1765
+					    break;
1766
+				    case 'EVEN':
1767
+					    self::addSimpleTag('3 DATE');
1768
+					    self::addSimpleTag('3 PLAC');
1769
+					    break;
1770
+				    case 'STAT':
1771
+					    if (GedcomCodeTemp::isTagLDS($level1tag)) {
1772
+						    self::addSimpleTag('3 DATE', '', GedcomTag::getLabel('STAT:DATE'));
1773
+					    }
1774
+					    break;
1775
+				    case 'DATE':
1776
+					    // TIME is NOT a valid 5.5.1 tag
1777
+					    if (in_array($level1tag, Config::dateAndTime())) {
1778
+						    self::addSimpleTag('3 TIME');
1779
+					    }
1780
+					    break;
1781
+				    case 'HUSB':
1782
+				    case 'WIFE':
1783
+					    self::addSimpleTag('3 AGE');
1784
+					    break;
1785
+				    case 'FAMC':
1786
+					    if ($level1tag === 'ADOP') {
1787
+						    self::addSimpleTag('3 ADOP BOTH');
1788
+					    }
1789
+					    break;
1790 1790
 				}
1791 1791
 			} elseif ($key === 'DATE' && $add_date) {
1792 1792
 				self::addSimpleTag('2 DATE', $level1tag, GedcomTag::getLabel($level1tag . ':DATE'));
Please login to merge, or discard this patch.
Braces   +76 added lines, -38 removed lines patch added patch discarded remove patch
@@ -52,7 +52,8 @@  discard block
 block discarded – undo
52 52
 /**
53 53
  * Class FunctionsEdit - common functions
54 54
  */
55
-class FunctionsEdit {
55
+class FunctionsEdit
56
+{
56 57
 	/**
57 58
 	 * Create a <select> control for a form.
58 59
 	 *
@@ -64,7 +65,8 @@  discard block
 block discarded – undo
64 65
 	 *
65 66
 	 * @return string
66 67
 	 */
67
-	public static function selectEditControl($name, $values, $empty, $selected, $extra = '') {
68
+	public static function selectEditControl($name, $values, $empty, $selected, $extra = '')
69
+	{
68 70
 		if (is_null($empty)) {
69 71
 			$html = '';
70 72
 		} else {
@@ -104,7 +106,8 @@  discard block
 block discarded – undo
104 106
 	 *
105 107
 	 * @return string
106 108
 	 */
107
-	public static function radioButtons($name, $values, $selected, $extra = '') {
109
+	public static function radioButtons($name, $values, $selected, $extra = '')
110
+	{
108 111
 		$html = '';
109 112
 		foreach ($values as $key => $value) {
110 113
 			$html .=
@@ -129,7 +132,8 @@  discard block
 block discarded – undo
129 132
 	 *
130 133
 	 * @return string
131 134
 	 */
132
-	public static function editFieldYesNo($name, $selected = false, $extra = '') {
135
+	public static function editFieldYesNo($name, $selected = false, $extra = '')
136
+	{
133 137
 		return self::radioButtons(
134 138
 			$name, array(I18N::translate('no'), I18N::translate('yes')), $selected, $extra
135 139
 		);
@@ -144,7 +148,8 @@  discard block
 block discarded – undo
144 148
 	 *
145 149
 	 * @return string
146 150
 	 */
147
-	public static function checkbox($name, $is_checked = false, $extra = '') {
151
+	public static function checkbox($name, $is_checked = false, $extra = '')
152
+	{
148 153
 		return '<input type="checkbox" name="' . $name . '" value="1" ' . ($is_checked ? 'checked ' : '') . $extra . '>';
149 154
 	}
150 155
 
@@ -160,7 +165,8 @@  discard block
 block discarded – undo
160 165
 	 *
161 166
 	 * @return string
162 167
 	 */
163
-	public static function twoStateCheckbox($name, $is_checked = 0, $extra = '') {
168
+	public static function twoStateCheckbox($name, $is_checked = 0, $extra = '')
169
+	{
164 170
 		return
165 171
 			'<input type="hidden" id="' . $name . '" name="' . $name . '" value="' . ($is_checked ? 1 : 0) . '">' .
166 172
 			'<input type="checkbox" name="' . $name . '-GUI-ONLY" value="1"' .
@@ -176,7 +182,8 @@  discard block
 block discarded – undo
176 182
 	 *
177 183
 	 * @return string
178 184
 	 */
179
-	public static function editLanguageCheckboxes($parameter_name, $accepted_languages) {
185
+	public static function editLanguageCheckboxes($parameter_name, $accepted_languages)
186
+	{
180 187
 		$html = '';
181 188
 		foreach (I18N::activeLocales() as $locale) {
182 189
 			$html .= '<div class="checkbox">';
@@ -200,7 +207,8 @@  discard block
 block discarded – undo
200 207
 	 *
201 208
 	 * @return string
202 209
 	 */
203
-	public static function editFieldAccessLevel($name, $selected = '', $extra = '') {
210
+	public static function editFieldAccessLevel($name, $selected = '', $extra = '')
211
+	{
204 212
 		$ACCESS_LEVEL = array(
205 213
 			Auth::PRIV_PRIVATE => I18N::translate('Show to visitors'),
206 214
 			Auth::PRIV_USER    => I18N::translate('Show to members'),
@@ -220,7 +228,8 @@  discard block
 block discarded – undo
220 228
 	 *
221 229
 	 * @return string
222 230
 	 */
223
-	public static function editFieldRestriction($name, $selected = '', $extra = '') {
231
+	public static function editFieldRestriction($name, $selected = '', $extra = '')
232
+	{
224 233
 		$RESN = array(
225 234
 			''             => '',
226 235
 			'none'         => I18N::translate('Show to visitors'), // Not valid GEDCOM, but very useful
@@ -241,7 +250,8 @@  discard block
 block discarded – undo
241 250
 	 *
242 251
 	 * @return string
243 252
 	 */
244
-	public static function editFieldContact($name, $selected = '', $extra = '') {
253
+	public static function editFieldContact($name, $selected = '', $extra = '')
254
+	{
245 255
 		// Different ways to contact the users
246 256
 		$CONTACT_METHODS = array(
247 257
 			'messaging'  => I18N::translate('Internal messaging'),
@@ -263,7 +273,8 @@  discard block
 block discarded – undo
263 273
 	 *
264 274
 	 * @return string
265 275
 	 */
266
-	public static function editFieldLanguage($name, $selected = '', $extra = '') {
276
+	public static function editFieldLanguage($name, $selected = '', $extra = '')
277
+	{
267 278
 		$languages = array();
268 279
 		foreach (I18N::activeLocales() as $locale) {
269 280
 			$languages[$locale->languageTag()] = $locale->endonym();
@@ -283,7 +294,8 @@  discard block
 block discarded – undo
283 294
 	 *
284 295
 	 * @return string
285 296
 	 */
286
-	public static function editFieldInteger($name, $selected = '', $min, $max, $extra = '') {
297
+	public static function editFieldInteger($name, $selected = '', $min, $max, $extra = '')
298
+	{
287 299
 		$array = array();
288 300
 		for ($i = $min; $i <= $max; ++$i) {
289 301
 			$array[$i] = I18N::number($i);
@@ -301,7 +313,8 @@  discard block
 block discarded – undo
301 313
 	 *
302 314
 	 * @return string
303 315
 	 */
304
-	public static function editFieldUsername($name, $selected = '', $extra = '') {
316
+	public static function editFieldUsername($name, $selected = '', $extra = '')
317
+	{
305 318
 		$users = array();
306 319
 		foreach (User::all() as $user) {
307 320
 			$users[$user->getUserName()] = $user->getRealName() . ' - ' . $user->getUserName();
@@ -324,7 +337,8 @@  discard block
 block discarded – undo
324 337
 	 *
325 338
 	 * @return string
326 339
 	 */
327
-	public static function editFieldAdoption($name, $selected = '', $extra = '', Individual $individual = null) {
340
+	public static function editFieldAdoption($name, $selected = '', $extra = '', Individual $individual = null)
341
+	{
328 342
 		return self::selectEditControl($name, GedcomCodeAdop::getValues($individual), null, $selected, $extra);
329 343
 	}
330 344
 
@@ -338,7 +352,8 @@  discard block
 block discarded – undo
338 352
 	 *
339 353
 	 * @return string
340 354
 	 */
341
-	public static function editFieldPedigree($name, $selected = '', $extra = '', Individual $individual = null) {
355
+	public static function editFieldPedigree($name, $selected = '', $extra = '', Individual $individual = null)
356
+	{
342 357
 		return self::selectEditControl($name, GedcomCodePedi::getValues($individual), '', $selected, $extra);
343 358
 	}
344 359
 
@@ -352,7 +367,8 @@  discard block
 block discarded – undo
352 367
 	 *
353 368
 	 * @return string
354 369
 	 */
355
-	public static function editFieldNameType($name, $selected = '', $extra = '', Individual $individual = null) {
370
+	public static function editFieldNameType($name, $selected = '', $extra = '', Individual $individual = null)
371
+	{
356 372
 		return self::selectEditControl($name, GedcomCodeName::getValues($individual), '', $selected, $extra);
357 373
 	}
358 374
 
@@ -365,7 +381,8 @@  discard block
 block discarded – undo
365 381
 	 *
366 382
 	 * @return string
367 383
 	 */
368
-	public static function editFieldRelationship($name, $selected = '', $extra = '') {
384
+	public static function editFieldRelationship($name, $selected = '', $extra = '')
385
+	{
369 386
 		$rela_codes = GedcomCodeRela::getValues();
370 387
 		// The user is allowed to specify values that aren't in the list.
371 388
 		if (!array_key_exists($selected, $rela_codes)) {
@@ -383,7 +400,8 @@  discard block
 block discarded – undo
383 400
 	 *
384 401
 	 * @return string
385 402
 	 */
386
-	public static function removeLinks($gedrec, $xref) {
403
+	public static function removeLinks($gedrec, $xref)
404
+	{
387 405
 		$gedrec = preg_replace('/\n1 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[2-9].*)*/', '', $gedrec);
388 406
 		$gedrec = preg_replace('/\n2 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[3-9].*)*/', '', $gedrec);
389 407
 		$gedrec = preg_replace('/\n3 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[4-9].*)*/', '', $gedrec);
@@ -400,7 +418,8 @@  discard block
 block discarded – undo
400 418
 	 *
401 419
 	 * @return string
402 420
 	 */
403
-	public static function printCalendarPopup($id) {
421
+	public static function printCalendarPopup($id)
422
+	{
404 423
 		return
405 424
 			' <a href="#" onclick="cal_toggleDate(\'caldiv' . $id . '\', \'' . $id . '\'); return false;" class="icon-button_calendar" title="' . I18N::translate('Select a date') . '"></a>' .
406 425
 			'<div id="caldiv' . $id . '" style="position:absolute;visibility:hidden;background-color:white;z-index:1000;"></div>';
@@ -413,7 +432,8 @@  discard block
 block discarded – undo
413 432
 	 *
414 433
 	 * @return string
415 434
 	 */
416
-	public static function printAddNewMediaLink($element_id) {
435
+	public static function printAddNewMediaLink($element_id)
436
+	{
417 437
 		return '<a href="#" onclick="pastefield=document.getElementById(\'' . $element_id . '\'); window.open(\'addmedia.php?action=showmediaform\', \'_blank\', edit_window_specs); return false;" class="icon-button_addmedia" title="' . I18N::translate('Create a media object') . '"></a>';
418 438
 	}
419 439
 
@@ -424,7 +444,8 @@  discard block
 block discarded – undo
424 444
 	 *
425 445
 	 * @return string
426 446
 	 */
427
-	public static function printAddNewRepositoryLink($element_id) {
447
+	public static function printAddNewRepositoryLink($element_id)
448
+	{
428 449
 		return '<a href="#" onclick="addnewrepository(document.getElementById(\'' . $element_id . '\')); return false;" class="icon-button_addrepository" title="' . I18N::translate('Create a repository') . '"></a>';
429 450
 	}
430 451
 
@@ -435,7 +456,8 @@  discard block
 block discarded – undo
435 456
 	 *
436 457
 	 * @return string
437 458
 	 */
438
-	public static function printAddNewNoteLink($element_id) {
459
+	public static function printAddNewNoteLink($element_id)
460
+	{
439 461
 		return '<a href="#" onclick="addnewnote(document.getElementById(\'' . $element_id . '\')); return false;" class="icon-button_addnote" title="' . I18N::translate('Create a shared note') . '"></a>';
440 462
 	}
441 463
 
@@ -446,7 +468,8 @@  discard block
 block discarded – undo
446 468
 	 *
447 469
 	 * @return string
448 470
 	 */
449
-	public static function printEditNoteLink($note_id) {
471
+	public static function printEditNoteLink($note_id)
472
+	{
450 473
 		return '<a href="#" onclick="edit_note(\'' . $note_id . '\'); return false;" class="icon-button_note" title="' . I18N::translate('Edit the shared note') . '"></a>';
451 474
 	}
452 475
 
@@ -457,7 +480,8 @@  discard block
 block discarded – undo
457 480
 	 *
458 481
 	 * @return string
459 482
 	 */
460
-	public static function printAddNewSourceLink($element_id) {
483
+	public static function printAddNewSourceLink($element_id)
484
+	{
461 485
 		return '<a href="#" onclick="addnewsource(document.getElementById(\'' . $element_id . '\')); return false;" class="icon-button_addsource" title="' . I18N::translate('Create a source') . '"></a>';
462 486
 	}
463 487
 
@@ -480,7 +504,8 @@  discard block
 block discarded – undo
480 504
 	 *
481 505
 	 * @return string
482 506
 	 */
483
-	public static function addSimpleTag($tag, $upperlevel = '', $label = '', $extra = null, Individual $person = null) {
507
+	public static function addSimpleTag($tag, $upperlevel = '', $label = '', $extra = null, Individual $person = null)
508
+	{
484 509
 		global $tags, $main_fact, $xref, $bdm, $action, $WT_TREE;
485 510
 
486 511
 		// Keep track of SOUR fields, so we can reference them in subsequent PAGE fields.
@@ -964,7 +989,8 @@  discard block
 block discarded – undo
964 989
 	 * @param string $locale - Sort the censuses for this locale
965 990
 	 * @param string $xref   - The individual for whom we are adding a census
966 991
 	 */
967
-	public static function censusDateSelector($locale, $xref) {
992
+	public static function censusDateSelector($locale, $xref)
993
+	{
968 994
 		global $controller;
969 995
 
970 996
 		// Show more likely census details at the top of the list.
@@ -1049,7 +1075,8 @@  discard block
 block discarded – undo
1049 1075
 	 * @param int $level
1050 1076
 	 * @param string $parent_tag
1051 1077
 	 */
1052
-	public static function printAddLayer($tag, $level = 2, $parent_tag = '') {
1078
+	public static function printAddLayer($tag, $level = 2, $parent_tag = '')
1079
+	{
1053 1080
 		global $WT_TREE;
1054 1081
 
1055 1082
 		switch ($tag) {
@@ -1152,7 +1179,8 @@  discard block
 block discarded – undo
1152 1179
 	 *
1153 1180
 	 * @param string $fact
1154 1181
 	 */
1155
-	public static function addSimpleTags($fact) {
1182
+	public static function addSimpleTags($fact)
1183
+	{
1156 1184
 		global $WT_TREE;
1157 1185
 
1158 1186
 		// For new individuals, these facts default to "Y"
@@ -1185,7 +1213,8 @@  discard block
 block discarded – undo
1185 1213
 	 *
1186 1214
 	 * @return string
1187 1215
 	 */
1188
-	public static function addNewName() {
1216
+	public static function addNewName()
1217
+	{
1189 1218
 		global $WT_TREE;
1190 1219
 
1191 1220
 		$gedrec = "\n1 NAME " . Filter::post('NAME');
@@ -1217,7 +1246,8 @@  discard block
 block discarded – undo
1217 1246
 	 *
1218 1247
 	 * @return string
1219 1248
 	 */
1220
-	public static function addNewSex() {
1249
+	public static function addNewSex()
1250
+	{
1221 1251
 		switch (Filter::post('SEX', '[MF]', 'U')) {
1222 1252
 		case 'M':
1223 1253
 			return "\n1 SEX M";
@@ -1235,7 +1265,8 @@  discard block
 block discarded – undo
1235 1265
 	 *
1236 1266
 	 * @return string
1237 1267
 	 */
1238
-	public static function addNewFact($fact) {
1268
+	public static function addNewFact($fact)
1269
+	{
1239 1270
 		global $WT_TREE;
1240 1271
 
1241 1272
 		$FACT = Filter::post($fact);
@@ -1305,7 +1336,8 @@  discard block
 block discarded – undo
1305 1336
 	 * - $islinkRest[] - an array of 1 or 0 values to indicate when the text is a link element
1306 1337
 	 * - $textRest[] - an array of the text data for each line
1307 1338
 	 */
1308
-	public static function splitSource() {
1339
+	public static function splitSource()
1340
+	{
1309 1341
 		global $glevels, $tag, $islink, $text;
1310 1342
 		global $glevelsSOUR, $tagSOUR, $islinkSOUR, $textSOUR;
1311 1343
 		global $glevelsRest, $tagRest, $islinkRest, $textRest;
@@ -1363,7 +1395,8 @@  discard block
 block discarded – undo
1363 1395
 	 *
1364 1396
 	 * @return string
1365 1397
 	 */
1366
-	public static function updateSource($inputRec, $levelOverride = 'no') {
1398
+	public static function updateSource($inputRec, $levelOverride = 'no')
1399
+	{
1367 1400
 		global $glevels, $tag, $islink, $text;
1368 1401
 		global $glevelsSOUR, $tagSOUR, $islinkSOUR, $textSOUR;
1369 1402
 
@@ -1403,7 +1436,8 @@  discard block
 block discarded – undo
1403 1436
 	 *
1404 1437
 	 * @return string
1405 1438
 	 */
1406
-	public static function updateRest($inputRec, $levelOverride = 'no') {
1439
+	public static function updateRest($inputRec, $levelOverride = 'no')
1440
+	{
1407 1441
 		global $glevels, $tag, $islink, $text;
1408 1442
 		global $glevelsRest, $tagRest, $islinkRest, $textRest;
1409 1443
 
@@ -1458,7 +1492,8 @@  discard block
 block discarded – undo
1458 1492
 	 *
1459 1493
 	 * @return string The updated gedcom record
1460 1494
 	 */
1461
-	public static function handleUpdates($newged, $levelOverride = 'no') {
1495
+	public static function handleUpdates($newged, $levelOverride = 'no')
1496
+	{
1462 1497
 		global $glevels, $islink, $tag, $uploaded_files, $text;
1463 1498
 
1464 1499
 		if ($levelOverride === 'no' || count($glevels) === 0) {
@@ -1532,7 +1567,8 @@  discard block
 block discarded – undo
1532 1567
 	 *
1533 1568
 	 * @param string $fact the new fact we are adding
1534 1569
 	 */
1535
-	public static function createAddForm($fact) {
1570
+	public static function createAddForm($fact)
1571
+	{
1536 1572
 		global $tags, $WT_TREE;
1537 1573
 
1538 1574
 		$tags = array();
@@ -1576,7 +1612,8 @@  discard block
 block discarded – undo
1576 1612
 	 *
1577 1613
 	 * @return string
1578 1614
 	 */
1579
-	public static function createEditForm(Fact $fact) {
1615
+	public static function createEditForm(Fact $fact)
1616
+	{
1580 1617
 		global $tags;
1581 1618
 
1582 1619
 		$record = $fact->getParent();
@@ -1720,7 +1757,8 @@  discard block
 block discarded – undo
1720 1757
 	 * @param string $level1tag the type of the level 1 gedcom record
1721 1758
 	 * @param bool $add_date
1722 1759
 	 */
1723
-	public static function insertMissingSubtags($level1tag, $add_date = false) {
1760
+	public static function insertMissingSubtags($level1tag, $add_date = false)
1761
+	{
1724 1762
 		global $tags, $WT_TREE;
1725 1763
 
1726 1764
 		// handle  MARRiage TYPE
Please login to merge, or discard this patch.
app/Functions/FunctionsCharts.php 2 patches
Indentation   +523 added lines, -523 removed lines patch added patch discarded remove patch
@@ -25,527 +25,527 @@
 block discarded – undo
25 25
  * Class FunctionsCharts - common functions
26 26
  */
27 27
 class FunctionsCharts {
28
-	/**
29
-	 * print a table cell with sosa number
30
-	 *
31
-	 * @param int $sosa
32
-	 * @param string $pid optional pid
33
-	 * @param string $arrowDirection direction of link arrow
34
-	 */
35
-	public static function printSosaNumber($sosa, $pid = "", $arrowDirection = "up") {
36
-		if (substr($sosa, -1, 1) == ".") {
37
-			$personLabel = substr($sosa, 0, -1);
38
-		} else {
39
-			$personLabel = $sosa;
40
-		}
41
-		if ($arrowDirection == "blank") {
42
-			$visibility = "hidden";
43
-		} else {
44
-			$visibility = "normal";
45
-		}
46
-		echo "<td class=\"subheaders center\" style=\"vertical-align: middle; text-indent: 0px; margin-top: 0px; white-space: nowrap; visibility: ", $visibility, ";\">";
47
-		echo $personLabel;
48
-		if ($sosa != "1" && $pid != "") {
49
-			if ($arrowDirection == "left") {
50
-				$dir = 0;
51
-			} elseif ($arrowDirection == "right") {
52
-				$dir = 1;
53
-			} elseif ($arrowDirection == "down") {
54
-				$dir = 3;
55
-			} else {
56
-				$dir = 2; // either 'blank' or 'up'
57
-			}
58
-			echo '<br>';
59
-			self::printUrlArrow('#' . $pid, $pid, $dir);
60
-		}
61
-		echo '</td>';
62
-	}
63
-
64
-	/**
65
-	 * print the parents table for a family
66
-	 *
67
-	 * @param Family $family family gedcom ID
68
-	 * @param int $sosa child sosa number
69
-	 * @param string $label indi label (descendancy booklet)
70
-	 * @param string $parid parent ID (descendancy booklet)
71
-	 * @param string $gparid gd-parent ID (descendancy booklet)
72
-	 * @param int $show_full large or small box
73
-	 */
74
-	public static function printFamilyParents(Family $family, $sosa = 0, $label = '', $parid = '', $gparid = '', $show_full = 1) {
75
-
76
-		if ($show_full) {
77
-			$pbheight = Theme::theme()->parameter('chart-box-y') + 14;
78
-		} else {
79
-			$pbheight = Theme::theme()->parameter('compact-chart-box-y') + 14;
80
-		}
81
-
82
-		$husb = $family->getHusband();
83
-		if ($husb) {
84
-			echo '<a name="', $husb->getXref(), '"></a>';
85
-		} else {
86
-			$husb = new Individual('M', "0 @M@ INDI\n1 SEX M", null, $family->getTree());
87
-		}
88
-		$wife = $family->getWife();
89
-		if ($wife) {
90
-			echo '<a name="', $wife->getXref(), '"></a>';
91
-		} else {
92
-			$wife = new Individual('F', "0 @F@ INDI\n1 SEX F", null, $family->getTree());
93
-		}
94
-
95
-		if ($sosa) {
96
-			echo '<p class="name_head">', $family->getFullName(), '</p>';
97
-		}
98
-
99
-		/**
100
-		 * husband side
101
-		 */
102
-		echo '<table cellspacing="0" cellpadding="0" border="0"><tr><td rowspan="2">';
103
-		echo '<table border="0"><tr>';
104
-
105
-		if ($parid) {
106
-			if ($husb->getXref() == $parid) {
107
-				self::printSosaNumber($label);
108
-			} else {
109
-				self::printSosaNumber($label, '', 'blank');
110
-			}
111
-		} elseif ($sosa) {
112
-			self::printSosaNumber($sosa * 2);
113
-		}
114
-		if ($husb->isPendingAddtion()) {
115
-			echo '<td class="facts_value new">';
116
-		} elseif ($husb->isPendingDeletion()) {
117
-			echo '<td class="facts_value old">';
118
-		} else {
119
-			echo '<td>';
120
-		}
121
-		FunctionsPrint::printPedigreePerson($husb, $show_full);
122
-		echo '</td></tr></table>';
123
-		echo '</td>';
124
-		// husband’s parents
125
-		$hfam = $husb->getPrimaryChildFamily();
126
-		if ($hfam) {
127
-			// remove the|| test for $sosa
128
-			echo '<td rowspan="2"><img src="' . Theme::theme()->parameter('image-hline') . '"></td><td rowspan="2"><img src="' . Theme::theme()->parameter('image-vline') . '" width="3" height="' . ($pbheight + 9) . '"></td>';
129
-			echo '<td><img class="line5" src="' . Theme::theme()->parameter('image-hline') . '"></td><td>';
130
-			// husband’s father
131
-			if ($hfam && $hfam->getHusband()) {
132
-				echo '<table border="0"><tr>';
133
-				if ($sosa > 0) {
134
-					self::printSosaNumber($sosa * 4, $hfam->getHusband()->getXref(), 'down');
135
-				}
136
-				if (!empty($gparid) && $hfam->getHusband()->getXref() == $gparid) {
137
-					self::printSosaNumber(trim(substr($label, 0, -3), '.') . '.');
138
-				}
139
-				echo '<td>';
140
-				FunctionsPrint::printPedigreePerson($hfam->getHusband(), $show_full);
141
-				echo '</td></tr></table>';
142
-			} elseif ($hfam && !$hfam->getHusband()) {
143
-				// Empty box for grandfather
144
-				echo '<table border="0"><tr>';
145
-				echo '<td>';
146
-				FunctionsPrint::printPedigreePerson($hfam->getHusband(), $show_full);
147
-				echo '</td></tr></table>';
148
-			}
149
-			echo '</td>';
150
-		}
151
-		if ($hfam && ($sosa != -1)) {
152
-			echo '<td rowspan="2">';
153
-			self::printUrlArrow(($sosa == 0 ? '?famid=' . $hfam->getXref() . '&amp;ged=' . $hfam->getTree()->getNameUrl() : '#' . $hfam->getXref()), $hfam->getXref(), 1);
154
-			echo '</td>';
155
-		}
156
-		if ($hfam) {
157
-			// husband’s mother
158
-			echo '</tr><tr><td><img src="' . Theme::theme()->parameter('image-hline') . '"></td><td>';
159
-			if ($hfam && $hfam->getWife()) {
160
-				echo '<table border=\'0\'><tr>';
161
-				if ($sosa > 0) {
162
-					self::printSosaNumber($sosa * 4 + 1, $hfam->getWife()->getXref(), 'down');
163
-				}
164
-				if (!empty($gparid) && $hfam->getWife()->getXref() == $gparid) {
165
-					self::printSosaNumber(trim(substr($label, 0, -3), '.') . '.');
166
-				}
167
-				echo '<td>';
168
-				FunctionsPrint::printPedigreePerson($hfam->getWife(), $show_full);
169
-				echo '</td></tr></table>';
170
-			} elseif ($hfam && !$hfam->getWife()) {
171
-				// Empty box for grandmother
172
-				echo '<table border="0"><tr>';
173
-				echo '<td>';
174
-				FunctionsPrint::printPedigreePerson($hfam->getWife(), $show_full);
175
-				echo '</td></tr></table>';
176
-			}
177
-			echo '</td>';
178
-		}
179
-		echo '</tr></table>';
180
-		if ($sosa && $family->canShow()) {
181
-			foreach ($family->getFacts(WT_EVENTS_MARR) as $fact) {
182
-				echo '<a href="', $family->getHtmlUrl(), '" class="details1">';
183
-				echo str_repeat('&nbsp;', 10);
184
-				echo $fact->summary();
185
-				echo '</a>';
186
-			}
187
-		} else {
188
-			echo '<br>';
189
-		}
190
-
191
-		/**
192
-		 * wife side
193
-		 */
194
-		echo '<table cellspacing="0" cellpadding="0" border="0"><tr><td rowspan="2">';
195
-		echo '<table><tr>';
196
-		if ($parid) {
197
-			if ($wife->getXref() == $parid) {
198
-				self::printSosaNumber($label);
199
-			} else {
200
-				self::printSosaNumber($label, '', 'blank');
201
-			}
202
-		} elseif ($sosa) {
203
-			self::printSosaNumber($sosa * 2 + 1);
204
-		}
205
-		if ($wife->isPendingAddtion()) {
206
-			echo '<td class="facts_value new">';
207
-		} elseif ($wife->isPendingDeletion()) {
208
-			echo '<td class="facts_value old">';
209
-		} else {
210
-			echo '<td>';
211
-		}
212
-		FunctionsPrint::printPedigreePerson($wife, $show_full);
213
-		echo '</td></tr></table>';
214
-		echo '</td>';
215
-		// wife’s parents
216
-		$hfam = $wife->getPrimaryChildFamily();
217
-
218
-		if ($hfam) {
219
-			echo '<td rowspan="2"><img src="' . Theme::theme()->parameter('image-hline') . '"></td><td rowspan="2"><img src="' . Theme::theme()->parameter('image-vline') . '" width="3" height="' . ($pbheight + 9) . '"></td>';
220
-			echo '<td><img class="line5" src="' . Theme::theme()->parameter('image-hline') . '"></td><td>';
221
-			// wife’s father
222
-			if ($hfam && $hfam->getHusband()) {
223
-				echo '<table><tr>';
224
-				if ($sosa > 0) {
225
-					self::printSosaNumber($sosa * 4 + 2, $hfam->getHusband()->getXref(), 'down');
226
-				}
227
-				if (!empty($gparid) && $hfam->getHusband()->getXref() == $gparid) {
228
-					self::printSosaNumber(trim(substr($label, 0, -3), '.') . '.');
229
-				}
230
-				echo '<td>';
231
-				FunctionsPrint::printPedigreePerson($hfam->getHusband(), $show_full);
232
-				echo '</td></tr></table>';
233
-			} elseif ($hfam && !$hfam->getHusband()) {
234
-				// Empty box for grandfather
235
-				echo '<table border="0"><tr>';
236
-				echo '<td>';
237
-				FunctionsPrint::printPedigreePerson($hfam->getHusband(), $show_full);
238
-				echo '</td></tr></table>';
239
-			}
240
-			echo '</td>';
241
-		}
242
-		if ($hfam && ($sosa != -1)) {
243
-			echo '<td rowspan="2">';
244
-			self::printUrlArrow(($sosa == 0 ? '?famid=' . $hfam->getXref() . '&amp;ged=' . $hfam->getTree()->getNameUrl() : '#' . $hfam->getXref()), $hfam->getXref(), 1);
245
-			echo '</td>';
246
-		}
247
-		if ($hfam) {
248
-			// wife’s mother
249
-			echo '</tr><tr><td><img src="' . Theme::theme()->parameter('image-hline') . '"></td><td>';
250
-			if ($hfam && $hfam->getWife()) {
251
-				echo '<table><tr>';
252
-				if ($sosa > 0) {
253
-					self::printSosaNumber($sosa * 4 + 3, $hfam->getWife()->getXref(), 'down');
254
-				}
255
-				if (!empty($gparid) && $hfam->getWife()->getXref() == $gparid) {
256
-					self::printSosaNumber(trim(substr($label, 0, -3), '.') . '.');
257
-				}
258
-				echo '<td>';
259
-				FunctionsPrint::printPedigreePerson($hfam->getWife(), $show_full);
260
-				echo '</td></tr></table>';
261
-			} elseif ($hfam && !$hfam->getWife()) {
262
-				// Empty box for grandmother
263
-				echo '<table border="0"><tr>';
264
-				echo '<td>';
265
-				FunctionsPrint::printPedigreePerson($hfam->getWife(), $show_full);
266
-				echo '</td></tr></table>';
267
-			}
268
-			echo '</td>';
269
-		}
270
-		echo '</tr></table>';
271
-	}
272
-
273
-	/**
274
-	 * print the children table for a family
275
-	 *
276
-	 * @param Family $family family
277
-	 * @param string $childid child ID
278
-	 * @param int $sosa child sosa number
279
-	 * @param string $label indi label (descendancy booklet)
280
-	 * @param int $show_cousins display cousins on chart
281
-	 * @param int $show_full large or small box
282
-	 */
283
-	public static function printFamilyChildren(Family $family, $childid = '', $sosa = 0, $label = '', $show_cousins = 0, $show_full = 1) {
284
-
285
-		if ($show_full) {
286
-			$bheight = Theme::theme()->parameter('chart-box-y');
287
-		} else {
288
-			$bheight = Theme::theme()->parameter('compact-chart-box-y');
289
-		}
290
-
291
-		$pbheight = $bheight + 14;
292
-
293
-		$children = $family->getChildren();
294
-		$numchil  = count($children);
295
-
296
-		echo '<table border="0" cellpadding="0" cellspacing="2"><tr>';
297
-		if ($sosa > 0) {
298
-			echo '<td></td>';
299
-		}
300
-		echo '<td><span class="subheaders">';
301
-		if ($numchil == 0) {
302
-			echo I18N::translate('No children');
303
-		} else {
304
-			echo I18N::plural('%s child', '%s children', $numchil, $numchil);
305
-		}
306
-		echo '</span>';
307
-
308
-		if ($sosa == 0 && Auth::isEditor($family->getTree())) {
309
-			echo '<br>';
310
-			echo "<a href=\"#\" onclick=\"return add_child_to_family('", $family->getXref(), "', 'U');\">" . I18N::translate('Add a child to this family') . "</a>";
311
-			echo ' <a class="icon-sex_m_15x15" href="#" onclick="return add_child_to_family(\'', $family->getXref(), '\', \'M\');" title="', I18N::translate('son'), '"></a>';
312
-			echo ' <a class="icon-sex_f_15x15" href="#" onclick="return add_child_to_family(\'', $family->getXref(), '\', \'F\');" title="', I18N::translate('daughter'), '"></a>';
313
-			echo '<br><br>';
314
-		}
315
-		echo '</td>';
316
-		if ($sosa > 0) {
317
-			echo '<td></td><td></td>';
318
-		}
319
-		echo '</tr>';
320
-
321
-		$nchi = 1;
322
-		if ($children) {
323
-			foreach ($children as $child) {
324
-				echo '<tr>';
325
-				if ($sosa != 0) {
326
-					if ($child->getXref() == $childid) {
327
-						self::printSosaNumber($sosa, $childid);
328
-					} elseif (empty($label)) {
329
-						self::printSosaNumber("");
330
-					} else {
331
-						self::printSosaNumber($label . ($nchi++) . ".");
332
-					}
333
-				}
334
-				if ($child->isPendingAddtion()) {
335
-					echo '<td class="new">';
336
-				} elseif ($child->isPendingDeletion()) {
337
-					echo '<td class="old">';
338
-				} else {
339
-					echo '<td>';
340
-				}
341
-				FunctionsPrint::printPedigreePerson($child, $show_full);
342
-				echo '</td>';
343
-				if ($sosa != 0) {
344
-					// loop for all families where current child is a spouse
345
-					$famids = $child->getSpouseFamilies();
346
-
347
-					$maxfam = count($famids) - 1;
348
-					for ($f = 0; $f <= $maxfam; $f++) {
349
-						$famid_child = $famids[$f]->getXref();
350
-						// multiple marriages
351
-						if ($f > 0) {
352
-							echo '</tr><tr><td></td>';
353
-							echo '<td style="text-align:end; vertical-align: top;">';
354
-
355
-							//find out how many cousins there are to establish vertical line on second families
356
-							$fchildren = $famids[$f]->getChildren();
357
-							$kids      = count($fchildren);
358
-							$Pheader   = ($bheight - 1) * $kids;
359
-							$PBadj     = 6; // default
360
-							if ($show_cousins > 0) {
361
-								if ($kids) {
362
-									$PBadj = max(0, $Pheader / 2 + $kids * 4.5);
363
-								}
364
-							}
365
-
366
-							if ($f == $maxfam) {
367
-								echo '<img height="' . ((($bheight / 2)) + $PBadj) . 'px"';
368
-							} else {
369
-								echo '<img height="' . $pbheight . 'px"';
370
-							}
371
-							echo ' width="3" src="' . Theme::theme()->parameter('image-vline') . '">';
372
-							echo '</td>';
373
-						}
374
-						echo '<td class="details1" style="text-align:center;">';
375
-						$spouse = $famids[$f]->getSpouse($child);
376
-
377
-						$marr = $famids[$f]->getFirstFact('MARR');
378
-						$div  = $famids[$f]->getFirstFact('DIV');
379
-						if ($marr) {
380
-							// marriage date
381
-							echo $marr->getDate()->minimumDate()->format('%Y');
382
-							// divorce date
383
-							if ($div) {
384
-								echo '–', $div->getDate()->minimumDate()->format('%Y');
385
-							}
386
-						}
387
-						echo "<br><img width=\"100%\" class=\"line5\" height=\"3\" src=\"" . Theme::theme()->parameter('image-hline') . "\" alt=\"\">";
388
-						echo "</td>";
389
-						// spouse information
390
-						echo "<td style=\"vertical-align: center;";
391
-						if (!empty($divrec)) {
392
-							echo " filter:alpha(opacity=40);opacity:0.4;\">";
393
-						} else {
394
-							echo "\">";
395
-						}
396
-						FunctionsPrint::printPedigreePerson($spouse, $show_full);
397
-						echo "</td>";
398
-						// cousins
399
-						if ($show_cousins) {
400
-							self::printCousins($famid_child, $show_full);
401
-						}
402
-					}
403
-				}
404
-				echo "</tr>";
405
-			}
406
-		} elseif ($sosa < 1) {
407
-			// message 'no children' except for sosa
408
-			if (preg_match('/\n1 NCHI (\d+)/', $family->getGedcom(), $match) && $match[1] == 0) {
409
-				echo '<tr><td><i class="icon-childless"></i> ' . I18N::translate('This family remained childless') . '</td></tr>';
410
-			}
411
-		}
412
-		echo "</table><br>";
413
-	}
414
-
415
-	/**
416
-	 * print a family with Sosa-Stradonitz numbering system
417
-	 * ($rootid=1, father=2, mother=3 ...)
418
-	 *
419
-	 * @param string $famid family gedcom ID
420
-	 * @param string $childid tree root ID
421
-	 * @param int $sosa starting sosa number
422
-	 * @param string $label indi label (descendancy booklet)
423
-	 * @param string $parid parent ID (descendancy booklet)
424
-	 * @param string $gparid gd-parent ID (descendancy booklet)
425
-	 * @param int $show_cousins display cousins on chart
426
-	 * @param int $show_full large or small box
427
-	 */
428
-	public static function printSosaFamily($famid, $childid, $sosa, $label = '', $parid = '', $gparid = '', $show_cousins = 0, $show_full = 1) {
429
-		global $WT_TREE;
430
-
431
-		echo '<hr>';
432
-		echo '<p style="page-break-before: always;">';
433
-		if (!empty($famid)) {
434
-			echo '<a name="', $famid, '"></a>';
435
-		}
436
-		self::printFamilyParents(Family::getInstance($famid, $WT_TREE), $sosa, $label, $parid, $gparid, $show_full);
437
-		echo '<br>';
438
-		echo '<table><tr><td>';
439
-		self::printFamilyChildren(Family::getInstance($famid, $WT_TREE), $childid, $sosa, $label, $show_cousins, $show_full);
440
-		echo '</td></tr></table>';
441
-		echo '<br>';
442
-	}
443
-
444
-	/**
445
-	 * print an arrow to a new url
446
-	 *
447
-	 * @param string $url target url
448
-	 * @param string $label arrow label
449
-	 * @param int $dir arrow direction 0=left 1=right 2=up 3=down (default=2)
450
-	 */
451
-	public static function printUrlArrow($url, $label, $dir = 2) {
452
-		if ($url === '') {
453
-			return;
454
-		}
455
-
456
-		// arrow direction
457
-		$adir = $dir;
458
-		if (I18N::direction() === 'rtl' && $dir === 0) {
459
-			$adir = 1;
460
-		}
461
-		if (I18N::direction() === 'rtl' && $dir === 1) {
462
-			$adir = 0;
463
-		}
464
-
465
-		// arrow style     0         1         2         3
466
-		$array_style = array('icon-larrow', 'icon-rarrow', 'icon-uarrow', 'icon-darrow');
467
-		$astyle      = $array_style[$adir];
468
-
469
-		// Labels include people’s names, which may contain markup
470
-		echo '<a href="' . $url . '" title="' . strip_tags($label) . '" class="' . $astyle . '"></a>';
471
-	}
472
-
473
-	/**
474
-	 * builds and returns sosa relationship name in the active language
475
-	 *
476
-	 * @param string $sosa sosa number
477
-	 *
478
-	 * @return string
479
-	 */
480
-	public static function getSosaName($sosa) {
481
-		$path = '';
482
-		while ($sosa > 1) {
483
-			if ($sosa % 2 == 1) {
484
-				$sosa -= 1;
485
-				$path = 'mot' . $path;
486
-			} else {
487
-				$path = 'fat' . $path;
488
-			}
489
-			$sosa /= 2;
490
-		}
491
-
492
-		return Functions::getRelationshipNameFromPath($path, null, null);
493
-	}
494
-
495
-	/**
496
-	 * print cousins list
497
-	 *
498
-	 * @param string $famid family ID
499
-	 * @param int $show_full large or small box
500
-	 */
501
-	public static function printCousins($famid, $show_full = 1) {
502
-		global $WT_TREE;
503
-
504
-		if ($show_full) {
505
-			$bheight = Theme::theme()->parameter('chart-box-y');
506
-		} else {
507
-			$bheight = Theme::theme()->parameter('compact-chart-box-y');
508
-		}
509
-
510
-		$family    = Family::getInstance($famid, $WT_TREE);
511
-		$fchildren = $family->getChildren();
512
-
513
-		$kids = count($fchildren);
514
-
515
-		echo '<td>';
516
-		if ($kids) {
517
-			echo '<table cellspacing="0" cellpadding="0" border="0" ><tr>';
518
-			if ($kids > 1) {
519
-				echo '<td rowspan="', $kids, '"><img width="3px" height="', (($bheight + 9) * ($kids - 1)), 'px" src="', Theme::theme()->parameter('image-vline'), '"></td>';
520
-			}
521
-			$ctkids = count($fchildren);
522
-			$i      = 1;
523
-			foreach ($fchildren as $fchil) {
524
-				if ($i == 1) {
525
-					echo '<td><img width="10px" height="3px" style="vertical-align:top"';
526
-				} else {
527
-					echo '<td><img width="10px" height="3px"';
528
-				}
529
-				if (I18N::direction() === 'ltr') {
530
-					echo ' style="padding-right: 2px;"';
531
-				} else {
532
-					echo ' style="padding-left: 2px;"';
533
-				}
534
-				echo ' src="', Theme::theme()->parameter('image-hline'), '"></td><td>';
535
-				FunctionsPrint::printPedigreePerson($fchil, $show_full);
536
-				echo '</td></tr>';
537
-				if ($i < $ctkids) {
538
-					echo '<tr>';
539
-					$i++;
540
-				}
541
-			}
542
-			echo '</table>';
543
-		} else {
544
-			// If there is known that there are no children (as opposed to no known children)
545
-			if (preg_match('/\n1 NCHI (\d+)/', $family->getGedcom(), $match) && $match[1] == 0) {
546
-				echo ' <i class="icon-childless" title="', I18N::translate('This family remained childless'), '"></i>';
547
-			}
548
-		}
549
-		echo '</td>';
550
-	}
28
+    /**
29
+     * print a table cell with sosa number
30
+     *
31
+     * @param int $sosa
32
+     * @param string $pid optional pid
33
+     * @param string $arrowDirection direction of link arrow
34
+     */
35
+    public static function printSosaNumber($sosa, $pid = "", $arrowDirection = "up") {
36
+        if (substr($sosa, -1, 1) == ".") {
37
+            $personLabel = substr($sosa, 0, -1);
38
+        } else {
39
+            $personLabel = $sosa;
40
+        }
41
+        if ($arrowDirection == "blank") {
42
+            $visibility = "hidden";
43
+        } else {
44
+            $visibility = "normal";
45
+        }
46
+        echo "<td class=\"subheaders center\" style=\"vertical-align: middle; text-indent: 0px; margin-top: 0px; white-space: nowrap; visibility: ", $visibility, ";\">";
47
+        echo $personLabel;
48
+        if ($sosa != "1" && $pid != "") {
49
+            if ($arrowDirection == "left") {
50
+                $dir = 0;
51
+            } elseif ($arrowDirection == "right") {
52
+                $dir = 1;
53
+            } elseif ($arrowDirection == "down") {
54
+                $dir = 3;
55
+            } else {
56
+                $dir = 2; // either 'blank' or 'up'
57
+            }
58
+            echo '<br>';
59
+            self::printUrlArrow('#' . $pid, $pid, $dir);
60
+        }
61
+        echo '</td>';
62
+    }
63
+
64
+    /**
65
+     * print the parents table for a family
66
+     *
67
+     * @param Family $family family gedcom ID
68
+     * @param int $sosa child sosa number
69
+     * @param string $label indi label (descendancy booklet)
70
+     * @param string $parid parent ID (descendancy booklet)
71
+     * @param string $gparid gd-parent ID (descendancy booklet)
72
+     * @param int $show_full large or small box
73
+     */
74
+    public static function printFamilyParents(Family $family, $sosa = 0, $label = '', $parid = '', $gparid = '', $show_full = 1) {
75
+
76
+        if ($show_full) {
77
+            $pbheight = Theme::theme()->parameter('chart-box-y') + 14;
78
+        } else {
79
+            $pbheight = Theme::theme()->parameter('compact-chart-box-y') + 14;
80
+        }
81
+
82
+        $husb = $family->getHusband();
83
+        if ($husb) {
84
+            echo '<a name="', $husb->getXref(), '"></a>';
85
+        } else {
86
+            $husb = new Individual('M', "0 @M@ INDI\n1 SEX M", null, $family->getTree());
87
+        }
88
+        $wife = $family->getWife();
89
+        if ($wife) {
90
+            echo '<a name="', $wife->getXref(), '"></a>';
91
+        } else {
92
+            $wife = new Individual('F', "0 @F@ INDI\n1 SEX F", null, $family->getTree());
93
+        }
94
+
95
+        if ($sosa) {
96
+            echo '<p class="name_head">', $family->getFullName(), '</p>';
97
+        }
98
+
99
+        /**
100
+         * husband side
101
+         */
102
+        echo '<table cellspacing="0" cellpadding="0" border="0"><tr><td rowspan="2">';
103
+        echo '<table border="0"><tr>';
104
+
105
+        if ($parid) {
106
+            if ($husb->getXref() == $parid) {
107
+                self::printSosaNumber($label);
108
+            } else {
109
+                self::printSosaNumber($label, '', 'blank');
110
+            }
111
+        } elseif ($sosa) {
112
+            self::printSosaNumber($sosa * 2);
113
+        }
114
+        if ($husb->isPendingAddtion()) {
115
+            echo '<td class="facts_value new">';
116
+        } elseif ($husb->isPendingDeletion()) {
117
+            echo '<td class="facts_value old">';
118
+        } else {
119
+            echo '<td>';
120
+        }
121
+        FunctionsPrint::printPedigreePerson($husb, $show_full);
122
+        echo '</td></tr></table>';
123
+        echo '</td>';
124
+        // husband’s parents
125
+        $hfam = $husb->getPrimaryChildFamily();
126
+        if ($hfam) {
127
+            // remove the|| test for $sosa
128
+            echo '<td rowspan="2"><img src="' . Theme::theme()->parameter('image-hline') . '"></td><td rowspan="2"><img src="' . Theme::theme()->parameter('image-vline') . '" width="3" height="' . ($pbheight + 9) . '"></td>';
129
+            echo '<td><img class="line5" src="' . Theme::theme()->parameter('image-hline') . '"></td><td>';
130
+            // husband’s father
131
+            if ($hfam && $hfam->getHusband()) {
132
+                echo '<table border="0"><tr>';
133
+                if ($sosa > 0) {
134
+                    self::printSosaNumber($sosa * 4, $hfam->getHusband()->getXref(), 'down');
135
+                }
136
+                if (!empty($gparid) && $hfam->getHusband()->getXref() == $gparid) {
137
+                    self::printSosaNumber(trim(substr($label, 0, -3), '.') . '.');
138
+                }
139
+                echo '<td>';
140
+                FunctionsPrint::printPedigreePerson($hfam->getHusband(), $show_full);
141
+                echo '</td></tr></table>';
142
+            } elseif ($hfam && !$hfam->getHusband()) {
143
+                // Empty box for grandfather
144
+                echo '<table border="0"><tr>';
145
+                echo '<td>';
146
+                FunctionsPrint::printPedigreePerson($hfam->getHusband(), $show_full);
147
+                echo '</td></tr></table>';
148
+            }
149
+            echo '</td>';
150
+        }
151
+        if ($hfam && ($sosa != -1)) {
152
+            echo '<td rowspan="2">';
153
+            self::printUrlArrow(($sosa == 0 ? '?famid=' . $hfam->getXref() . '&amp;ged=' . $hfam->getTree()->getNameUrl() : '#' . $hfam->getXref()), $hfam->getXref(), 1);
154
+            echo '</td>';
155
+        }
156
+        if ($hfam) {
157
+            // husband’s mother
158
+            echo '</tr><tr><td><img src="' . Theme::theme()->parameter('image-hline') . '"></td><td>';
159
+            if ($hfam && $hfam->getWife()) {
160
+                echo '<table border=\'0\'><tr>';
161
+                if ($sosa > 0) {
162
+                    self::printSosaNumber($sosa * 4 + 1, $hfam->getWife()->getXref(), 'down');
163
+                }
164
+                if (!empty($gparid) && $hfam->getWife()->getXref() == $gparid) {
165
+                    self::printSosaNumber(trim(substr($label, 0, -3), '.') . '.');
166
+                }
167
+                echo '<td>';
168
+                FunctionsPrint::printPedigreePerson($hfam->getWife(), $show_full);
169
+                echo '</td></tr></table>';
170
+            } elseif ($hfam && !$hfam->getWife()) {
171
+                // Empty box for grandmother
172
+                echo '<table border="0"><tr>';
173
+                echo '<td>';
174
+                FunctionsPrint::printPedigreePerson($hfam->getWife(), $show_full);
175
+                echo '</td></tr></table>';
176
+            }
177
+            echo '</td>';
178
+        }
179
+        echo '</tr></table>';
180
+        if ($sosa && $family->canShow()) {
181
+            foreach ($family->getFacts(WT_EVENTS_MARR) as $fact) {
182
+                echo '<a href="', $family->getHtmlUrl(), '" class="details1">';
183
+                echo str_repeat('&nbsp;', 10);
184
+                echo $fact->summary();
185
+                echo '</a>';
186
+            }
187
+        } else {
188
+            echo '<br>';
189
+        }
190
+
191
+        /**
192
+         * wife side
193
+         */
194
+        echo '<table cellspacing="0" cellpadding="0" border="0"><tr><td rowspan="2">';
195
+        echo '<table><tr>';
196
+        if ($parid) {
197
+            if ($wife->getXref() == $parid) {
198
+                self::printSosaNumber($label);
199
+            } else {
200
+                self::printSosaNumber($label, '', 'blank');
201
+            }
202
+        } elseif ($sosa) {
203
+            self::printSosaNumber($sosa * 2 + 1);
204
+        }
205
+        if ($wife->isPendingAddtion()) {
206
+            echo '<td class="facts_value new">';
207
+        } elseif ($wife->isPendingDeletion()) {
208
+            echo '<td class="facts_value old">';
209
+        } else {
210
+            echo '<td>';
211
+        }
212
+        FunctionsPrint::printPedigreePerson($wife, $show_full);
213
+        echo '</td></tr></table>';
214
+        echo '</td>';
215
+        // wife’s parents
216
+        $hfam = $wife->getPrimaryChildFamily();
217
+
218
+        if ($hfam) {
219
+            echo '<td rowspan="2"><img src="' . Theme::theme()->parameter('image-hline') . '"></td><td rowspan="2"><img src="' . Theme::theme()->parameter('image-vline') . '" width="3" height="' . ($pbheight + 9) . '"></td>';
220
+            echo '<td><img class="line5" src="' . Theme::theme()->parameter('image-hline') . '"></td><td>';
221
+            // wife’s father
222
+            if ($hfam && $hfam->getHusband()) {
223
+                echo '<table><tr>';
224
+                if ($sosa > 0) {
225
+                    self::printSosaNumber($sosa * 4 + 2, $hfam->getHusband()->getXref(), 'down');
226
+                }
227
+                if (!empty($gparid) && $hfam->getHusband()->getXref() == $gparid) {
228
+                    self::printSosaNumber(trim(substr($label, 0, -3), '.') . '.');
229
+                }
230
+                echo '<td>';
231
+                FunctionsPrint::printPedigreePerson($hfam->getHusband(), $show_full);
232
+                echo '</td></tr></table>';
233
+            } elseif ($hfam && !$hfam->getHusband()) {
234
+                // Empty box for grandfather
235
+                echo '<table border="0"><tr>';
236
+                echo '<td>';
237
+                FunctionsPrint::printPedigreePerson($hfam->getHusband(), $show_full);
238
+                echo '</td></tr></table>';
239
+            }
240
+            echo '</td>';
241
+        }
242
+        if ($hfam && ($sosa != -1)) {
243
+            echo '<td rowspan="2">';
244
+            self::printUrlArrow(($sosa == 0 ? '?famid=' . $hfam->getXref() . '&amp;ged=' . $hfam->getTree()->getNameUrl() : '#' . $hfam->getXref()), $hfam->getXref(), 1);
245
+            echo '</td>';
246
+        }
247
+        if ($hfam) {
248
+            // wife’s mother
249
+            echo '</tr><tr><td><img src="' . Theme::theme()->parameter('image-hline') . '"></td><td>';
250
+            if ($hfam && $hfam->getWife()) {
251
+                echo '<table><tr>';
252
+                if ($sosa > 0) {
253
+                    self::printSosaNumber($sosa * 4 + 3, $hfam->getWife()->getXref(), 'down');
254
+                }
255
+                if (!empty($gparid) && $hfam->getWife()->getXref() == $gparid) {
256
+                    self::printSosaNumber(trim(substr($label, 0, -3), '.') . '.');
257
+                }
258
+                echo '<td>';
259
+                FunctionsPrint::printPedigreePerson($hfam->getWife(), $show_full);
260
+                echo '</td></tr></table>';
261
+            } elseif ($hfam && !$hfam->getWife()) {
262
+                // Empty box for grandmother
263
+                echo '<table border="0"><tr>';
264
+                echo '<td>';
265
+                FunctionsPrint::printPedigreePerson($hfam->getWife(), $show_full);
266
+                echo '</td></tr></table>';
267
+            }
268
+            echo '</td>';
269
+        }
270
+        echo '</tr></table>';
271
+    }
272
+
273
+    /**
274
+     * print the children table for a family
275
+     *
276
+     * @param Family $family family
277
+     * @param string $childid child ID
278
+     * @param int $sosa child sosa number
279
+     * @param string $label indi label (descendancy booklet)
280
+     * @param int $show_cousins display cousins on chart
281
+     * @param int $show_full large or small box
282
+     */
283
+    public static function printFamilyChildren(Family $family, $childid = '', $sosa = 0, $label = '', $show_cousins = 0, $show_full = 1) {
284
+
285
+        if ($show_full) {
286
+            $bheight = Theme::theme()->parameter('chart-box-y');
287
+        } else {
288
+            $bheight = Theme::theme()->parameter('compact-chart-box-y');
289
+        }
290
+
291
+        $pbheight = $bheight + 14;
292
+
293
+        $children = $family->getChildren();
294
+        $numchil  = count($children);
295
+
296
+        echo '<table border="0" cellpadding="0" cellspacing="2"><tr>';
297
+        if ($sosa > 0) {
298
+            echo '<td></td>';
299
+        }
300
+        echo '<td><span class="subheaders">';
301
+        if ($numchil == 0) {
302
+            echo I18N::translate('No children');
303
+        } else {
304
+            echo I18N::plural('%s child', '%s children', $numchil, $numchil);
305
+        }
306
+        echo '</span>';
307
+
308
+        if ($sosa == 0 && Auth::isEditor($family->getTree())) {
309
+            echo '<br>';
310
+            echo "<a href=\"#\" onclick=\"return add_child_to_family('", $family->getXref(), "', 'U');\">" . I18N::translate('Add a child to this family') . "</a>";
311
+            echo ' <a class="icon-sex_m_15x15" href="#" onclick="return add_child_to_family(\'', $family->getXref(), '\', \'M\');" title="', I18N::translate('son'), '"></a>';
312
+            echo ' <a class="icon-sex_f_15x15" href="#" onclick="return add_child_to_family(\'', $family->getXref(), '\', \'F\');" title="', I18N::translate('daughter'), '"></a>';
313
+            echo '<br><br>';
314
+        }
315
+        echo '</td>';
316
+        if ($sosa > 0) {
317
+            echo '<td></td><td></td>';
318
+        }
319
+        echo '</tr>';
320
+
321
+        $nchi = 1;
322
+        if ($children) {
323
+            foreach ($children as $child) {
324
+                echo '<tr>';
325
+                if ($sosa != 0) {
326
+                    if ($child->getXref() == $childid) {
327
+                        self::printSosaNumber($sosa, $childid);
328
+                    } elseif (empty($label)) {
329
+                        self::printSosaNumber("");
330
+                    } else {
331
+                        self::printSosaNumber($label . ($nchi++) . ".");
332
+                    }
333
+                }
334
+                if ($child->isPendingAddtion()) {
335
+                    echo '<td class="new">';
336
+                } elseif ($child->isPendingDeletion()) {
337
+                    echo '<td class="old">';
338
+                } else {
339
+                    echo '<td>';
340
+                }
341
+                FunctionsPrint::printPedigreePerson($child, $show_full);
342
+                echo '</td>';
343
+                if ($sosa != 0) {
344
+                    // loop for all families where current child is a spouse
345
+                    $famids = $child->getSpouseFamilies();
346
+
347
+                    $maxfam = count($famids) - 1;
348
+                    for ($f = 0; $f <= $maxfam; $f++) {
349
+                        $famid_child = $famids[$f]->getXref();
350
+                        // multiple marriages
351
+                        if ($f > 0) {
352
+                            echo '</tr><tr><td></td>';
353
+                            echo '<td style="text-align:end; vertical-align: top;">';
354
+
355
+                            //find out how many cousins there are to establish vertical line on second families
356
+                            $fchildren = $famids[$f]->getChildren();
357
+                            $kids      = count($fchildren);
358
+                            $Pheader   = ($bheight - 1) * $kids;
359
+                            $PBadj     = 6; // default
360
+                            if ($show_cousins > 0) {
361
+                                if ($kids) {
362
+                                    $PBadj = max(0, $Pheader / 2 + $kids * 4.5);
363
+                                }
364
+                            }
365
+
366
+                            if ($f == $maxfam) {
367
+                                echo '<img height="' . ((($bheight / 2)) + $PBadj) . 'px"';
368
+                            } else {
369
+                                echo '<img height="' . $pbheight . 'px"';
370
+                            }
371
+                            echo ' width="3" src="' . Theme::theme()->parameter('image-vline') . '">';
372
+                            echo '</td>';
373
+                        }
374
+                        echo '<td class="details1" style="text-align:center;">';
375
+                        $spouse = $famids[$f]->getSpouse($child);
376
+
377
+                        $marr = $famids[$f]->getFirstFact('MARR');
378
+                        $div  = $famids[$f]->getFirstFact('DIV');
379
+                        if ($marr) {
380
+                            // marriage date
381
+                            echo $marr->getDate()->minimumDate()->format('%Y');
382
+                            // divorce date
383
+                            if ($div) {
384
+                                echo '–', $div->getDate()->minimumDate()->format('%Y');
385
+                            }
386
+                        }
387
+                        echo "<br><img width=\"100%\" class=\"line5\" height=\"3\" src=\"" . Theme::theme()->parameter('image-hline') . "\" alt=\"\">";
388
+                        echo "</td>";
389
+                        // spouse information
390
+                        echo "<td style=\"vertical-align: center;";
391
+                        if (!empty($divrec)) {
392
+                            echo " filter:alpha(opacity=40);opacity:0.4;\">";
393
+                        } else {
394
+                            echo "\">";
395
+                        }
396
+                        FunctionsPrint::printPedigreePerson($spouse, $show_full);
397
+                        echo "</td>";
398
+                        // cousins
399
+                        if ($show_cousins) {
400
+                            self::printCousins($famid_child, $show_full);
401
+                        }
402
+                    }
403
+                }
404
+                echo "</tr>";
405
+            }
406
+        } elseif ($sosa < 1) {
407
+            // message 'no children' except for sosa
408
+            if (preg_match('/\n1 NCHI (\d+)/', $family->getGedcom(), $match) && $match[1] == 0) {
409
+                echo '<tr><td><i class="icon-childless"></i> ' . I18N::translate('This family remained childless') . '</td></tr>';
410
+            }
411
+        }
412
+        echo "</table><br>";
413
+    }
414
+
415
+    /**
416
+     * print a family with Sosa-Stradonitz numbering system
417
+     * ($rootid=1, father=2, mother=3 ...)
418
+     *
419
+     * @param string $famid family gedcom ID
420
+     * @param string $childid tree root ID
421
+     * @param int $sosa starting sosa number
422
+     * @param string $label indi label (descendancy booklet)
423
+     * @param string $parid parent ID (descendancy booklet)
424
+     * @param string $gparid gd-parent ID (descendancy booklet)
425
+     * @param int $show_cousins display cousins on chart
426
+     * @param int $show_full large or small box
427
+     */
428
+    public static function printSosaFamily($famid, $childid, $sosa, $label = '', $parid = '', $gparid = '', $show_cousins = 0, $show_full = 1) {
429
+        global $WT_TREE;
430
+
431
+        echo '<hr>';
432
+        echo '<p style="page-break-before: always;">';
433
+        if (!empty($famid)) {
434
+            echo '<a name="', $famid, '"></a>';
435
+        }
436
+        self::printFamilyParents(Family::getInstance($famid, $WT_TREE), $sosa, $label, $parid, $gparid, $show_full);
437
+        echo '<br>';
438
+        echo '<table><tr><td>';
439
+        self::printFamilyChildren(Family::getInstance($famid, $WT_TREE), $childid, $sosa, $label, $show_cousins, $show_full);
440
+        echo '</td></tr></table>';
441
+        echo '<br>';
442
+    }
443
+
444
+    /**
445
+     * print an arrow to a new url
446
+     *
447
+     * @param string $url target url
448
+     * @param string $label arrow label
449
+     * @param int $dir arrow direction 0=left 1=right 2=up 3=down (default=2)
450
+     */
451
+    public static function printUrlArrow($url, $label, $dir = 2) {
452
+        if ($url === '') {
453
+            return;
454
+        }
455
+
456
+        // arrow direction
457
+        $adir = $dir;
458
+        if (I18N::direction() === 'rtl' && $dir === 0) {
459
+            $adir = 1;
460
+        }
461
+        if (I18N::direction() === 'rtl' && $dir === 1) {
462
+            $adir = 0;
463
+        }
464
+
465
+        // arrow style     0         1         2         3
466
+        $array_style = array('icon-larrow', 'icon-rarrow', 'icon-uarrow', 'icon-darrow');
467
+        $astyle      = $array_style[$adir];
468
+
469
+        // Labels include people’s names, which may contain markup
470
+        echo '<a href="' . $url . '" title="' . strip_tags($label) . '" class="' . $astyle . '"></a>';
471
+    }
472
+
473
+    /**
474
+     * builds and returns sosa relationship name in the active language
475
+     *
476
+     * @param string $sosa sosa number
477
+     *
478
+     * @return string
479
+     */
480
+    public static function getSosaName($sosa) {
481
+        $path = '';
482
+        while ($sosa > 1) {
483
+            if ($sosa % 2 == 1) {
484
+                $sosa -= 1;
485
+                $path = 'mot' . $path;
486
+            } else {
487
+                $path = 'fat' . $path;
488
+            }
489
+            $sosa /= 2;
490
+        }
491
+
492
+        return Functions::getRelationshipNameFromPath($path, null, null);
493
+    }
494
+
495
+    /**
496
+     * print cousins list
497
+     *
498
+     * @param string $famid family ID
499
+     * @param int $show_full large or small box
500
+     */
501
+    public static function printCousins($famid, $show_full = 1) {
502
+        global $WT_TREE;
503
+
504
+        if ($show_full) {
505
+            $bheight = Theme::theme()->parameter('chart-box-y');
506
+        } else {
507
+            $bheight = Theme::theme()->parameter('compact-chart-box-y');
508
+        }
509
+
510
+        $family    = Family::getInstance($famid, $WT_TREE);
511
+        $fchildren = $family->getChildren();
512
+
513
+        $kids = count($fchildren);
514
+
515
+        echo '<td>';
516
+        if ($kids) {
517
+            echo '<table cellspacing="0" cellpadding="0" border="0" ><tr>';
518
+            if ($kids > 1) {
519
+                echo '<td rowspan="', $kids, '"><img width="3px" height="', (($bheight + 9) * ($kids - 1)), 'px" src="', Theme::theme()->parameter('image-vline'), '"></td>';
520
+            }
521
+            $ctkids = count($fchildren);
522
+            $i      = 1;
523
+            foreach ($fchildren as $fchil) {
524
+                if ($i == 1) {
525
+                    echo '<td><img width="10px" height="3px" style="vertical-align:top"';
526
+                } else {
527
+                    echo '<td><img width="10px" height="3px"';
528
+                }
529
+                if (I18N::direction() === 'ltr') {
530
+                    echo ' style="padding-right: 2px;"';
531
+                } else {
532
+                    echo ' style="padding-left: 2px;"';
533
+                }
534
+                echo ' src="', Theme::theme()->parameter('image-hline'), '"></td><td>';
535
+                FunctionsPrint::printPedigreePerson($fchil, $show_full);
536
+                echo '</td></tr>';
537
+                if ($i < $ctkids) {
538
+                    echo '<tr>';
539
+                    $i++;
540
+                }
541
+            }
542
+            echo '</table>';
543
+        } else {
544
+            // If there is known that there are no children (as opposed to no known children)
545
+            if (preg_match('/\n1 NCHI (\d+)/', $family->getGedcom(), $match) && $match[1] == 0) {
546
+                echo ' <i class="icon-childless" title="', I18N::translate('This family remained childless'), '"></i>';
547
+            }
548
+        }
549
+        echo '</td>';
550
+    }
551 551
 }
Please login to merge, or discard this patch.
Braces   +16 added lines, -8 removed lines patch added patch discarded remove patch
@@ -24,7 +24,8 @@  discard block
 block discarded – undo
24 24
 /**
25 25
  * Class FunctionsCharts - common functions
26 26
  */
27
-class FunctionsCharts {
27
+class FunctionsCharts
28
+{
28 29
 	/**
29 30
 	 * print a table cell with sosa number
30 31
 	 *
@@ -32,7 +33,8 @@  discard block
 block discarded – undo
32 33
 	 * @param string $pid optional pid
33 34
 	 * @param string $arrowDirection direction of link arrow
34 35
 	 */
35
-	public static function printSosaNumber($sosa, $pid = "", $arrowDirection = "up") {
36
+	public static function printSosaNumber($sosa, $pid = "", $arrowDirection = "up")
37
+	{
36 38
 		if (substr($sosa, -1, 1) == ".") {
37 39
 			$personLabel = substr($sosa, 0, -1);
38 40
 		} else {
@@ -71,7 +73,8 @@  discard block
 block discarded – undo
71 73
 	 * @param string $gparid gd-parent ID (descendancy booklet)
72 74
 	 * @param int $show_full large or small box
73 75
 	 */
74
-	public static function printFamilyParents(Family $family, $sosa = 0, $label = '', $parid = '', $gparid = '', $show_full = 1) {
76
+	public static function printFamilyParents(Family $family, $sosa = 0, $label = '', $parid = '', $gparid = '', $show_full = 1)
77
+	{
75 78
 
76 79
 		if ($show_full) {
77 80
 			$pbheight = Theme::theme()->parameter('chart-box-y') + 14;
@@ -280,7 +283,8 @@  discard block
 block discarded – undo
280 283
 	 * @param int $show_cousins display cousins on chart
281 284
 	 * @param int $show_full large or small box
282 285
 	 */
283
-	public static function printFamilyChildren(Family $family, $childid = '', $sosa = 0, $label = '', $show_cousins = 0, $show_full = 1) {
286
+	public static function printFamilyChildren(Family $family, $childid = '', $sosa = 0, $label = '', $show_cousins = 0, $show_full = 1)
287
+	{
284 288
 
285 289
 		if ($show_full) {
286 290
 			$bheight = Theme::theme()->parameter('chart-box-y');
@@ -425,7 +429,8 @@  discard block
 block discarded – undo
425 429
 	 * @param int $show_cousins display cousins on chart
426 430
 	 * @param int $show_full large or small box
427 431
 	 */
428
-	public static function printSosaFamily($famid, $childid, $sosa, $label = '', $parid = '', $gparid = '', $show_cousins = 0, $show_full = 1) {
432
+	public static function printSosaFamily($famid, $childid, $sosa, $label = '', $parid = '', $gparid = '', $show_cousins = 0, $show_full = 1)
433
+	{
429 434
 		global $WT_TREE;
430 435
 
431 436
 		echo '<hr>';
@@ -448,7 +453,8 @@  discard block
 block discarded – undo
448 453
 	 * @param string $label arrow label
449 454
 	 * @param int $dir arrow direction 0=left 1=right 2=up 3=down (default=2)
450 455
 	 */
451
-	public static function printUrlArrow($url, $label, $dir = 2) {
456
+	public static function printUrlArrow($url, $label, $dir = 2)
457
+	{
452 458
 		if ($url === '') {
453 459
 			return;
454 460
 		}
@@ -477,7 +483,8 @@  discard block
 block discarded – undo
477 483
 	 *
478 484
 	 * @return string
479 485
 	 */
480
-	public static function getSosaName($sosa) {
486
+	public static function getSosaName($sosa)
487
+	{
481 488
 		$path = '';
482 489
 		while ($sosa > 1) {
483 490
 			if ($sosa % 2 == 1) {
@@ -498,7 +505,8 @@  discard block
 block discarded – undo
498 505
 	 * @param string $famid family ID
499 506
 	 * @param int $show_full large or small box
500 507
 	 */
501
-	public static function printCousins($famid, $show_full = 1) {
508
+	public static function printCousins($famid, $show_full = 1)
509
+	{
502 510
 		global $WT_TREE;
503 511
 
504 512
 		if ($show_full) {
Please login to merge, or discard this patch.
app/Tree.php 3 patches
Indentation   +768 added lines, -768 removed lines patch added patch discarded remove patch
@@ -23,772 +23,772 @@
 block discarded – undo
23 23
  * Provide an interface to the wt_gedcom table.
24 24
  */
25 25
 class Tree {
26
-	/** @var int The tree's ID number */
27
-	private $tree_id;
28
-
29
-	/** @var string The tree's name */
30
-	private $name;
31
-
32
-	/** @var string The tree's title */
33
-	private $title;
34
-
35
-	/** @var int[] Default access rules for facts in this tree */
36
-	private $fact_privacy;
37
-
38
-	/** @var int[] Default access rules for individuals in this tree */
39
-	private $individual_privacy;
40
-
41
-	/** @var integer[][] Default access rules for individual facts in this tree */
42
-	private $individual_fact_privacy;
43
-
44
-	/** @var Tree[] All trees that we have permission to see. */
45
-	private static $trees;
46
-
47
-	/** @var string[] Cached copy of the wt_gedcom_setting table. */
48
-	private $preferences;
49
-
50
-	/** @var string[][] Cached copy of the wt_user_gedcom_setting table. */
51
-	private $user_preferences = array();
52
-
53
-	/**
54
-	 * Create a tree object. This is a private constructor - it can only
55
-	 * be called from Tree::getAll() to ensure proper initialisation.
56
-	 *
57
-	 * @param int    $tree_id
58
-	 * @param string $tree_name
59
-	 * @param string $tree_title
60
-	 */
61
-	private function __construct($tree_id, $tree_name, $tree_title) {
62
-		$this->tree_id                 = $tree_id;
63
-		$this->name                    = $tree_name;
64
-		$this->title                   = $tree_title;
65
-		$this->fact_privacy            = array();
66
-		$this->individual_privacy      = array();
67
-		$this->individual_fact_privacy = array();
68
-
69
-		// Load the privacy settings for this tree
70
-		$rows = Database::prepare(
71
-			"SELECT xref, tag_type, CASE resn WHEN 'none' THEN :priv_public WHEN 'privacy' THEN :priv_user WHEN 'confidential' THEN :priv_none WHEN 'hidden' THEN :priv_hide END AS resn" .
72
-			" FROM `##default_resn` WHERE gedcom_id = :tree_id"
73
-		)->execute(array(
74
-			'priv_public' => Auth::PRIV_PRIVATE,
75
-			'priv_user'   => Auth::PRIV_USER,
76
-			'priv_none'   => Auth::PRIV_NONE,
77
-			'priv_hide'   => Auth::PRIV_HIDE,
78
-			'tree_id'     => $this->tree_id,
79
-		))->fetchAll();
80
-
81
-		foreach ($rows as $row) {
82
-			if ($row->xref !== null) {
83
-				if ($row->tag_type !== null) {
84
-					$this->individual_fact_privacy[$row->xref][$row->tag_type] = (int) $row->resn;
85
-				} else {
86
-					$this->individual_privacy[$row->xref] = (int) $row->resn;
87
-				}
88
-			} else {
89
-				$this->fact_privacy[$row->tag_type] = (int) $row->resn;
90
-			}
91
-		}
92
-
93
-	}
94
-
95
-	/**
96
-	 * The ID of this tree
97
-	 *
98
-	 * @return int
99
-	 */
100
-	public function getTreeId() {
101
-		return $this->tree_id;
102
-	}
103
-
104
-	/**
105
-	 * The name of this tree
106
-	 *
107
-	 * @return string
108
-	 */
109
-	public function getName() {
110
-		return $this->name;
111
-	}
112
-
113
-	/**
114
-	 * The name of this tree
115
-	 *
116
-	 * @return string
117
-	 */
118
-	public function getNameHtml() {
119
-		return Filter::escapeHtml($this->name);
120
-	}
121
-
122
-	/**
123
-	 * The name of this tree
124
-	 *
125
-	 * @return string
126
-	 */
127
-	public function getNameUrl() {
128
-		return Filter::escapeUrl($this->name);
129
-	}
130
-
131
-	/**
132
-	 * The title of this tree
133
-	 *
134
-	 * @return string
135
-	 */
136
-	public function getTitle() {
137
-		return $this->title;
138
-	}
139
-
140
-	/**
141
-	 * The title of this tree, with HTML markup
142
-	 *
143
-	 * @return string
144
-	 */
145
-	public function getTitleHtml() {
146
-		return '<span dir="auto">' . Filter::escapeHtml($this->title) . '</span>';
147
-	}
148
-
149
-	/**
150
-	 * The fact-level privacy for this tree.
151
-	 *
152
-	 * @return int[]
153
-	 */
154
-	public function getFactPrivacy() {
155
-		return $this->fact_privacy;
156
-	}
157
-
158
-	/**
159
-	 * The individual-level privacy for this tree.
160
-	 *
161
-	 * @return int[]
162
-	 */
163
-	public function getIndividualPrivacy() {
164
-		return $this->individual_privacy;
165
-	}
166
-
167
-	/**
168
-	 * The individual-fact-level privacy for this tree.
169
-	 *
170
-	 * @return integer[][]
171
-	 */
172
-	public function getIndividualFactPrivacy() {
173
-		return $this->individual_fact_privacy;
174
-	}
175
-
176
-	/**
177
-	 * Get the tree’s configuration settings.
178
-	 *
179
-	 * @param string      $setting_name
180
-	 * @param string|null $default
181
-	 *
182
-	 * @return string|null
183
-	 */
184
-	public function getPreference($setting_name, $default = null) {
185
-		if ($this->preferences === null) {
186
-			$this->preferences = Database::prepare(
187
-				"SELECT setting_name, setting_value FROM `##gedcom_setting` WHERE gedcom_id = ?"
188
-			)->execute(array($this->tree_id))->fetchAssoc();
189
-		}
190
-
191
-		if (array_key_exists($setting_name, $this->preferences)) {
192
-			return $this->preferences[$setting_name];
193
-		} else {
194
-			return $default;
195
-		}
196
-	}
197
-
198
-	/**
199
-	 * Set the tree’s configuration settings.
200
-	 *
201
-	 * @param string $setting_name
202
-	 * @param string $setting_value
203
-	 *
204
-	 * @return $this
205
-	 */
206
-	public function setPreference($setting_name, $setting_value) {
207
-		if ($setting_value !== $this->getPreference($setting_name)) {
208
-			// Update the database
209
-			if ($setting_value === null) {
210
-				Database::prepare(
211
-					"DELETE FROM `##gedcom_setting` WHERE gedcom_id = :tree_id AND setting_name = :setting_name"
212
-				)->execute(array(
213
-					'tree_id'      => $this->tree_id,
214
-					'setting_name' => $setting_name,
215
-				));
216
-			} else {
217
-				Database::prepare(
218
-					"REPLACE INTO `##gedcom_setting` (gedcom_id, setting_name, setting_value)" .
219
-					" VALUES (:tree_id, :setting_name, LEFT(:setting_value, 255))"
220
-				)->execute(array(
221
-					'tree_id'       => $this->tree_id,
222
-					'setting_name'  => $setting_name,
223
-					'setting_value' => $setting_value,
224
-				));
225
-			}
226
-			// Update our cache
227
-			$this->preferences[$setting_name] = $setting_value;
228
-			// Audit log of changes
229
-			Log::addConfigurationLog('Tree preference "' . $setting_name . '" set to "' . $setting_value . '"', $this);
230
-		}
231
-
232
-		return $this;
233
-	}
234
-
235
-	/**
236
-	 * Get the tree’s user-configuration settings.
237
-	 *
238
-	 * @param User        $user
239
-	 * @param string      $setting_name
240
-	 * @param string|null $default
241
-	 *
242
-	 * @return string
243
-	 */
244
-	public function getUserPreference(User $user, $setting_name, $default = null) {
245
-		// There are lots of settings, and we need to fetch lots of them on every page
246
-		// so it is quicker to fetch them all in one go.
247
-		if (!array_key_exists($user->getUserId(), $this->user_preferences)) {
248
-			$this->user_preferences[$user->getUserId()] = Database::prepare(
249
-				"SELECT setting_name, setting_value FROM `##user_gedcom_setting` WHERE user_id = ? AND gedcom_id = ?"
250
-			)->execute(array($user->getUserId(), $this->tree_id))->fetchAssoc();
251
-		}
252
-
253
-		if (array_key_exists($setting_name, $this->user_preferences[$user->getUserId()])) {
254
-			return $this->user_preferences[$user->getUserId()][$setting_name];
255
-		} else {
256
-			return $default;
257
-		}
258
-	}
259
-
260
-	/**
261
-	 * Set the tree’s user-configuration settings.
262
-	 *
263
-	 * @param User    $user
264
-	 * @param string  $setting_name
265
-	 * @param string  $setting_value
266
-	 *
267
-	 * @return $this
268
-	 */
269
-	public function setUserPreference(User $user, $setting_name, $setting_value) {
270
-		if ($this->getUserPreference($user, $setting_name) !== $setting_value) {
271
-			// Update the database
272
-			if ($setting_value === null) {
273
-				Database::prepare(
274
-					"DELETE FROM `##user_gedcom_setting` WHERE gedcom_id = :tree_id AND user_id = :user_id AND setting_name = :setting_name"
275
-				)->execute(array(
276
-					'tree_id'      => $this->tree_id,
277
-					'user_id'      => $user->getUserId(),
278
-					'setting_name' => $setting_name,
279
-				));
280
-			} else {
281
-				Database::prepare(
282
-					"REPLACE INTO `##user_gedcom_setting` (user_id, gedcom_id, setting_name, setting_value) VALUES (:user_id, :tree_id, :setting_name, LEFT(:setting_value, 255))"
283
-				)->execute(array(
284
-					'user_id'       => $user->getUserId(),
285
-					'tree_id'       => $this->tree_id,
286
-					'setting_name'  => $setting_name,
287
-					'setting_value' => $setting_value,
288
-				));
289
-			}
290
-			// Update our cache
291
-			$this->user_preferences[$user->getUserId()][$setting_name] = $setting_value;
292
-			// Audit log of changes
293
-			Log::addConfigurationLog('Tree preference "' . $setting_name . '" set to "' . $setting_value . '" for user "' . $user->getUserName() . '"', $this);
294
-		}
295
-
296
-		return $this;
297
-	}
298
-
299
-	/**
300
-	 * Can a user accept changes for this tree?
301
-	 *
302
-	 * @param User $user
303
-	 *
304
-	 * @return bool
305
-	 */
306
-	public function canAcceptChanges(User $user) {
307
-		return Auth::isModerator($this, $user);
308
-	}
309
-
310
-	/**
311
-	 * Fetch all the trees that we have permission to access.
312
-	 *
313
-	 * @return Tree[]
314
-	 */
315
-	public static function getAll() {
316
-		if (self::$trees === null) {
317
-			self::$trees = array();
318
-			$rows        = Database::prepare(
319
-				"SELECT g.gedcom_id AS tree_id, g.gedcom_name AS tree_name, gs1.setting_value AS tree_title" .
320
-				" FROM `##gedcom` g" .
321
-				" LEFT JOIN `##gedcom_setting`      gs1 ON (g.gedcom_id=gs1.gedcom_id AND gs1.setting_name='title')" .
322
-				" LEFT JOIN `##gedcom_setting`      gs2 ON (g.gedcom_id=gs2.gedcom_id AND gs2.setting_name='imported')" .
323
-				" LEFT JOIN `##gedcom_setting`      gs3 ON (g.gedcom_id=gs3.gedcom_id AND gs3.setting_name='REQUIRE_AUTHENTICATION')" .
324
-				" LEFT JOIN `##user_gedcom_setting` ugs ON (g.gedcom_id=ugs.gedcom_id AND ugs.setting_name='canedit' AND ugs.user_id=?)" .
325
-				" WHERE " .
326
-				"  g.gedcom_id>0 AND (" . // exclude the "template" tree
327
-				"    EXISTS (SELECT 1 FROM `##user_setting` WHERE user_id=? AND setting_name='canadmin' AND setting_value=1)" . // Admin sees all
328
-				"   ) OR (" .
329
-				"    (gs2.setting_value = 1 OR ugs.setting_value = 'admin') AND (" . // Allow imported trees, with either:
330
-				"     gs3.setting_value <> 1 OR" . // visitor access
331
-				"     IFNULL(ugs.setting_value, 'none')<>'none'" . // explicit access
332
-				"   )" .
333
-				"  )" .
334
-				" ORDER BY g.sort_order, 3"
335
-			)->execute(array(Auth::id(), Auth::id()))->fetchAll();
336
-			foreach ($rows as $row) {
337
-				self::$trees[] = new self((int) $row->tree_id, $row->tree_name, $row->tree_title);
338
-			}
339
-		}
340
-
341
-		return self::$trees;
342
-	}
343
-
344
-	/**
345
-	 * Find the tree with a specific ID.
346
-	 *
347
-	 * @param int $tree_id
348
-	 *
349
-	 * @throws \DomainException
350
-	 *
351
-	 * @return Tree
352
-	 */
353
-	public static function findById($tree_id) {
354
-		foreach (self::getAll() as $tree) {
355
-			if ($tree->tree_id == $tree_id) {
356
-				return $tree;
357
-			}
358
-		}
359
-		throw new \DomainException;
360
-	}
361
-
362
-	/**
363
-	 * Find the tree with a specific name.
364
-	 *
365
-	 * @param string $tree_name
366
-	 *
367
-	 * @return Tree|null
368
-	 */
369
-	public static function findByName($tree_name) {
370
-		foreach (self::getAll() as $tree) {
371
-			if ($tree->name === $tree_name) {
372
-				return $tree;
373
-			}
374
-		}
375
-
376
-		return null;
377
-	}
378
-
379
-	/**
380
-	 * Create arguments to select_edit_control()
381
-	 * Note - these will be escaped later
382
-	 *
383
-	 * @return string[]
384
-	 */
385
-	public static function getIdList() {
386
-		$list = array();
387
-		foreach (self::getAll() as $tree) {
388
-			$list[$tree->tree_id] = $tree->title;
389
-		}
390
-
391
-		return $list;
392
-	}
393
-
394
-	/**
395
-	 * Create arguments to select_edit_control()
396
-	 * Note - these will be escaped later
397
-	 *
398
-	 * @return string[]
399
-	 */
400
-	public static function getNameList() {
401
-		$list = array();
402
-		foreach (self::getAll() as $tree) {
403
-			$list[$tree->name] = $tree->title;
404
-		}
405
-
406
-		return $list;
407
-	}
408
-
409
-	/**
410
-	 * Create a new tree
411
-	 *
412
-	 * @param string $tree_name
413
-	 * @param string $tree_title
414
-	 *
415
-	 * @return Tree
416
-	 */
417
-	public static function create($tree_name, $tree_title) {
418
-		try {
419
-			// Create a new tree
420
-			Database::prepare(
421
-				"INSERT INTO `##gedcom` (gedcom_name) VALUES (?)"
422
-			)->execute(array($tree_name));
423
-			$tree_id = Database::prepare("SELECT LAST_INSERT_ID()")->fetchOne();
424
-		} catch (PDOException $ex) {
425
-			// A tree with that name already exists?
426
-			return self::findByName($tree_name);
427
-		}
428
-
429
-		// Update the list of trees - to include this new one
430
-		self::$trees = null;
431
-		$tree        = self::findById($tree_id);
432
-
433
-		$tree->setPreference('imported', '0');
434
-		$tree->setPreference('title', $tree_title);
435
-
436
-		// Module privacy
437
-		Module::setDefaultAccess($tree_id);
438
-
439
-		// Set preferences from default tree
440
-		Database::prepare(
441
-			"INSERT INTO `##gedcom_setting` (gedcom_id, setting_name, setting_value)" .
442
-			" SELECT :tree_id, setting_name, setting_value" .
443
-			" FROM `##gedcom_setting` WHERE gedcom_id = -1"
444
-		)->execute(array(
445
-			'tree_id' => $tree_id,
446
-		));
447
-
448
-		Database::prepare(
449
-			"INSERT INTO `##default_resn` (gedcom_id, tag_type, resn)" .
450
-			" SELECT :tree_id, tag_type, resn" .
451
-			" FROM `##default_resn` WHERE gedcom_id = -1"
452
-		)->execute(array(
453
-			'tree_id' => $tree_id,
454
-		));
455
-
456
-		Database::prepare(
457
-			"INSERT INTO `##block` (gedcom_id, location, block_order, module_name)" .
458
-			" SELECT :tree_id, location, block_order, module_name" .
459
-			" FROM `##block` WHERE gedcom_id = -1"
460
-		)->execute(array(
461
-			'tree_id' => $tree_id,
462
-		));
463
-
464
-		// Gedcom and privacy settings
465
-		$tree->setPreference('CONTACT_USER_ID', Auth::id());
466
-		$tree->setPreference('WEBMASTER_USER_ID', Auth::id());
467
-		$tree->setPreference('LANGUAGE', WT_LOCALE); // Default to the current admin’s language
468
-		switch (WT_LOCALE) {
469
-		case 'es':
470
-			$tree->setPreference('SURNAME_TRADITION', 'spanish');
471
-			break;
472
-		case 'is':
473
-			$tree->setPreference('SURNAME_TRADITION', 'icelandic');
474
-			break;
475
-		case 'lt':
476
-			$tree->setPreference('SURNAME_TRADITION', 'lithuanian');
477
-			break;
478
-		case 'pl':
479
-			$tree->setPreference('SURNAME_TRADITION', 'polish');
480
-			break;
481
-		case 'pt':
482
-		case 'pt-BR':
483
-			$tree->setPreference('SURNAME_TRADITION', 'portuguese');
484
-			break;
485
-		default:
486
-			$tree->setPreference('SURNAME_TRADITION', 'paternal');
487
-			break;
488
-		}
489
-
490
-		// Genealogy data
491
-		// It is simpler to create a temporary/unimported GEDCOM than to populate all the tables...
492
-		$john_doe = /* I18N: This should be a common/default/placeholder name of an individual. Put slashes around the surname. */
493
-			I18N::translate('John /DOE/');
494
-		$note     = I18N::translate('Edit this individual and replace their details with your own.');
495
-		Database::prepare("INSERT INTO `##gedcom_chunk` (gedcom_id, chunk_data) VALUES (?, ?)")->execute(array(
496
-			$tree_id,
497
-			"0 HEAD\n1 CHAR UTF-8\n0 @I1@ INDI\n1 NAME {$john_doe}\n1 SEX M\n1 BIRT\n2 DATE 01 JAN 1850\n2 NOTE {$note}\n0 TRLR\n",
498
-		));
499
-
500
-		// Update our cache
501
-		self::$trees[$tree->tree_id] = $tree;
502
-
503
-		return $tree;
504
-	}
505
-
506
-	/**
507
-	 * Are there any pending edits for this tree, than need reviewing by a moderator.
508
-	 *
509
-	 * @return bool
510
-	 */
511
-	public function hasPendingEdit() {
512
-		return (bool) Database::prepare(
513
-			"SELECT 1 FROM `##change` WHERE status = 'pending' AND gedcom_id = :tree_id"
514
-		)->execute(array(
515
-			'tree_id' => $this->tree_id,
516
-		))->fetchOne();
517
-	}
518
-
519
-	/**
520
-	 * Delete all the genealogy data from a tree - in preparation for importing
521
-	 * new data. Optionally retain the media data, for when the user has been
522
-	 * editing their data offline using an application which deletes (or does not
523
-	 * support) media data.
524
-	 *
525
-	 * @param bool $keep_media
526
-	 */
527
-	public function deleteGenealogyData($keep_media) {
528
-		Database::prepare("DELETE FROM `##gedcom_chunk` WHERE gedcom_id = ?")->execute(array($this->tree_id));
529
-		Database::prepare("DELETE FROM `##individuals`  WHERE i_file    = ?")->execute(array($this->tree_id));
530
-		Database::prepare("DELETE FROM `##families`     WHERE f_file    = ?")->execute(array($this->tree_id));
531
-		Database::prepare("DELETE FROM `##sources`      WHERE s_file    = ?")->execute(array($this->tree_id));
532
-		Database::prepare("DELETE FROM `##other`        WHERE o_file    = ?")->execute(array($this->tree_id));
533
-		Database::prepare("DELETE FROM `##places`       WHERE p_file    = ?")->execute(array($this->tree_id));
534
-		Database::prepare("DELETE FROM `##placelinks`   WHERE pl_file   = ?")->execute(array($this->tree_id));
535
-		Database::prepare("DELETE FROM `##name`         WHERE n_file    = ?")->execute(array($this->tree_id));
536
-		Database::prepare("DELETE FROM `##dates`        WHERE d_file    = ?")->execute(array($this->tree_id));
537
-		Database::prepare("DELETE FROM `##change`       WHERE gedcom_id = ?")->execute(array($this->tree_id));
538
-
539
-		if ($keep_media) {
540
-			Database::prepare("DELETE FROM `##link` WHERE l_file =? AND l_type<>'OBJE'")->execute(array($this->tree_id));
541
-		} else {
542
-			Database::prepare("DELETE FROM `##link`  WHERE l_file =?")->execute(array($this->tree_id));
543
-			Database::prepare("DELETE FROM `##media` WHERE m_file =?")->execute(array($this->tree_id));
544
-		}
545
-	}
546
-
547
-	/**
548
-	 * Delete everything relating to a tree
549
-	 */
550
-	public function delete() {
551
-		// If this is the default tree, then unset it
552
-		if (Site::getPreference('DEFAULT_GEDCOM') === $this->name) {
553
-			Site::setPreference('DEFAULT_GEDCOM', '');
554
-		}
555
-
556
-		$this->deleteGenealogyData(false);
557
-
558
-		Database::prepare("DELETE `##block_setting` FROM `##block_setting` JOIN `##block` USING (block_id) WHERE gedcom_id=?")->execute(array($this->tree_id));
559
-		Database::prepare("DELETE FROM `##block`               WHERE gedcom_id = ?")->execute(array($this->tree_id));
560
-		Database::prepare("DELETE FROM `##user_gedcom_setting` WHERE gedcom_id = ?")->execute(array($this->tree_id));
561
-		Database::prepare("DELETE FROM `##gedcom_setting`      WHERE gedcom_id = ?")->execute(array($this->tree_id));
562
-		Database::prepare("DELETE FROM `##module_privacy`      WHERE gedcom_id = ?")->execute(array($this->tree_id));
563
-		Database::prepare("DELETE FROM `##next_id`             WHERE gedcom_id = ?")->execute(array($this->tree_id));
564
-		Database::prepare("DELETE FROM `##hit_counter`         WHERE gedcom_id = ?")->execute(array($this->tree_id));
565
-		Database::prepare("DELETE FROM `##default_resn`        WHERE gedcom_id = ?")->execute(array($this->tree_id));
566
-		Database::prepare("DELETE FROM `##gedcom_chunk`        WHERE gedcom_id = ?")->execute(array($this->tree_id));
567
-		Database::prepare("DELETE FROM `##log`                 WHERE gedcom_id = ?")->execute(array($this->tree_id));
568
-		Database::prepare("DELETE FROM `##gedcom`              WHERE gedcom_id = ?")->execute(array($this->tree_id));
569
-
570
-		// After updating the database, we need to fetch a new (sorted) copy
571
-		self::$trees = null;
572
-	}
573
-
574
-	/**
575
-	 * Export the tree to a GEDCOM file
576
-	 *
577
-	 * @param resource $stream
578
-	 */
579
-	public function exportGedcom($stream) {
580
-		$stmt = Database::prepare(
581
-			"SELECT i_gedcom AS gedcom, i_id AS xref, 1 AS n FROM `##individuals` WHERE i_file = :tree_id_1" .
582
-			" UNION ALL " .
583
-			"SELECT f_gedcom AS gedcom, f_id AS xref, 2 AS n FROM `##families`    WHERE f_file = :tree_id_2" .
584
-			" UNION ALL " .
585
-			"SELECT s_gedcom AS gedcom, s_id AS xref, 3 AS n FROM `##sources`     WHERE s_file = :tree_id_3" .
586
-			" UNION ALL " .
587
-			"SELECT o_gedcom AS gedcom, o_id AS xref, 4 AS n FROM `##other`       WHERE o_file = :tree_id_4 AND o_type NOT IN ('HEAD', 'TRLR')" .
588
-			" UNION ALL " .
589
-			"SELECT m_gedcom AS gedcom, m_id AS xref, 5 AS n FROM `##media`       WHERE m_file = :tree_id_5" .
590
-			" ORDER BY n, LENGTH(xref), xref"
591
-		)->execute(array(
592
-			'tree_id_1' => $this->tree_id,
593
-			'tree_id_2' => $this->tree_id,
594
-			'tree_id_3' => $this->tree_id,
595
-			'tree_id_4' => $this->tree_id,
596
-			'tree_id_5' => $this->tree_id,
597
-		));
598
-
599
-		$buffer = FunctionsExport::reformatRecord(FunctionsExport::gedcomHeader($this));
600
-		while ($row = $stmt->fetch()) {
601
-			$buffer .= FunctionsExport::reformatRecord($row->gedcom);
602
-			if (strlen($buffer) > 65535) {
603
-				fwrite($stream, $buffer);
604
-				$buffer = '';
605
-			}
606
-		}
607
-		fwrite($stream, $buffer . '0 TRLR' . WT_EOL);
608
-		$stmt->closeCursor();
609
-	}
610
-
611
-	/**
612
-	 * Import data from a gedcom file into this tree.
613
-	 *
614
-	 * @param string  $path       The full path to the (possibly temporary) file.
615
-	 * @param string  $filename   The preferred filename, for export/download.
616
-	 *
617
-	 * @throws \Exception
618
-	 */
619
-	public function importGedcomFile($path, $filename) {
620
-		// Read the file in blocks of roughly 64K. Ensure that each block
621
-		// contains complete gedcom records. This will ensure we don’t split
622
-		// multi-byte characters, as well as simplifying the code to import
623
-		// each block.
624
-
625
-		$file_data = '';
626
-		$fp        = fopen($path, 'rb');
627
-
628
-		// Don’t allow the user to cancel the request. We do not want to be left with an incomplete transaction.
629
-		ignore_user_abort(true);
630
-
631
-		Database::beginTransaction();
632
-		$this->deleteGenealogyData($this->getPreference('keep_media'));
633
-		$this->setPreference('gedcom_filename', $filename);
634
-		$this->setPreference('imported', '0');
635
-
636
-		while (!feof($fp)) {
637
-			$file_data .= fread($fp, 65536);
638
-			// There is no strrpos() function that searches for substrings :-(
639
-			for ($pos = strlen($file_data) - 1; $pos > 0; --$pos) {
640
-				if ($file_data[$pos] === '0' && ($file_data[$pos - 1] === "\n" || $file_data[$pos - 1] === "\r")) {
641
-					// We’ve found the last record boundary in this chunk of data
642
-					break;
643
-				}
644
-			}
645
-			if ($pos) {
646
-				Database::prepare(
647
-					"INSERT INTO `##gedcom_chunk` (gedcom_id, chunk_data) VALUES (?, ?)"
648
-				)->execute(array($this->tree_id, substr($file_data, 0, $pos)));
649
-				$file_data = substr($file_data, $pos);
650
-			}
651
-		}
652
-		Database::prepare(
653
-			"INSERT INTO `##gedcom_chunk` (gedcom_id, chunk_data) VALUES (?, ?)"
654
-		)->execute(array($this->tree_id, $file_data));
655
-
656
-		Database::commit();
657
-		fclose($fp);
658
-	}
659
-
660
-	/**
661
-	 * Generate a new XREF, unique across all family trees
662
-	 *
663
-	 * @param string $type
664
-	 *
665
-	 * @return string
666
-	 */
667
-	public function getNewXref($type = 'INDI') {
668
-		/** @var string[] Which tree preference is used for which record type */
669
-		static $type_to_preference = array(
670
-			'INDI' => 'GEDCOM_ID_PREFIX',
671
-			'FAM'  => 'FAM_ID_PREFIX',
672
-			'OBJE' => 'MEDIA_ID_PREFIX',
673
-			'NOTE' => 'NOTE_ID_PREFIX',
674
-			'SOUR' => 'SOURCE_ID_PREFIX',
675
-			'REPO' => 'REPO_ID_PREFIX',
676
-		);
677
-
678
-		if (array_key_exists($type, $type_to_preference)) {
679
-			$prefix = $this->getPreference($type_to_preference[$type]);
680
-		} else {
681
-			// Use the first non-underscore character
682
-			$prefix = substr(trim($type, '_'), 0, 1);
683
-		}
684
-
685
-		$increment = 1.0;
686
-		do {
687
-			// Use LAST_INSERT_ID(expr) to provide a transaction-safe sequence. See
688
-			// http://dev.mysql.com/doc/refman/5.6/en/information-functions.html#function_last-insert-id
689
-			$statement = Database::prepare(
690
-				"UPDATE `##next_id` SET next_id = LAST_INSERT_ID(next_id + :increment) WHERE record_type = :record_type AND gedcom_id = :tree_id"
691
-			);
692
-			$statement->execute(array(
693
-				'increment'   => (int) $increment,
694
-				'record_type' => $type,
695
-				'tree_id'     => $this->tree_id,
696
-			));
697
-
698
-			if ($statement->rowCount() === 0) {
699
-				// First time we've used this record type.
700
-				Database::prepare(
701
-					"INSERT INTO `##next_id` (gedcom_id, record_type, next_id) VALUES(:tree_id, :record_type, 1)"
702
-				)->execute(array(
703
-					'record_type' => $type,
704
-					'tree_id'     => $this->tree_id,
705
-				));
706
-				$num = 1;
707
-			} else {
708
-				$num = Database::prepare("SELECT LAST_INSERT_ID()")->fetchOne();
709
-			}
710
-
711
-			// Records may already exist with this sequence number.
712
-			$already_used = Database::prepare(
713
-				"SELECT i_id FROM `##individuals` WHERE i_id = :i_id" .
714
-				" UNION ALL " .
715
-				"SELECT f_id FROM `##families` WHERE f_id = :f_id" .
716
-				" UNION ALL " .
717
-				"SELECT s_id FROM `##sources` WHERE s_id = :s_id" .
718
-				" UNION ALL " .
719
-				"SELECT m_id FROM `##media` WHERE m_id = :m_id" .
720
-				" UNION ALL " .
721
-				"SELECT o_id FROM `##other` WHERE o_id = :o_id" .
722
-				" UNION ALL " .
723
-				"SELECT xref FROM `##change` WHERE xref = :xref"
724
-			)->execute(array(
725
-				'i_id' => $prefix . $num,
726
-				'f_id' => $prefix . $num,
727
-				's_id' => $prefix . $num,
728
-				'm_id' => $prefix . $num,
729
-				'o_id' => $prefix . $num,
730
-				'xref' => $prefix . $num,
731
-			))->fetchOne();
732
-
733
-			// This exponential increment allows us to scan over large blocks of
734
-			// existing data in a reasonable time.
735
-			$increment *= 1.01;
736
-		} while ($already_used);
737
-
738
-		return $prefix . $num;
739
-	}
740
-
741
-	/**
742
-	 * Create a new record from GEDCOM data.
743
-	 *
744
-	 * @param string $gedcom
745
-	 *
746
-	 * @throws \Exception
747
-	 *
748
-	 * @return GedcomRecord
749
-	 */
750
-	public function createRecord($gedcom) {
751
-		if (preg_match('/^0 @(' . WT_REGEX_XREF . ')@ (' . WT_REGEX_TAG . ')/', $gedcom, $match)) {
752
-			$xref = $match[1];
753
-			$type = $match[2];
754
-		} else {
755
-			throw new \Exception('Invalid argument to GedcomRecord::createRecord(' . $gedcom . ')');
756
-		}
757
-		if (strpos("\r", $gedcom) !== false) {
758
-			// MSDOS line endings will break things in horrible ways
759
-			throw new \Exception('Evil line endings found in GedcomRecord::createRecord(' . $gedcom . ')');
760
-		}
761
-
762
-		// webtrees creates XREFs containing digits. Anything else (e.g. “new”) is just a placeholder.
763
-		if (!preg_match('/\d/', $xref)) {
764
-			$xref   = $this->getNewXref($type);
765
-			$gedcom = preg_replace('/^0 @(' . WT_REGEX_XREF . ')@/', '0 @' . $xref . '@', $gedcom);
766
-		}
767
-
768
-		// Create a change record, if not already present
769
-		if (!preg_match('/\n1 CHAN/', $gedcom)) {
770
-			$gedcom .= "\n1 CHAN\n2 DATE " . date('d M Y') . "\n3 TIME " . date('H:i:s') . "\n2 _WT_USER " . Auth::user()->getUserName();
771
-		}
772
-
773
-		// Create a pending change
774
-		Database::prepare(
775
-			"INSERT INTO `##change` (gedcom_id, xref, old_gedcom, new_gedcom, user_id) VALUES (?, ?, '', ?, ?)"
776
-		)->execute(array(
777
-			$this->tree_id,
778
-			$xref,
779
-			$gedcom,
780
-			Auth::id(),
781
-		));
782
-
783
-		Log::addEditLog('Create: ' . $type . ' ' . $xref);
784
-
785
-		// Accept this pending change
786
-		if (Auth::user()->getPreference('auto_accept')) {
787
-			FunctionsImport::acceptAllChanges($xref, $this->tree_id);
788
-		}
789
-		// Return the newly created record. Note that since GedcomRecord
790
-		// has a cache of pending changes, we cannot use it to create a
791
-		// record with a newly created pending change.
792
-		return GedcomRecord::getInstance($xref, $this, $gedcom);
793
-	}
26
+    /** @var int The tree's ID number */
27
+    private $tree_id;
28
+
29
+    /** @var string The tree's name */
30
+    private $name;
31
+
32
+    /** @var string The tree's title */
33
+    private $title;
34
+
35
+    /** @var int[] Default access rules for facts in this tree */
36
+    private $fact_privacy;
37
+
38
+    /** @var int[] Default access rules for individuals in this tree */
39
+    private $individual_privacy;
40
+
41
+    /** @var integer[][] Default access rules for individual facts in this tree */
42
+    private $individual_fact_privacy;
43
+
44
+    /** @var Tree[] All trees that we have permission to see. */
45
+    private static $trees;
46
+
47
+    /** @var string[] Cached copy of the wt_gedcom_setting table. */
48
+    private $preferences;
49
+
50
+    /** @var string[][] Cached copy of the wt_user_gedcom_setting table. */
51
+    private $user_preferences = array();
52
+
53
+    /**
54
+     * Create a tree object. This is a private constructor - it can only
55
+     * be called from Tree::getAll() to ensure proper initialisation.
56
+     *
57
+     * @param int    $tree_id
58
+     * @param string $tree_name
59
+     * @param string $tree_title
60
+     */
61
+    private function __construct($tree_id, $tree_name, $tree_title) {
62
+        $this->tree_id                 = $tree_id;
63
+        $this->name                    = $tree_name;
64
+        $this->title                   = $tree_title;
65
+        $this->fact_privacy            = array();
66
+        $this->individual_privacy      = array();
67
+        $this->individual_fact_privacy = array();
68
+
69
+        // Load the privacy settings for this tree
70
+        $rows = Database::prepare(
71
+            "SELECT xref, tag_type, CASE resn WHEN 'none' THEN :priv_public WHEN 'privacy' THEN :priv_user WHEN 'confidential' THEN :priv_none WHEN 'hidden' THEN :priv_hide END AS resn" .
72
+            " FROM `##default_resn` WHERE gedcom_id = :tree_id"
73
+        )->execute(array(
74
+            'priv_public' => Auth::PRIV_PRIVATE,
75
+            'priv_user'   => Auth::PRIV_USER,
76
+            'priv_none'   => Auth::PRIV_NONE,
77
+            'priv_hide'   => Auth::PRIV_HIDE,
78
+            'tree_id'     => $this->tree_id,
79
+        ))->fetchAll();
80
+
81
+        foreach ($rows as $row) {
82
+            if ($row->xref !== null) {
83
+                if ($row->tag_type !== null) {
84
+                    $this->individual_fact_privacy[$row->xref][$row->tag_type] = (int) $row->resn;
85
+                } else {
86
+                    $this->individual_privacy[$row->xref] = (int) $row->resn;
87
+                }
88
+            } else {
89
+                $this->fact_privacy[$row->tag_type] = (int) $row->resn;
90
+            }
91
+        }
92
+
93
+    }
94
+
95
+    /**
96
+     * The ID of this tree
97
+     *
98
+     * @return int
99
+     */
100
+    public function getTreeId() {
101
+        return $this->tree_id;
102
+    }
103
+
104
+    /**
105
+     * The name of this tree
106
+     *
107
+     * @return string
108
+     */
109
+    public function getName() {
110
+        return $this->name;
111
+    }
112
+
113
+    /**
114
+     * The name of this tree
115
+     *
116
+     * @return string
117
+     */
118
+    public function getNameHtml() {
119
+        return Filter::escapeHtml($this->name);
120
+    }
121
+
122
+    /**
123
+     * The name of this tree
124
+     *
125
+     * @return string
126
+     */
127
+    public function getNameUrl() {
128
+        return Filter::escapeUrl($this->name);
129
+    }
130
+
131
+    /**
132
+     * The title of this tree
133
+     *
134
+     * @return string
135
+     */
136
+    public function getTitle() {
137
+        return $this->title;
138
+    }
139
+
140
+    /**
141
+     * The title of this tree, with HTML markup
142
+     *
143
+     * @return string
144
+     */
145
+    public function getTitleHtml() {
146
+        return '<span dir="auto">' . Filter::escapeHtml($this->title) . '</span>';
147
+    }
148
+
149
+    /**
150
+     * The fact-level privacy for this tree.
151
+     *
152
+     * @return int[]
153
+     */
154
+    public function getFactPrivacy() {
155
+        return $this->fact_privacy;
156
+    }
157
+
158
+    /**
159
+     * The individual-level privacy for this tree.
160
+     *
161
+     * @return int[]
162
+     */
163
+    public function getIndividualPrivacy() {
164
+        return $this->individual_privacy;
165
+    }
166
+
167
+    /**
168
+     * The individual-fact-level privacy for this tree.
169
+     *
170
+     * @return integer[][]
171
+     */
172
+    public function getIndividualFactPrivacy() {
173
+        return $this->individual_fact_privacy;
174
+    }
175
+
176
+    /**
177
+     * Get the tree’s configuration settings.
178
+     *
179
+     * @param string      $setting_name
180
+     * @param string|null $default
181
+     *
182
+     * @return string|null
183
+     */
184
+    public function getPreference($setting_name, $default = null) {
185
+        if ($this->preferences === null) {
186
+            $this->preferences = Database::prepare(
187
+                "SELECT setting_name, setting_value FROM `##gedcom_setting` WHERE gedcom_id = ?"
188
+            )->execute(array($this->tree_id))->fetchAssoc();
189
+        }
190
+
191
+        if (array_key_exists($setting_name, $this->preferences)) {
192
+            return $this->preferences[$setting_name];
193
+        } else {
194
+            return $default;
195
+        }
196
+    }
197
+
198
+    /**
199
+     * Set the tree’s configuration settings.
200
+     *
201
+     * @param string $setting_name
202
+     * @param string $setting_value
203
+     *
204
+     * @return $this
205
+     */
206
+    public function setPreference($setting_name, $setting_value) {
207
+        if ($setting_value !== $this->getPreference($setting_name)) {
208
+            // Update the database
209
+            if ($setting_value === null) {
210
+                Database::prepare(
211
+                    "DELETE FROM `##gedcom_setting` WHERE gedcom_id = :tree_id AND setting_name = :setting_name"
212
+                )->execute(array(
213
+                    'tree_id'      => $this->tree_id,
214
+                    'setting_name' => $setting_name,
215
+                ));
216
+            } else {
217
+                Database::prepare(
218
+                    "REPLACE INTO `##gedcom_setting` (gedcom_id, setting_name, setting_value)" .
219
+                    " VALUES (:tree_id, :setting_name, LEFT(:setting_value, 255))"
220
+                )->execute(array(
221
+                    'tree_id'       => $this->tree_id,
222
+                    'setting_name'  => $setting_name,
223
+                    'setting_value' => $setting_value,
224
+                ));
225
+            }
226
+            // Update our cache
227
+            $this->preferences[$setting_name] = $setting_value;
228
+            // Audit log of changes
229
+            Log::addConfigurationLog('Tree preference "' . $setting_name . '" set to "' . $setting_value . '"', $this);
230
+        }
231
+
232
+        return $this;
233
+    }
234
+
235
+    /**
236
+     * Get the tree’s user-configuration settings.
237
+     *
238
+     * @param User        $user
239
+     * @param string      $setting_name
240
+     * @param string|null $default
241
+     *
242
+     * @return string
243
+     */
244
+    public function getUserPreference(User $user, $setting_name, $default = null) {
245
+        // There are lots of settings, and we need to fetch lots of them on every page
246
+        // so it is quicker to fetch them all in one go.
247
+        if (!array_key_exists($user->getUserId(), $this->user_preferences)) {
248
+            $this->user_preferences[$user->getUserId()] = Database::prepare(
249
+                "SELECT setting_name, setting_value FROM `##user_gedcom_setting` WHERE user_id = ? AND gedcom_id = ?"
250
+            )->execute(array($user->getUserId(), $this->tree_id))->fetchAssoc();
251
+        }
252
+
253
+        if (array_key_exists($setting_name, $this->user_preferences[$user->getUserId()])) {
254
+            return $this->user_preferences[$user->getUserId()][$setting_name];
255
+        } else {
256
+            return $default;
257
+        }
258
+    }
259
+
260
+    /**
261
+     * Set the tree’s user-configuration settings.
262
+     *
263
+     * @param User    $user
264
+     * @param string  $setting_name
265
+     * @param string  $setting_value
266
+     *
267
+     * @return $this
268
+     */
269
+    public function setUserPreference(User $user, $setting_name, $setting_value) {
270
+        if ($this->getUserPreference($user, $setting_name) !== $setting_value) {
271
+            // Update the database
272
+            if ($setting_value === null) {
273
+                Database::prepare(
274
+                    "DELETE FROM `##user_gedcom_setting` WHERE gedcom_id = :tree_id AND user_id = :user_id AND setting_name = :setting_name"
275
+                )->execute(array(
276
+                    'tree_id'      => $this->tree_id,
277
+                    'user_id'      => $user->getUserId(),
278
+                    'setting_name' => $setting_name,
279
+                ));
280
+            } else {
281
+                Database::prepare(
282
+                    "REPLACE INTO `##user_gedcom_setting` (user_id, gedcom_id, setting_name, setting_value) VALUES (:user_id, :tree_id, :setting_name, LEFT(:setting_value, 255))"
283
+                )->execute(array(
284
+                    'user_id'       => $user->getUserId(),
285
+                    'tree_id'       => $this->tree_id,
286
+                    'setting_name'  => $setting_name,
287
+                    'setting_value' => $setting_value,
288
+                ));
289
+            }
290
+            // Update our cache
291
+            $this->user_preferences[$user->getUserId()][$setting_name] = $setting_value;
292
+            // Audit log of changes
293
+            Log::addConfigurationLog('Tree preference "' . $setting_name . '" set to "' . $setting_value . '" for user "' . $user->getUserName() . '"', $this);
294
+        }
295
+
296
+        return $this;
297
+    }
298
+
299
+    /**
300
+     * Can a user accept changes for this tree?
301
+     *
302
+     * @param User $user
303
+     *
304
+     * @return bool
305
+     */
306
+    public function canAcceptChanges(User $user) {
307
+        return Auth::isModerator($this, $user);
308
+    }
309
+
310
+    /**
311
+     * Fetch all the trees that we have permission to access.
312
+     *
313
+     * @return Tree[]
314
+     */
315
+    public static function getAll() {
316
+        if (self::$trees === null) {
317
+            self::$trees = array();
318
+            $rows        = Database::prepare(
319
+                "SELECT g.gedcom_id AS tree_id, g.gedcom_name AS tree_name, gs1.setting_value AS tree_title" .
320
+                " FROM `##gedcom` g" .
321
+                " LEFT JOIN `##gedcom_setting`      gs1 ON (g.gedcom_id=gs1.gedcom_id AND gs1.setting_name='title')" .
322
+                " LEFT JOIN `##gedcom_setting`      gs2 ON (g.gedcom_id=gs2.gedcom_id AND gs2.setting_name='imported')" .
323
+                " LEFT JOIN `##gedcom_setting`      gs3 ON (g.gedcom_id=gs3.gedcom_id AND gs3.setting_name='REQUIRE_AUTHENTICATION')" .
324
+                " LEFT JOIN `##user_gedcom_setting` ugs ON (g.gedcom_id=ugs.gedcom_id AND ugs.setting_name='canedit' AND ugs.user_id=?)" .
325
+                " WHERE " .
326
+                "  g.gedcom_id>0 AND (" . // exclude the "template" tree
327
+                "    EXISTS (SELECT 1 FROM `##user_setting` WHERE user_id=? AND setting_name='canadmin' AND setting_value=1)" . // Admin sees all
328
+                "   ) OR (" .
329
+                "    (gs2.setting_value = 1 OR ugs.setting_value = 'admin') AND (" . // Allow imported trees, with either:
330
+                "     gs3.setting_value <> 1 OR" . // visitor access
331
+                "     IFNULL(ugs.setting_value, 'none')<>'none'" . // explicit access
332
+                "   )" .
333
+                "  )" .
334
+                " ORDER BY g.sort_order, 3"
335
+            )->execute(array(Auth::id(), Auth::id()))->fetchAll();
336
+            foreach ($rows as $row) {
337
+                self::$trees[] = new self((int) $row->tree_id, $row->tree_name, $row->tree_title);
338
+            }
339
+        }
340
+
341
+        return self::$trees;
342
+    }
343
+
344
+    /**
345
+     * Find the tree with a specific ID.
346
+     *
347
+     * @param int $tree_id
348
+     *
349
+     * @throws \DomainException
350
+     *
351
+     * @return Tree
352
+     */
353
+    public static function findById($tree_id) {
354
+        foreach (self::getAll() as $tree) {
355
+            if ($tree->tree_id == $tree_id) {
356
+                return $tree;
357
+            }
358
+        }
359
+        throw new \DomainException;
360
+    }
361
+
362
+    /**
363
+     * Find the tree with a specific name.
364
+     *
365
+     * @param string $tree_name
366
+     *
367
+     * @return Tree|null
368
+     */
369
+    public static function findByName($tree_name) {
370
+        foreach (self::getAll() as $tree) {
371
+            if ($tree->name === $tree_name) {
372
+                return $tree;
373
+            }
374
+        }
375
+
376
+        return null;
377
+    }
378
+
379
+    /**
380
+     * Create arguments to select_edit_control()
381
+     * Note - these will be escaped later
382
+     *
383
+     * @return string[]
384
+     */
385
+    public static function getIdList() {
386
+        $list = array();
387
+        foreach (self::getAll() as $tree) {
388
+            $list[$tree->tree_id] = $tree->title;
389
+        }
390
+
391
+        return $list;
392
+    }
393
+
394
+    /**
395
+     * Create arguments to select_edit_control()
396
+     * Note - these will be escaped later
397
+     *
398
+     * @return string[]
399
+     */
400
+    public static function getNameList() {
401
+        $list = array();
402
+        foreach (self::getAll() as $tree) {
403
+            $list[$tree->name] = $tree->title;
404
+        }
405
+
406
+        return $list;
407
+    }
408
+
409
+    /**
410
+     * Create a new tree
411
+     *
412
+     * @param string $tree_name
413
+     * @param string $tree_title
414
+     *
415
+     * @return Tree
416
+     */
417
+    public static function create($tree_name, $tree_title) {
418
+        try {
419
+            // Create a new tree
420
+            Database::prepare(
421
+                "INSERT INTO `##gedcom` (gedcom_name) VALUES (?)"
422
+            )->execute(array($tree_name));
423
+            $tree_id = Database::prepare("SELECT LAST_INSERT_ID()")->fetchOne();
424
+        } catch (PDOException $ex) {
425
+            // A tree with that name already exists?
426
+            return self::findByName($tree_name);
427
+        }
428
+
429
+        // Update the list of trees - to include this new one
430
+        self::$trees = null;
431
+        $tree        = self::findById($tree_id);
432
+
433
+        $tree->setPreference('imported', '0');
434
+        $tree->setPreference('title', $tree_title);
435
+
436
+        // Module privacy
437
+        Module::setDefaultAccess($tree_id);
438
+
439
+        // Set preferences from default tree
440
+        Database::prepare(
441
+            "INSERT INTO `##gedcom_setting` (gedcom_id, setting_name, setting_value)" .
442
+            " SELECT :tree_id, setting_name, setting_value" .
443
+            " FROM `##gedcom_setting` WHERE gedcom_id = -1"
444
+        )->execute(array(
445
+            'tree_id' => $tree_id,
446
+        ));
447
+
448
+        Database::prepare(
449
+            "INSERT INTO `##default_resn` (gedcom_id, tag_type, resn)" .
450
+            " SELECT :tree_id, tag_type, resn" .
451
+            " FROM `##default_resn` WHERE gedcom_id = -1"
452
+        )->execute(array(
453
+            'tree_id' => $tree_id,
454
+        ));
455
+
456
+        Database::prepare(
457
+            "INSERT INTO `##block` (gedcom_id, location, block_order, module_name)" .
458
+            " SELECT :tree_id, location, block_order, module_name" .
459
+            " FROM `##block` WHERE gedcom_id = -1"
460
+        )->execute(array(
461
+            'tree_id' => $tree_id,
462
+        ));
463
+
464
+        // Gedcom and privacy settings
465
+        $tree->setPreference('CONTACT_USER_ID', Auth::id());
466
+        $tree->setPreference('WEBMASTER_USER_ID', Auth::id());
467
+        $tree->setPreference('LANGUAGE', WT_LOCALE); // Default to the current admin’s language
468
+        switch (WT_LOCALE) {
469
+        case 'es':
470
+            $tree->setPreference('SURNAME_TRADITION', 'spanish');
471
+            break;
472
+        case 'is':
473
+            $tree->setPreference('SURNAME_TRADITION', 'icelandic');
474
+            break;
475
+        case 'lt':
476
+            $tree->setPreference('SURNAME_TRADITION', 'lithuanian');
477
+            break;
478
+        case 'pl':
479
+            $tree->setPreference('SURNAME_TRADITION', 'polish');
480
+            break;
481
+        case 'pt':
482
+        case 'pt-BR':
483
+            $tree->setPreference('SURNAME_TRADITION', 'portuguese');
484
+            break;
485
+        default:
486
+            $tree->setPreference('SURNAME_TRADITION', 'paternal');
487
+            break;
488
+        }
489
+
490
+        // Genealogy data
491
+        // It is simpler to create a temporary/unimported GEDCOM than to populate all the tables...
492
+        $john_doe = /* I18N: This should be a common/default/placeholder name of an individual. Put slashes around the surname. */
493
+            I18N::translate('John /DOE/');
494
+        $note     = I18N::translate('Edit this individual and replace their details with your own.');
495
+        Database::prepare("INSERT INTO `##gedcom_chunk` (gedcom_id, chunk_data) VALUES (?, ?)")->execute(array(
496
+            $tree_id,
497
+            "0 HEAD\n1 CHAR UTF-8\n0 @I1@ INDI\n1 NAME {$john_doe}\n1 SEX M\n1 BIRT\n2 DATE 01 JAN 1850\n2 NOTE {$note}\n0 TRLR\n",
498
+        ));
499
+
500
+        // Update our cache
501
+        self::$trees[$tree->tree_id] = $tree;
502
+
503
+        return $tree;
504
+    }
505
+
506
+    /**
507
+     * Are there any pending edits for this tree, than need reviewing by a moderator.
508
+     *
509
+     * @return bool
510
+     */
511
+    public function hasPendingEdit() {
512
+        return (bool) Database::prepare(
513
+            "SELECT 1 FROM `##change` WHERE status = 'pending' AND gedcom_id = :tree_id"
514
+        )->execute(array(
515
+            'tree_id' => $this->tree_id,
516
+        ))->fetchOne();
517
+    }
518
+
519
+    /**
520
+     * Delete all the genealogy data from a tree - in preparation for importing
521
+     * new data. Optionally retain the media data, for when the user has been
522
+     * editing their data offline using an application which deletes (or does not
523
+     * support) media data.
524
+     *
525
+     * @param bool $keep_media
526
+     */
527
+    public function deleteGenealogyData($keep_media) {
528
+        Database::prepare("DELETE FROM `##gedcom_chunk` WHERE gedcom_id = ?")->execute(array($this->tree_id));
529
+        Database::prepare("DELETE FROM `##individuals`  WHERE i_file    = ?")->execute(array($this->tree_id));
530
+        Database::prepare("DELETE FROM `##families`     WHERE f_file    = ?")->execute(array($this->tree_id));
531
+        Database::prepare("DELETE FROM `##sources`      WHERE s_file    = ?")->execute(array($this->tree_id));
532
+        Database::prepare("DELETE FROM `##other`        WHERE o_file    = ?")->execute(array($this->tree_id));
533
+        Database::prepare("DELETE FROM `##places`       WHERE p_file    = ?")->execute(array($this->tree_id));
534
+        Database::prepare("DELETE FROM `##placelinks`   WHERE pl_file   = ?")->execute(array($this->tree_id));
535
+        Database::prepare("DELETE FROM `##name`         WHERE n_file    = ?")->execute(array($this->tree_id));
536
+        Database::prepare("DELETE FROM `##dates`        WHERE d_file    = ?")->execute(array($this->tree_id));
537
+        Database::prepare("DELETE FROM `##change`       WHERE gedcom_id = ?")->execute(array($this->tree_id));
538
+
539
+        if ($keep_media) {
540
+            Database::prepare("DELETE FROM `##link` WHERE l_file =? AND l_type<>'OBJE'")->execute(array($this->tree_id));
541
+        } else {
542
+            Database::prepare("DELETE FROM `##link`  WHERE l_file =?")->execute(array($this->tree_id));
543
+            Database::prepare("DELETE FROM `##media` WHERE m_file =?")->execute(array($this->tree_id));
544
+        }
545
+    }
546
+
547
+    /**
548
+     * Delete everything relating to a tree
549
+     */
550
+    public function delete() {
551
+        // If this is the default tree, then unset it
552
+        if (Site::getPreference('DEFAULT_GEDCOM') === $this->name) {
553
+            Site::setPreference('DEFAULT_GEDCOM', '');
554
+        }
555
+
556
+        $this->deleteGenealogyData(false);
557
+
558
+        Database::prepare("DELETE `##block_setting` FROM `##block_setting` JOIN `##block` USING (block_id) WHERE gedcom_id=?")->execute(array($this->tree_id));
559
+        Database::prepare("DELETE FROM `##block`               WHERE gedcom_id = ?")->execute(array($this->tree_id));
560
+        Database::prepare("DELETE FROM `##user_gedcom_setting` WHERE gedcom_id = ?")->execute(array($this->tree_id));
561
+        Database::prepare("DELETE FROM `##gedcom_setting`      WHERE gedcom_id = ?")->execute(array($this->tree_id));
562
+        Database::prepare("DELETE FROM `##module_privacy`      WHERE gedcom_id = ?")->execute(array($this->tree_id));
563
+        Database::prepare("DELETE FROM `##next_id`             WHERE gedcom_id = ?")->execute(array($this->tree_id));
564
+        Database::prepare("DELETE FROM `##hit_counter`         WHERE gedcom_id = ?")->execute(array($this->tree_id));
565
+        Database::prepare("DELETE FROM `##default_resn`        WHERE gedcom_id = ?")->execute(array($this->tree_id));
566
+        Database::prepare("DELETE FROM `##gedcom_chunk`        WHERE gedcom_id = ?")->execute(array($this->tree_id));
567
+        Database::prepare("DELETE FROM `##log`                 WHERE gedcom_id = ?")->execute(array($this->tree_id));
568
+        Database::prepare("DELETE FROM `##gedcom`              WHERE gedcom_id = ?")->execute(array($this->tree_id));
569
+
570
+        // After updating the database, we need to fetch a new (sorted) copy
571
+        self::$trees = null;
572
+    }
573
+
574
+    /**
575
+     * Export the tree to a GEDCOM file
576
+     *
577
+     * @param resource $stream
578
+     */
579
+    public function exportGedcom($stream) {
580
+        $stmt = Database::prepare(
581
+            "SELECT i_gedcom AS gedcom, i_id AS xref, 1 AS n FROM `##individuals` WHERE i_file = :tree_id_1" .
582
+            " UNION ALL " .
583
+            "SELECT f_gedcom AS gedcom, f_id AS xref, 2 AS n FROM `##families`    WHERE f_file = :tree_id_2" .
584
+            " UNION ALL " .
585
+            "SELECT s_gedcom AS gedcom, s_id AS xref, 3 AS n FROM `##sources`     WHERE s_file = :tree_id_3" .
586
+            " UNION ALL " .
587
+            "SELECT o_gedcom AS gedcom, o_id AS xref, 4 AS n FROM `##other`       WHERE o_file = :tree_id_4 AND o_type NOT IN ('HEAD', 'TRLR')" .
588
+            " UNION ALL " .
589
+            "SELECT m_gedcom AS gedcom, m_id AS xref, 5 AS n FROM `##media`       WHERE m_file = :tree_id_5" .
590
+            " ORDER BY n, LENGTH(xref), xref"
591
+        )->execute(array(
592
+            'tree_id_1' => $this->tree_id,
593
+            'tree_id_2' => $this->tree_id,
594
+            'tree_id_3' => $this->tree_id,
595
+            'tree_id_4' => $this->tree_id,
596
+            'tree_id_5' => $this->tree_id,
597
+        ));
598
+
599
+        $buffer = FunctionsExport::reformatRecord(FunctionsExport::gedcomHeader($this));
600
+        while ($row = $stmt->fetch()) {
601
+            $buffer .= FunctionsExport::reformatRecord($row->gedcom);
602
+            if (strlen($buffer) > 65535) {
603
+                fwrite($stream, $buffer);
604
+                $buffer = '';
605
+            }
606
+        }
607
+        fwrite($stream, $buffer . '0 TRLR' . WT_EOL);
608
+        $stmt->closeCursor();
609
+    }
610
+
611
+    /**
612
+     * Import data from a gedcom file into this tree.
613
+     *
614
+     * @param string  $path       The full path to the (possibly temporary) file.
615
+     * @param string  $filename   The preferred filename, for export/download.
616
+     *
617
+     * @throws \Exception
618
+     */
619
+    public function importGedcomFile($path, $filename) {
620
+        // Read the file in blocks of roughly 64K. Ensure that each block
621
+        // contains complete gedcom records. This will ensure we don’t split
622
+        // multi-byte characters, as well as simplifying the code to import
623
+        // each block.
624
+
625
+        $file_data = '';
626
+        $fp        = fopen($path, 'rb');
627
+
628
+        // Don’t allow the user to cancel the request. We do not want to be left with an incomplete transaction.
629
+        ignore_user_abort(true);
630
+
631
+        Database::beginTransaction();
632
+        $this->deleteGenealogyData($this->getPreference('keep_media'));
633
+        $this->setPreference('gedcom_filename', $filename);
634
+        $this->setPreference('imported', '0');
635
+
636
+        while (!feof($fp)) {
637
+            $file_data .= fread($fp, 65536);
638
+            // There is no strrpos() function that searches for substrings :-(
639
+            for ($pos = strlen($file_data) - 1; $pos > 0; --$pos) {
640
+                if ($file_data[$pos] === '0' && ($file_data[$pos - 1] === "\n" || $file_data[$pos - 1] === "\r")) {
641
+                    // We’ve found the last record boundary in this chunk of data
642
+                    break;
643
+                }
644
+            }
645
+            if ($pos) {
646
+                Database::prepare(
647
+                    "INSERT INTO `##gedcom_chunk` (gedcom_id, chunk_data) VALUES (?, ?)"
648
+                )->execute(array($this->tree_id, substr($file_data, 0, $pos)));
649
+                $file_data = substr($file_data, $pos);
650
+            }
651
+        }
652
+        Database::prepare(
653
+            "INSERT INTO `##gedcom_chunk` (gedcom_id, chunk_data) VALUES (?, ?)"
654
+        )->execute(array($this->tree_id, $file_data));
655
+
656
+        Database::commit();
657
+        fclose($fp);
658
+    }
659
+
660
+    /**
661
+     * Generate a new XREF, unique across all family trees
662
+     *
663
+     * @param string $type
664
+     *
665
+     * @return string
666
+     */
667
+    public function getNewXref($type = 'INDI') {
668
+        /** @var string[] Which tree preference is used for which record type */
669
+        static $type_to_preference = array(
670
+            'INDI' => 'GEDCOM_ID_PREFIX',
671
+            'FAM'  => 'FAM_ID_PREFIX',
672
+            'OBJE' => 'MEDIA_ID_PREFIX',
673
+            'NOTE' => 'NOTE_ID_PREFIX',
674
+            'SOUR' => 'SOURCE_ID_PREFIX',
675
+            'REPO' => 'REPO_ID_PREFIX',
676
+        );
677
+
678
+        if (array_key_exists($type, $type_to_preference)) {
679
+            $prefix = $this->getPreference($type_to_preference[$type]);
680
+        } else {
681
+            // Use the first non-underscore character
682
+            $prefix = substr(trim($type, '_'), 0, 1);
683
+        }
684
+
685
+        $increment = 1.0;
686
+        do {
687
+            // Use LAST_INSERT_ID(expr) to provide a transaction-safe sequence. See
688
+            // http://dev.mysql.com/doc/refman/5.6/en/information-functions.html#function_last-insert-id
689
+            $statement = Database::prepare(
690
+                "UPDATE `##next_id` SET next_id = LAST_INSERT_ID(next_id + :increment) WHERE record_type = :record_type AND gedcom_id = :tree_id"
691
+            );
692
+            $statement->execute(array(
693
+                'increment'   => (int) $increment,
694
+                'record_type' => $type,
695
+                'tree_id'     => $this->tree_id,
696
+            ));
697
+
698
+            if ($statement->rowCount() === 0) {
699
+                // First time we've used this record type.
700
+                Database::prepare(
701
+                    "INSERT INTO `##next_id` (gedcom_id, record_type, next_id) VALUES(:tree_id, :record_type, 1)"
702
+                )->execute(array(
703
+                    'record_type' => $type,
704
+                    'tree_id'     => $this->tree_id,
705
+                ));
706
+                $num = 1;
707
+            } else {
708
+                $num = Database::prepare("SELECT LAST_INSERT_ID()")->fetchOne();
709
+            }
710
+
711
+            // Records may already exist with this sequence number.
712
+            $already_used = Database::prepare(
713
+                "SELECT i_id FROM `##individuals` WHERE i_id = :i_id" .
714
+                " UNION ALL " .
715
+                "SELECT f_id FROM `##families` WHERE f_id = :f_id" .
716
+                " UNION ALL " .
717
+                "SELECT s_id FROM `##sources` WHERE s_id = :s_id" .
718
+                " UNION ALL " .
719
+                "SELECT m_id FROM `##media` WHERE m_id = :m_id" .
720
+                " UNION ALL " .
721
+                "SELECT o_id FROM `##other` WHERE o_id = :o_id" .
722
+                " UNION ALL " .
723
+                "SELECT xref FROM `##change` WHERE xref = :xref"
724
+            )->execute(array(
725
+                'i_id' => $prefix . $num,
726
+                'f_id' => $prefix . $num,
727
+                's_id' => $prefix . $num,
728
+                'm_id' => $prefix . $num,
729
+                'o_id' => $prefix . $num,
730
+                'xref' => $prefix . $num,
731
+            ))->fetchOne();
732
+
733
+            // This exponential increment allows us to scan over large blocks of
734
+            // existing data in a reasonable time.
735
+            $increment *= 1.01;
736
+        } while ($already_used);
737
+
738
+        return $prefix . $num;
739
+    }
740
+
741
+    /**
742
+     * Create a new record from GEDCOM data.
743
+     *
744
+     * @param string $gedcom
745
+     *
746
+     * @throws \Exception
747
+     *
748
+     * @return GedcomRecord
749
+     */
750
+    public function createRecord($gedcom) {
751
+        if (preg_match('/^0 @(' . WT_REGEX_XREF . ')@ (' . WT_REGEX_TAG . ')/', $gedcom, $match)) {
752
+            $xref = $match[1];
753
+            $type = $match[2];
754
+        } else {
755
+            throw new \Exception('Invalid argument to GedcomRecord::createRecord(' . $gedcom . ')');
756
+        }
757
+        if (strpos("\r", $gedcom) !== false) {
758
+            // MSDOS line endings will break things in horrible ways
759
+            throw new \Exception('Evil line endings found in GedcomRecord::createRecord(' . $gedcom . ')');
760
+        }
761
+
762
+        // webtrees creates XREFs containing digits. Anything else (e.g. “new”) is just a placeholder.
763
+        if (!preg_match('/\d/', $xref)) {
764
+            $xref   = $this->getNewXref($type);
765
+            $gedcom = preg_replace('/^0 @(' . WT_REGEX_XREF . ')@/', '0 @' . $xref . '@', $gedcom);
766
+        }
767
+
768
+        // Create a change record, if not already present
769
+        if (!preg_match('/\n1 CHAN/', $gedcom)) {
770
+            $gedcom .= "\n1 CHAN\n2 DATE " . date('d M Y') . "\n3 TIME " . date('H:i:s') . "\n2 _WT_USER " . Auth::user()->getUserName();
771
+        }
772
+
773
+        // Create a pending change
774
+        Database::prepare(
775
+            "INSERT INTO `##change` (gedcom_id, xref, old_gedcom, new_gedcom, user_id) VALUES (?, ?, '', ?, ?)"
776
+        )->execute(array(
777
+            $this->tree_id,
778
+            $xref,
779
+            $gedcom,
780
+            Auth::id(),
781
+        ));
782
+
783
+        Log::addEditLog('Create: ' . $type . ' ' . $xref);
784
+
785
+        // Accept this pending change
786
+        if (Auth::user()->getPreference('auto_accept')) {
787
+            FunctionsImport::acceptAllChanges($xref, $this->tree_id);
788
+        }
789
+        // Return the newly created record. Note that since GedcomRecord
790
+        // has a cache of pending changes, we cannot use it to create a
791
+        // record with a newly created pending change.
792
+        return GedcomRecord::getInstance($xref, $this, $gedcom);
793
+    }
794 794
 }
Please login to merge, or discard this patch.
Switch Indentation   +19 added lines, -19 removed lines patch added patch discarded remove patch
@@ -466,25 +466,25 @@
 block discarded – undo
466 466
 		$tree->setPreference('WEBMASTER_USER_ID', Auth::id());
467 467
 		$tree->setPreference('LANGUAGE', WT_LOCALE); // Default to the current admin’s language
468 468
 		switch (WT_LOCALE) {
469
-		case 'es':
470
-			$tree->setPreference('SURNAME_TRADITION', 'spanish');
471
-			break;
472
-		case 'is':
473
-			$tree->setPreference('SURNAME_TRADITION', 'icelandic');
474
-			break;
475
-		case 'lt':
476
-			$tree->setPreference('SURNAME_TRADITION', 'lithuanian');
477
-			break;
478
-		case 'pl':
479
-			$tree->setPreference('SURNAME_TRADITION', 'polish');
480
-			break;
481
-		case 'pt':
482
-		case 'pt-BR':
483
-			$tree->setPreference('SURNAME_TRADITION', 'portuguese');
484
-			break;
485
-		default:
486
-			$tree->setPreference('SURNAME_TRADITION', 'paternal');
487
-			break;
469
+		    case 'es':
470
+			    $tree->setPreference('SURNAME_TRADITION', 'spanish');
471
+			    break;
472
+		    case 'is':
473
+			    $tree->setPreference('SURNAME_TRADITION', 'icelandic');
474
+			    break;
475
+		    case 'lt':
476
+			    $tree->setPreference('SURNAME_TRADITION', 'lithuanian');
477
+			    break;
478
+		    case 'pl':
479
+			    $tree->setPreference('SURNAME_TRADITION', 'polish');
480
+			    break;
481
+		    case 'pt':
482
+		    case 'pt-BR':
483
+			    $tree->setPreference('SURNAME_TRADITION', 'portuguese');
484
+			    break;
485
+		    default:
486
+			    $tree->setPreference('SURNAME_TRADITION', 'paternal');
487
+			    break;
488 488
 		}
489 489
 
490 490
 		// Genealogy data
Please login to merge, or discard this patch.
Braces   +58 added lines, -29 removed lines patch added patch discarded remove patch
@@ -22,7 +22,8 @@  discard block
 block discarded – undo
22 22
 /**
23 23
  * Provide an interface to the wt_gedcom table.
24 24
  */
25
-class Tree {
25
+class Tree
26
+{
26 27
 	/** @var int The tree's ID number */
27 28
 	private $tree_id;
28 29
 
@@ -58,7 +59,8 @@  discard block
 block discarded – undo
58 59
 	 * @param string $tree_name
59 60
 	 * @param string $tree_title
60 61
 	 */
61
-	private function __construct($tree_id, $tree_name, $tree_title) {
62
+	private function __construct($tree_id, $tree_name, $tree_title)
63
+	{
62 64
 		$this->tree_id                 = $tree_id;
63 65
 		$this->name                    = $tree_name;
64 66
 		$this->title                   = $tree_title;
@@ -97,7 +99,8 @@  discard block
 block discarded – undo
97 99
 	 *
98 100
 	 * @return int
99 101
 	 */
100
-	public function getTreeId() {
102
+	public function getTreeId()
103
+	{
101 104
 		return $this->tree_id;
102 105
 	}
103 106
 
@@ -106,7 +109,8 @@  discard block
 block discarded – undo
106 109
 	 *
107 110
 	 * @return string
108 111
 	 */
109
-	public function getName() {
112
+	public function getName()
113
+	{
110 114
 		return $this->name;
111 115
 	}
112 116
 
@@ -115,7 +119,8 @@  discard block
 block discarded – undo
115 119
 	 *
116 120
 	 * @return string
117 121
 	 */
118
-	public function getNameHtml() {
122
+	public function getNameHtml()
123
+	{
119 124
 		return Filter::escapeHtml($this->name);
120 125
 	}
121 126
 
@@ -124,7 +129,8 @@  discard block
 block discarded – undo
124 129
 	 *
125 130
 	 * @return string
126 131
 	 */
127
-	public function getNameUrl() {
132
+	public function getNameUrl()
133
+	{
128 134
 		return Filter::escapeUrl($this->name);
129 135
 	}
130 136
 
@@ -133,7 +139,8 @@  discard block
 block discarded – undo
133 139
 	 *
134 140
 	 * @return string
135 141
 	 */
136
-	public function getTitle() {
142
+	public function getTitle()
143
+	{
137 144
 		return $this->title;
138 145
 	}
139 146
 
@@ -142,7 +149,8 @@  discard block
 block discarded – undo
142 149
 	 *
143 150
 	 * @return string
144 151
 	 */
145
-	public function getTitleHtml() {
152
+	public function getTitleHtml()
153
+	{
146 154
 		return '<span dir="auto">' . Filter::escapeHtml($this->title) . '</span>';
147 155
 	}
148 156
 
@@ -151,7 +159,8 @@  discard block
 block discarded – undo
151 159
 	 *
152 160
 	 * @return int[]
153 161
 	 */
154
-	public function getFactPrivacy() {
162
+	public function getFactPrivacy()
163
+	{
155 164
 		return $this->fact_privacy;
156 165
 	}
157 166
 
@@ -160,7 +169,8 @@  discard block
 block discarded – undo
160 169
 	 *
161 170
 	 * @return int[]
162 171
 	 */
163
-	public function getIndividualPrivacy() {
172
+	public function getIndividualPrivacy()
173
+	{
164 174
 		return $this->individual_privacy;
165 175
 	}
166 176
 
@@ -169,7 +179,8 @@  discard block
 block discarded – undo
169 179
 	 *
170 180
 	 * @return integer[][]
171 181
 	 */
172
-	public function getIndividualFactPrivacy() {
182
+	public function getIndividualFactPrivacy()
183
+	{
173 184
 		return $this->individual_fact_privacy;
174 185
 	}
175 186
 
@@ -181,7 +192,8 @@  discard block
 block discarded – undo
181 192
 	 *
182 193
 	 * @return string|null
183 194
 	 */
184
-	public function getPreference($setting_name, $default = null) {
195
+	public function getPreference($setting_name, $default = null)
196
+	{
185 197
 		if ($this->preferences === null) {
186 198
 			$this->preferences = Database::prepare(
187 199
 				"SELECT setting_name, setting_value FROM `##gedcom_setting` WHERE gedcom_id = ?"
@@ -203,7 +215,8 @@  discard block
 block discarded – undo
203 215
 	 *
204 216
 	 * @return $this
205 217
 	 */
206
-	public function setPreference($setting_name, $setting_value) {
218
+	public function setPreference($setting_name, $setting_value)
219
+	{
207 220
 		if ($setting_value !== $this->getPreference($setting_name)) {
208 221
 			// Update the database
209 222
 			if ($setting_value === null) {
@@ -241,7 +254,8 @@  discard block
 block discarded – undo
241 254
 	 *
242 255
 	 * @return string
243 256
 	 */
244
-	public function getUserPreference(User $user, $setting_name, $default = null) {
257
+	public function getUserPreference(User $user, $setting_name, $default = null)
258
+	{
245 259
 		// There are lots of settings, and we need to fetch lots of them on every page
246 260
 		// so it is quicker to fetch them all in one go.
247 261
 		if (!array_key_exists($user->getUserId(), $this->user_preferences)) {
@@ -266,7 +280,8 @@  discard block
 block discarded – undo
266 280
 	 *
267 281
 	 * @return $this
268 282
 	 */
269
-	public function setUserPreference(User $user, $setting_name, $setting_value) {
283
+	public function setUserPreference(User $user, $setting_name, $setting_value)
284
+	{
270 285
 		if ($this->getUserPreference($user, $setting_name) !== $setting_value) {
271 286
 			// Update the database
272 287
 			if ($setting_value === null) {
@@ -303,7 +318,8 @@  discard block
 block discarded – undo
303 318
 	 *
304 319
 	 * @return bool
305 320
 	 */
306
-	public function canAcceptChanges(User $user) {
321
+	public function canAcceptChanges(User $user)
322
+	{
307 323
 		return Auth::isModerator($this, $user);
308 324
 	}
309 325
 
@@ -312,7 +328,8 @@  discard block
 block discarded – undo
312 328
 	 *
313 329
 	 * @return Tree[]
314 330
 	 */
315
-	public static function getAll() {
331
+	public static function getAll()
332
+	{
316 333
 		if (self::$trees === null) {
317 334
 			self::$trees = array();
318 335
 			$rows        = Database::prepare(
@@ -350,7 +367,8 @@  discard block
 block discarded – undo
350 367
 	 *
351 368
 	 * @return Tree
352 369
 	 */
353
-	public static function findById($tree_id) {
370
+	public static function findById($tree_id)
371
+	{
354 372
 		foreach (self::getAll() as $tree) {
355 373
 			if ($tree->tree_id == $tree_id) {
356 374
 				return $tree;
@@ -366,7 +384,8 @@  discard block
 block discarded – undo
366 384
 	 *
367 385
 	 * @return Tree|null
368 386
 	 */
369
-	public static function findByName($tree_name) {
387
+	public static function findByName($tree_name)
388
+	{
370 389
 		foreach (self::getAll() as $tree) {
371 390
 			if ($tree->name === $tree_name) {
372 391
 				return $tree;
@@ -382,7 +401,8 @@  discard block
 block discarded – undo
382 401
 	 *
383 402
 	 * @return string[]
384 403
 	 */
385
-	public static function getIdList() {
404
+	public static function getIdList()
405
+	{
386 406
 		$list = array();
387 407
 		foreach (self::getAll() as $tree) {
388 408
 			$list[$tree->tree_id] = $tree->title;
@@ -397,7 +417,8 @@  discard block
 block discarded – undo
397 417
 	 *
398 418
 	 * @return string[]
399 419
 	 */
400
-	public static function getNameList() {
420
+	public static function getNameList()
421
+	{
401 422
 		$list = array();
402 423
 		foreach (self::getAll() as $tree) {
403 424
 			$list[$tree->name] = $tree->title;
@@ -414,7 +435,8 @@  discard block
 block discarded – undo
414 435
 	 *
415 436
 	 * @return Tree
416 437
 	 */
417
-	public static function create($tree_name, $tree_title) {
438
+	public static function create($tree_name, $tree_title)
439
+	{
418 440
 		try {
419 441
 			// Create a new tree
420 442
 			Database::prepare(
@@ -508,7 +530,8 @@  discard block
 block discarded – undo
508 530
 	 *
509 531
 	 * @return bool
510 532
 	 */
511
-	public function hasPendingEdit() {
533
+	public function hasPendingEdit()
534
+	{
512 535
 		return (bool) Database::prepare(
513 536
 			"SELECT 1 FROM `##change` WHERE status = 'pending' AND gedcom_id = :tree_id"
514 537
 		)->execute(array(
@@ -524,7 +547,8 @@  discard block
 block discarded – undo
524 547
 	 *
525 548
 	 * @param bool $keep_media
526 549
 	 */
527
-	public function deleteGenealogyData($keep_media) {
550
+	public function deleteGenealogyData($keep_media)
551
+	{
528 552
 		Database::prepare("DELETE FROM `##gedcom_chunk` WHERE gedcom_id = ?")->execute(array($this->tree_id));
529 553
 		Database::prepare("DELETE FROM `##individuals`  WHERE i_file    = ?")->execute(array($this->tree_id));
530 554
 		Database::prepare("DELETE FROM `##families`     WHERE f_file    = ?")->execute(array($this->tree_id));
@@ -547,7 +571,8 @@  discard block
 block discarded – undo
547 571
 	/**
548 572
 	 * Delete everything relating to a tree
549 573
 	 */
550
-	public function delete() {
574
+	public function delete()
575
+	{
551 576
 		// If this is the default tree, then unset it
552 577
 		if (Site::getPreference('DEFAULT_GEDCOM') === $this->name) {
553 578
 			Site::setPreference('DEFAULT_GEDCOM', '');
@@ -576,7 +601,8 @@  discard block
 block discarded – undo
576 601
 	 *
577 602
 	 * @param resource $stream
578 603
 	 */
579
-	public function exportGedcom($stream) {
604
+	public function exportGedcom($stream)
605
+	{
580 606
 		$stmt = Database::prepare(
581 607
 			"SELECT i_gedcom AS gedcom, i_id AS xref, 1 AS n FROM `##individuals` WHERE i_file = :tree_id_1" .
582 608
 			" UNION ALL " .
@@ -616,7 +642,8 @@  discard block
 block discarded – undo
616 642
 	 *
617 643
 	 * @throws \Exception
618 644
 	 */
619
-	public function importGedcomFile($path, $filename) {
645
+	public function importGedcomFile($path, $filename)
646
+	{
620 647
 		// Read the file in blocks of roughly 64K. Ensure that each block
621 648
 		// contains complete gedcom records. This will ensure we don’t split
622 649
 		// multi-byte characters, as well as simplifying the code to import
@@ -664,7 +691,8 @@  discard block
 block discarded – undo
664 691
 	 *
665 692
 	 * @return string
666 693
 	 */
667
-	public function getNewXref($type = 'INDI') {
694
+	public function getNewXref($type = 'INDI')
695
+	{
668 696
 		/** @var string[] Which tree preference is used for which record type */
669 697
 		static $type_to_preference = array(
670 698
 			'INDI' => 'GEDCOM_ID_PREFIX',
@@ -747,7 +775,8 @@  discard block
 block discarded – undo
747 775
 	 *
748 776
 	 * @return GedcomRecord
749 777
 	 */
750
-	public function createRecord($gedcom) {
778
+	public function createRecord($gedcom)
779
+	{
751 780
 		if (preg_match('/^0 @(' . WT_REGEX_XREF . ')@ (' . WT_REGEX_TAG . ')/', $gedcom, $match)) {
752 781
 			$xref = $match[1];
753 782
 			$type = $match[2];
Please login to merge, or discard this patch.
app/SurnameTradition/SpanishSurnameTradition.php 3 patches
Indentation   +65 added lines, -65 removed lines patch added patch discarded remove patch
@@ -23,74 +23,74 @@
 block discarded – undo
23 23
  * Child:  Pablo /CCCC/ /AAAA/
24 24
  */
25 25
 class SpanishSurnameTradition extends DefaultSurnameTradition implements SurnameTraditionInterface {
26
-	/**
27
-	 * What names are given to a new child
28
-	 *
29
-	 * @param string $father_name A GEDCOM NAME
30
-	 * @param string $mother_name A GEDCOM NAME
31
-	 * @param string $child_sex   M, F or U
32
-	 *
33
-	 * @return string[] Associative array of GEDCOM name parts (SURN, _MARNM, etc.)
34
-	 */
35
-	public function newChildNames($father_name, $mother_name, $child_sex) {
36
-		if (preg_match(self::REGEX_SURNS, $father_name, $match_father)) {
37
-			$father_surname = $match_father['SURN1'];
38
-		} else {
39
-			$father_surname = '';
40
-		}
26
+    /**
27
+     * What names are given to a new child
28
+     *
29
+     * @param string $father_name A GEDCOM NAME
30
+     * @param string $mother_name A GEDCOM NAME
31
+     * @param string $child_sex   M, F or U
32
+     *
33
+     * @return string[] Associative array of GEDCOM name parts (SURN, _MARNM, etc.)
34
+     */
35
+    public function newChildNames($father_name, $mother_name, $child_sex) {
36
+        if (preg_match(self::REGEX_SURNS, $father_name, $match_father)) {
37
+            $father_surname = $match_father['SURN1'];
38
+        } else {
39
+            $father_surname = '';
40
+        }
41 41
 
42
-		if (preg_match(self::REGEX_SURNS, $mother_name, $match_mother)) {
43
-			$mother_surname = $match_mother['SURN1'];
44
-		} else {
45
-			$mother_surname = '';
46
-		}
42
+        if (preg_match(self::REGEX_SURNS, $mother_name, $match_mother)) {
43
+            $mother_surname = $match_mother['SURN1'];
44
+        } else {
45
+            $mother_surname = '';
46
+        }
47 47
 
48
-		return array(
49
-			'NAME' => '/' . $father_surname . '/ /' . $mother_surname . '/',
50
-			'SURN' => trim($father_surname . ',' . $mother_surname, ','),
51
-		);
52
-	}
48
+        return array(
49
+            'NAME' => '/' . $father_surname . '/ /' . $mother_surname . '/',
50
+            'SURN' => trim($father_surname . ',' . $mother_surname, ','),
51
+        );
52
+    }
53 53
 
54
-	/**
55
-	 * What names are given to a new parent
56
-	 *
57
-	 * @param string $child_name A GEDCOM NAME
58
-	 * @param string $parent_sex M, F or U
59
-	 *
60
-	 * @return string[] Associative array of GEDCOM name parts (SURN, _MARNM, etc.)
61
-	 */
62
-	public function newParentNames($child_name, $parent_sex) {
63
-		if (preg_match(self::REGEX_SURNS, $child_name, $match)) {
64
-			switch ($parent_sex) {
65
-			case 'M':
66
-				return array(
67
-					'NAME' => '/' . $match['SURN1'] . '/ //',
68
-					'SURN' => $match['SURN1'],
69
-				);
70
-			case 'F':
71
-				return array(
72
-					'NAME' => '/' . $match['SURN2'] . '/ //',
73
-					'SURN' => $match['SURN2'],
74
-				);
75
-			}
76
-		}
54
+    /**
55
+     * What names are given to a new parent
56
+     *
57
+     * @param string $child_name A GEDCOM NAME
58
+     * @param string $parent_sex M, F or U
59
+     *
60
+     * @return string[] Associative array of GEDCOM name parts (SURN, _MARNM, etc.)
61
+     */
62
+    public function newParentNames($child_name, $parent_sex) {
63
+        if (preg_match(self::REGEX_SURNS, $child_name, $match)) {
64
+            switch ($parent_sex) {
65
+            case 'M':
66
+                return array(
67
+                    'NAME' => '/' . $match['SURN1'] . '/ //',
68
+                    'SURN' => $match['SURN1'],
69
+                );
70
+            case 'F':
71
+                return array(
72
+                    'NAME' => '/' . $match['SURN2'] . '/ //',
73
+                    'SURN' => $match['SURN2'],
74
+                );
75
+            }
76
+        }
77 77
 
78
-		return array(
79
-			'NAME' => '// //',
80
-		);
81
-	}
78
+        return array(
79
+            'NAME' => '// //',
80
+        );
81
+    }
82 82
 
83
-	/**
84
-	 * What names are given to a new spouse
85
-	 *
86
-	 * @param string $spouse_name A GEDCOM NAME
87
-	 * @param string $spouse_sex  M, F or U
88
-	 *
89
-	 * @return string[] Associative array of GEDCOM name parts (SURN, _MARNM, etc.)
90
-	 */
91
-	public function newSpouseNames($spouse_name, $spouse_sex) {
92
-		return array(
93
-			'NAME' => '// //',
94
-		);
95
-	}
83
+    /**
84
+     * What names are given to a new spouse
85
+     *
86
+     * @param string $spouse_name A GEDCOM NAME
87
+     * @param string $spouse_sex  M, F or U
88
+     *
89
+     * @return string[] Associative array of GEDCOM name parts (SURN, _MARNM, etc.)
90
+     */
91
+    public function newSpouseNames($spouse_name, $spouse_sex) {
92
+        return array(
93
+            'NAME' => '// //',
94
+        );
95
+    }
96 96
 }
Please login to merge, or discard this patch.
Switch Indentation   +10 added lines, -10 removed lines patch added patch discarded remove patch
@@ -62,16 +62,16 @@
 block discarded – undo
62 62
 	public function newParentNames($child_name, $parent_sex) {
63 63
 		if (preg_match(self::REGEX_SURNS, $child_name, $match)) {
64 64
 			switch ($parent_sex) {
65
-			case 'M':
66
-				return array(
67
-					'NAME' => '/' . $match['SURN1'] . '/ //',
68
-					'SURN' => $match['SURN1'],
69
-				);
70
-			case 'F':
71
-				return array(
72
-					'NAME' => '/' . $match['SURN2'] . '/ //',
73
-					'SURN' => $match['SURN2'],
74
-				);
65
+			    case 'M':
66
+				    return array(
67
+					    'NAME' => '/' . $match['SURN1'] . '/ //',
68
+					    'SURN' => $match['SURN1'],
69
+				    );
70
+			    case 'F':
71
+				    return array(
72
+					    'NAME' => '/' . $match['SURN2'] . '/ //',
73
+					    'SURN' => $match['SURN2'],
74
+				    );
75 75
 			}
76 76
 		}
77 77
 
Please login to merge, or discard this patch.
Braces   +8 added lines, -4 removed lines patch added patch discarded remove patch
@@ -22,7 +22,8 @@  discard block
 block discarded – undo
22 22
  * Father: Jose  /CCCC/ /DDDD/
23 23
  * Child:  Pablo /CCCC/ /AAAA/
24 24
  */
25
-class SpanishSurnameTradition extends DefaultSurnameTradition implements SurnameTraditionInterface {
25
+class SpanishSurnameTradition extends DefaultSurnameTradition implements SurnameTraditionInterface
26
+{
26 27
 	/**
27 28
 	 * What names are given to a new child
28 29
 	 *
@@ -32,7 +33,8 @@  discard block
 block discarded – undo
32 33
 	 *
33 34
 	 * @return string[] Associative array of GEDCOM name parts (SURN, _MARNM, etc.)
34 35
 	 */
35
-	public function newChildNames($father_name, $mother_name, $child_sex) {
36
+	public function newChildNames($father_name, $mother_name, $child_sex)
37
+	{
36 38
 		if (preg_match(self::REGEX_SURNS, $father_name, $match_father)) {
37 39
 			$father_surname = $match_father['SURN1'];
38 40
 		} else {
@@ -59,7 +61,8 @@  discard block
 block discarded – undo
59 61
 	 *
60 62
 	 * @return string[] Associative array of GEDCOM name parts (SURN, _MARNM, etc.)
61 63
 	 */
62
-	public function newParentNames($child_name, $parent_sex) {
64
+	public function newParentNames($child_name, $parent_sex)
65
+	{
63 66
 		if (preg_match(self::REGEX_SURNS, $child_name, $match)) {
64 67
 			switch ($parent_sex) {
65 68
 			case 'M':
@@ -88,7 +91,8 @@  discard block
 block discarded – undo
88 91
 	 *
89 92
 	 * @return string[] Associative array of GEDCOM name parts (SURN, _MARNM, etc.)
90 93
 	 */
91
-	public function newSpouseNames($spouse_name, $spouse_sex) {
94
+	public function newSpouseNames($spouse_name, $spouse_sex)
95
+	{
92 96
 		return array(
93 97
 			'NAME' => '// //',
94 98
 		);
Please login to merge, or discard this patch.