Completed
Push — 1.7 ( ce7091...0d97cf )
by Greg
14:14 queued 07:41
created
app/Functions/FunctionsPrintLists.php 1 patch
Indentation   +1341 added lines, -1341 removed lines patch added patch discarded remove patch
@@ -37,49 +37,49 @@  discard block
 block discarded – undo
37 37
  * Class FunctionsPrintLists - create sortable lists using datatables.net
38 38
  */
39 39
 class FunctionsPrintLists {
40
-	/**
41
-	 * Generate a SURN,GIVN and GIVN,SURN sortable name for an individual.
42
-	 * This allows table data to sort by surname or given names.
43
-	 *
44
-	 * Use AAAA as a separator (instead of ","), as Javascript localeCompare()
45
-	 * ignores punctuation and "ANN,ROACH" would sort after "ANNE,ROACH",
46
-	 * instead of before it.
47
-	 *
48
-	 * @param Individual $individual
49
-	 *
50
-	 * @return string[]
51
-	 */
52
-	private static function sortableNames(Individual $individual) {
53
-		$names   = $individual->getAllNames();
54
-		$primary = $individual->getPrimaryName();
55
-
56
-		list($surn, $givn) = explode(',', $names[$primary]['sort']);
57
-
58
-		$givn = str_replace('@P.N.', 'AAAA', $givn);
59
-		$surn = str_replace('@N.N.', 'AAAA', $surn);
60
-
61
-		return array(
62
-			$surn . 'AAAA' . $givn,
63
-			$givn . 'AAAA' . $surn,
64
-		);
65
-	}
66
-
67
-	/**
68
-	 * Print a table of individuals
69
-	 *
70
-	 * @param Individual[] $indiviudals
71
-	 * @param string       $option
72
-	 *
73
-	 * @return string
74
-	 */
75
-	public static function individualTable($indiviudals, $option = '') {
76
-		global $controller, $WT_TREE;
77
-
78
-		$table_id = 'table-indi-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page
79
-
80
-		$controller
81
-			->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
82
-			->addInlineJavascript('
40
+    /**
41
+     * Generate a SURN,GIVN and GIVN,SURN sortable name for an individual.
42
+     * This allows table data to sort by surname or given names.
43
+     *
44
+     * Use AAAA as a separator (instead of ","), as Javascript localeCompare()
45
+     * ignores punctuation and "ANN,ROACH" would sort after "ANNE,ROACH",
46
+     * instead of before it.
47
+     *
48
+     * @param Individual $individual
49
+     *
50
+     * @return string[]
51
+     */
52
+    private static function sortableNames(Individual $individual) {
53
+        $names   = $individual->getAllNames();
54
+        $primary = $individual->getPrimaryName();
55
+
56
+        list($surn, $givn) = explode(',', $names[$primary]['sort']);
57
+
58
+        $givn = str_replace('@P.N.', 'AAAA', $givn);
59
+        $surn = str_replace('@N.N.', 'AAAA', $surn);
60
+
61
+        return array(
62
+            $surn . 'AAAA' . $givn,
63
+            $givn . 'AAAA' . $surn,
64
+        );
65
+    }
66
+
67
+    /**
68
+     * Print a table of individuals
69
+     *
70
+     * @param Individual[] $indiviudals
71
+     * @param string       $option
72
+     *
73
+     * @return string
74
+     */
75
+    public static function individualTable($indiviudals, $option = '') {
76
+        global $controller, $WT_TREE;
77
+
78
+        $table_id = 'table-indi-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page
79
+
80
+        $controller
81
+            ->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
82
+            ->addInlineJavascript('
83 83
 				jQuery.fn.dataTableExt.oSort["text-asc"] = textCompareAsc;
84 84
 				jQuery.fn.dataTableExt.oSort["text-desc"] = textCompareDesc;
85 85
 				jQuery("#' . $table_id . '").dataTable( {
@@ -143,21 +143,21 @@  discard block
 block discarded – undo
143 143
 				jQuery(".loading-image").css("display", "none");
144 144
 			');
145 145
 
146
-		$max_age = (int) $WT_TREE->getPreference('MAX_ALIVE_AGE');
147
-
148
-		// Inititialise chart data
149
-		$deat_by_age = array();
150
-		for ($age = 0; $age <= $max_age; $age++) {
151
-			$deat_by_age[$age] = '';
152
-		}
153
-		$birt_by_decade = array();
154
-		$deat_by_decade = array();
155
-		for ($year = 1550; $year < 2030; $year += 10) {
156
-			$birt_by_decade[$year] = '';
157
-			$deat_by_decade[$year] = '';
158
-		}
159
-
160
-		$html = '
146
+        $max_age = (int) $WT_TREE->getPreference('MAX_ALIVE_AGE');
147
+
148
+        // Inititialise chart data
149
+        $deat_by_age = array();
150
+        for ($age = 0; $age <= $max_age; $age++) {
151
+            $deat_by_age[$age] = '';
152
+        }
153
+        $birt_by_decade = array();
154
+        $deat_by_decade = array();
155
+        for ($year = 1550; $year < 2030; $year += 10) {
156
+            $birt_by_decade[$year] = '';
157
+            $deat_by_decade[$year] = '';
158
+        }
159
+
160
+        $html = '
161 161
 			<div class="loading-image"></div>
162 162
 			<div class="indi-list">
163 163
 				<table id="' . $table_id . '">
@@ -312,175 +312,175 @@  discard block
 block discarded – undo
312 312
 					</tfoot>
313 313
 					<tbody>';
314 314
 
315
-		$hundred_years_ago = new Date(date('Y') - 100);
316
-		$unique_indis      = array(); // Don't double-count indis with multiple names.
317
-
318
-		foreach ($indiviudals as $key => $individual) {
319
-			if (!$individual->canShowName()) {
320
-				continue;
321
-			}
322
-			if ($individual->isPendingAddtion()) {
323
-				$class = ' class="new"';
324
-			} elseif ($individual->isPendingDeletion()) {
325
-				$class = ' class="old"';
326
-			} else {
327
-				$class = '';
328
-			}
329
-			$html .= '<tr' . $class . '>';
330
-			// Extract Given names and Surnames for sorting
331
-			list($surn_givn, $givn_surn) = self::sortableNames($individual);
332
-
333
-			$html .= '<td colspan="2" data-sort="' . Filter::escapeHtml($givn_surn) . '">';
334
-			foreach ($individual->getAllNames() as $num => $name) {
335
-				if ($name['type'] == 'NAME') {
336
-					$title = '';
337
-				} else {
338
-					$title = 'title="' . strip_tags(GedcomTag::getLabel($name['type'], $individual)) . '"';
339
-				}
340
-				if ($num == $individual->getPrimaryName()) {
341
-					$class             = ' class="name2"';
342
-					$sex_image         = $individual->getSexImage();
343
-				} else {
344
-					$class     = '';
345
-					$sex_image = '';
346
-				}
347
-				$html .= '<a ' . $title . ' href="' . $individual->getHtmlUrl() . '"' . $class . '>' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>' . $sex_image . '<br>';
348
-			}
349
-			$html .= $individual->getPrimaryParentsNames('parents details1', 'none');
350
-			$html .= '</td>';
351
-
352
-			// Hidden column for sortable name
353
-			$html .= '<td hidden data-sort="' . Filter::escapeHtml($surn_givn) . '"></td>';
354
-
355
-			// SOSA
356
-			$html .= '<td class="center" data-sort="' . $key . '">';
357
-			if ($option === 'sosa') {
358
-				$html .= '<a href="relationship.php?pid1=' . $indiviudals[1] . '&amp;pid2=' . $individual->getXref() . '" title="' . I18N::translate('Relationships') . '">' . I18N::number($key) . '</a>';
359
-			}
360
-			$html .= '</td>';
361
-
362
-			// Birth date
363
-			$birth_dates = $individual->getAllBirthDates();
364
-			$html .= '<td data-sort="' . $individual->getEstimatedBirthDate()->julianDay() . '">';
365
-			foreach ($birth_dates as $n => $birth_date) {
366
-				if ($n > 0) {
367
-					$html .= '<br>';
368
-				}
369
-				$html .= $birth_date->display(true);
370
-			}
371
-			$html .= '</td>';
372
-
373
-			// Birth anniversary
374
-			if (isset($birth_dates[0]) && $birth_dates[0]->gregorianYear() >= 1550 && $birth_dates[0]->gregorianYear() < 2030 && !isset($unique_indis[$individual->getXref()])) {
375
-				$birt_by_decade[(int) ($birth_dates[0]->gregorianYear() / 10) * 10] .= $individual->getSex();
376
-				$anniversary = Date::getAge($birth_dates[0], null, 2);
377
-			} else {
378
-				$anniversary = '';
379
-			}
380
-			$html .= '<td class="center" data-sort="' . -$individual->getEstimatedBirthDate()->julianDay() . '">' . $anniversary . '</td>';
381
-
382
-			// Birth place
383
-			$html .= '<td>';
384
-			foreach ($individual->getAllBirthPlaces() as $n => $birth_place) {
385
-				$tmp = new Place($birth_place, $individual->getTree());
386
-				if ($n > 0) {
387
-					$html .= '<br>';
388
-				}
389
-				$html .= '<a href="' . $tmp->getURL() . '" title="' . strip_tags($tmp->getFullName()) . '">';
390
-				$html .= FunctionsPrint::highlightSearchHits($tmp->getShortName()) . '</a>';
391
-			}
392
-			$html .= '</td>';
393
-
394
-			// Number of children
395
-			$number_of_children = $individual->getNumberOfChildren();
396
-			$html .= '<td class="center" data-sort="' . $number_of_children . '">' . I18N::number($number_of_children) . '</td>';
397
-
398
-			// Death date
399
-			$death_dates = $individual->getAllDeathDates();
400
-			$html .= '<td data-sort="' . $individual->getEstimatedDeathDate()->julianDay() . '">';
401
-			foreach ($death_dates as $num => $death_date) {
402
-				if ($num) {
403
-					$html .= '<br>';
404
-				}
405
-				$html .= $death_date->display(true);
406
-			}
407
-			$html .= '</td>';
408
-
409
-			// Death anniversary
410
-			if (isset($death_dates[0]) && $death_dates[0]->gregorianYear() >= 1550 && $death_dates[0]->gregorianYear() < 2030 && !isset($unique_indis[$individual->getXref()])) {
411
-				$birt_by_decade[(int) ($death_dates[0]->gregorianYear() / 10) * 10] .= $individual->getSex();
412
-				$anniversary = Date::getAge($death_dates[0], null, 2);
413
-			} else {
414
-				$anniversary = '';
415
-			}
416
-			$html .= '<td class="center" data-sort="' . -$individual->getEstimatedDeathDate()->julianDay() . '">' . $anniversary . '</td>';
417
-
418
-			// Age at death
419
-			if (isset($birth_dates[0]) && isset($death_dates[0])) {
420
-				$age_at_death      = Date::getAge($birth_dates[0], $death_dates[0], 0);
421
-				$age_at_death_sort = Date::getAge($birth_dates[0], $death_dates[0], 2);
422
-				if (!isset($unique_indis[$individual->getXref()]) && $age >= 0 && $age <= $max_age) {
423
-					$deat_by_age[$age_at_death] .= $individual->getSex();
424
-				}
425
-			} else {
426
-				$age_at_death      = '';
427
-				$age_at_death_sort = PHP_INT_MAX;
428
-			}
429
-			$html .= '<td class="center" data-sort="' . $age_at_death_sort . '">' . $age_at_death . '</td>';
430
-
431
-			// Death place
432
-			$html .= '<td>';
433
-			foreach ($individual->getAllDeathPlaces() as $n => $death_place) {
434
-				$tmp = new Place($death_place, $individual->getTree());
435
-				if ($n > 0) {
436
-					$html .= '<br>';
437
-				}
438
-				$html .= '<a href="' . $tmp->getURL() . '" title="' . strip_tags($tmp->getFullName()) . '">';
439
-				$html .= FunctionsPrint::highlightSearchHits($tmp->getShortName()) . '</a>';
440
-			}
441
-			$html .= '</td>';
442
-
443
-			// Last change
444
-			$html .= '<td data-sort="' . $individual->lastChangeTimestamp(true) . '">' . $individual->lastChangeTimestamp() . '</td>';
445
-
446
-			// Filter by sex
447
-			$html .= '<td hidden>' . $individual->getSex() . '</td>';
448
-
449
-			// Filter by birth date
450
-			$html .= '<td hidden>';
451
-			if (!$individual->canShow() || Date::compare($individual->getEstimatedBirthDate(), $hundred_years_ago) > 0) {
452
-				$html .= 'Y100';
453
-			} else {
454
-				$html .= 'YES';
455
-			}
456
-			$html .= '</td>';
457
-
458
-			// Filter by death date
459
-			$html .= '<td hidden>';
460
-			// Died in last 100 years? Died? Not dead?
461
-			if (isset($death_dates[0]) && Date::compare($death_dates[0], $hundred_years_ago) > 0) {
462
-				$html .= 'Y100';
463
-			} elseif ($individual->isDead()) {
464
-				$html .= 'YES';
465
-			} else {
466
-				$html .= 'N';
467
-			}
468
-			$html .= '</td>';
469
-
470
-			// Filter by roots/leaves
471
-			$html .= '<td hidden>';
472
-			if (!$individual->getChildFamilies()) {
473
-				$html .= 'R';
474
-			} elseif (!$individual->isDead() && $individual->getNumberOfChildren() < 1) {
475
-				$html .= 'L';
476
-				$html .= '&nbsp;';
477
-			}
478
-			$html .= '</td>';
479
-			$html .= '</tr>';
480
-
481
-			$unique_indis[$individual->getXref()] = true;
482
-		}
483
-		$html .= '
315
+        $hundred_years_ago = new Date(date('Y') - 100);
316
+        $unique_indis      = array(); // Don't double-count indis with multiple names.
317
+
318
+        foreach ($indiviudals as $key => $individual) {
319
+            if (!$individual->canShowName()) {
320
+                continue;
321
+            }
322
+            if ($individual->isPendingAddtion()) {
323
+                $class = ' class="new"';
324
+            } elseif ($individual->isPendingDeletion()) {
325
+                $class = ' class="old"';
326
+            } else {
327
+                $class = '';
328
+            }
329
+            $html .= '<tr' . $class . '>';
330
+            // Extract Given names and Surnames for sorting
331
+            list($surn_givn, $givn_surn) = self::sortableNames($individual);
332
+
333
+            $html .= '<td colspan="2" data-sort="' . Filter::escapeHtml($givn_surn) . '">';
334
+            foreach ($individual->getAllNames() as $num => $name) {
335
+                if ($name['type'] == 'NAME') {
336
+                    $title = '';
337
+                } else {
338
+                    $title = 'title="' . strip_tags(GedcomTag::getLabel($name['type'], $individual)) . '"';
339
+                }
340
+                if ($num == $individual->getPrimaryName()) {
341
+                    $class             = ' class="name2"';
342
+                    $sex_image         = $individual->getSexImage();
343
+                } else {
344
+                    $class     = '';
345
+                    $sex_image = '';
346
+                }
347
+                $html .= '<a ' . $title . ' href="' . $individual->getHtmlUrl() . '"' . $class . '>' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>' . $sex_image . '<br>';
348
+            }
349
+            $html .= $individual->getPrimaryParentsNames('parents details1', 'none');
350
+            $html .= '</td>';
351
+
352
+            // Hidden column for sortable name
353
+            $html .= '<td hidden data-sort="' . Filter::escapeHtml($surn_givn) . '"></td>';
354
+
355
+            // SOSA
356
+            $html .= '<td class="center" data-sort="' . $key . '">';
357
+            if ($option === 'sosa') {
358
+                $html .= '<a href="relationship.php?pid1=' . $indiviudals[1] . '&amp;pid2=' . $individual->getXref() . '" title="' . I18N::translate('Relationships') . '">' . I18N::number($key) . '</a>';
359
+            }
360
+            $html .= '</td>';
361
+
362
+            // Birth date
363
+            $birth_dates = $individual->getAllBirthDates();
364
+            $html .= '<td data-sort="' . $individual->getEstimatedBirthDate()->julianDay() . '">';
365
+            foreach ($birth_dates as $n => $birth_date) {
366
+                if ($n > 0) {
367
+                    $html .= '<br>';
368
+                }
369
+                $html .= $birth_date->display(true);
370
+            }
371
+            $html .= '</td>';
372
+
373
+            // Birth anniversary
374
+            if (isset($birth_dates[0]) && $birth_dates[0]->gregorianYear() >= 1550 && $birth_dates[0]->gregorianYear() < 2030 && !isset($unique_indis[$individual->getXref()])) {
375
+                $birt_by_decade[(int) ($birth_dates[0]->gregorianYear() / 10) * 10] .= $individual->getSex();
376
+                $anniversary = Date::getAge($birth_dates[0], null, 2);
377
+            } else {
378
+                $anniversary = '';
379
+            }
380
+            $html .= '<td class="center" data-sort="' . -$individual->getEstimatedBirthDate()->julianDay() . '">' . $anniversary . '</td>';
381
+
382
+            // Birth place
383
+            $html .= '<td>';
384
+            foreach ($individual->getAllBirthPlaces() as $n => $birth_place) {
385
+                $tmp = new Place($birth_place, $individual->getTree());
386
+                if ($n > 0) {
387
+                    $html .= '<br>';
388
+                }
389
+                $html .= '<a href="' . $tmp->getURL() . '" title="' . strip_tags($tmp->getFullName()) . '">';
390
+                $html .= FunctionsPrint::highlightSearchHits($tmp->getShortName()) . '</a>';
391
+            }
392
+            $html .= '</td>';
393
+
394
+            // Number of children
395
+            $number_of_children = $individual->getNumberOfChildren();
396
+            $html .= '<td class="center" data-sort="' . $number_of_children . '">' . I18N::number($number_of_children) . '</td>';
397
+
398
+            // Death date
399
+            $death_dates = $individual->getAllDeathDates();
400
+            $html .= '<td data-sort="' . $individual->getEstimatedDeathDate()->julianDay() . '">';
401
+            foreach ($death_dates as $num => $death_date) {
402
+                if ($num) {
403
+                    $html .= '<br>';
404
+                }
405
+                $html .= $death_date->display(true);
406
+            }
407
+            $html .= '</td>';
408
+
409
+            // Death anniversary
410
+            if (isset($death_dates[0]) && $death_dates[0]->gregorianYear() >= 1550 && $death_dates[0]->gregorianYear() < 2030 && !isset($unique_indis[$individual->getXref()])) {
411
+                $birt_by_decade[(int) ($death_dates[0]->gregorianYear() / 10) * 10] .= $individual->getSex();
412
+                $anniversary = Date::getAge($death_dates[0], null, 2);
413
+            } else {
414
+                $anniversary = '';
415
+            }
416
+            $html .= '<td class="center" data-sort="' . -$individual->getEstimatedDeathDate()->julianDay() . '">' . $anniversary . '</td>';
417
+
418
+            // Age at death
419
+            if (isset($birth_dates[0]) && isset($death_dates[0])) {
420
+                $age_at_death      = Date::getAge($birth_dates[0], $death_dates[0], 0);
421
+                $age_at_death_sort = Date::getAge($birth_dates[0], $death_dates[0], 2);
422
+                if (!isset($unique_indis[$individual->getXref()]) && $age >= 0 && $age <= $max_age) {
423
+                    $deat_by_age[$age_at_death] .= $individual->getSex();
424
+                }
425
+            } else {
426
+                $age_at_death      = '';
427
+                $age_at_death_sort = PHP_INT_MAX;
428
+            }
429
+            $html .= '<td class="center" data-sort="' . $age_at_death_sort . '">' . $age_at_death . '</td>';
430
+
431
+            // Death place
432
+            $html .= '<td>';
433
+            foreach ($individual->getAllDeathPlaces() as $n => $death_place) {
434
+                $tmp = new Place($death_place, $individual->getTree());
435
+                if ($n > 0) {
436
+                    $html .= '<br>';
437
+                }
438
+                $html .= '<a href="' . $tmp->getURL() . '" title="' . strip_tags($tmp->getFullName()) . '">';
439
+                $html .= FunctionsPrint::highlightSearchHits($tmp->getShortName()) . '</a>';
440
+            }
441
+            $html .= '</td>';
442
+
443
+            // Last change
444
+            $html .= '<td data-sort="' . $individual->lastChangeTimestamp(true) . '">' . $individual->lastChangeTimestamp() . '</td>';
445
+
446
+            // Filter by sex
447
+            $html .= '<td hidden>' . $individual->getSex() . '</td>';
448
+
449
+            // Filter by birth date
450
+            $html .= '<td hidden>';
451
+            if (!$individual->canShow() || Date::compare($individual->getEstimatedBirthDate(), $hundred_years_ago) > 0) {
452
+                $html .= 'Y100';
453
+            } else {
454
+                $html .= 'YES';
455
+            }
456
+            $html .= '</td>';
457
+
458
+            // Filter by death date
459
+            $html .= '<td hidden>';
460
+            // Died in last 100 years? Died? Not dead?
461
+            if (isset($death_dates[0]) && Date::compare($death_dates[0], $hundred_years_ago) > 0) {
462
+                $html .= 'Y100';
463
+            } elseif ($individual->isDead()) {
464
+                $html .= 'YES';
465
+            } else {
466
+                $html .= 'N';
467
+            }
468
+            $html .= '</td>';
469
+
470
+            // Filter by roots/leaves
471
+            $html .= '<td hidden>';
472
+            if (!$individual->getChildFamilies()) {
473
+                $html .= 'R';
474
+            } elseif (!$individual->isDead() && $individual->getNumberOfChildren() < 1) {
475
+                $html .= 'L';
476
+                $html .= '&nbsp;';
477
+            }
478
+            $html .= '</td>';
479
+            $html .= '</tr>';
480
+
481
+            $unique_indis[$individual->getXref()] = true;
482
+        }
483
+        $html .= '
484 484
 					</tbody>
485 485
 				</table>
486 486
 				<div id="indi_list_table-charts_' . $table_id . '" style="display:none">
@@ -502,24 +502,24 @@  discard block
 block discarded – undo
502 502
 				</div>
503 503
 			</div>';
504 504
 
505
-		return $html;
506
-	}
505
+        return $html;
506
+    }
507 507
 
508
-	/**
509
-	 * Print a table of families
510
-	 *
511
-	 * @param Family[] $families
512
-	 *
513
-	 * @return string
514
-	 */
515
-	public static function familyTable($families) {
516
-		global $WT_TREE, $controller;
508
+    /**
509
+     * Print a table of families
510
+     *
511
+     * @param Family[] $families
512
+     *
513
+     * @return string
514
+     */
515
+    public static function familyTable($families) {
516
+        global $WT_TREE, $controller;
517 517
 
518
-		$table_id = 'table-fam-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page
518
+        $table_id = 'table-fam-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page
519 519
 
520
-		$controller
521
-			->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
522
-			->addInlineJavascript('
520
+        $controller
521
+            ->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
522
+            ->addInlineJavascript('
523 523
 				jQuery.fn.dataTableExt.oSort["text-asc"] = textCompareAsc;
524 524
 				jQuery.fn.dataTableExt.oSort["text-desc"] = textCompareDesc;
525 525
 				jQuery("#' . $table_id . '").dataTable( {
@@ -581,21 +581,21 @@  discard block
 block discarded – undo
581 581
 				jQuery(".loading-image").css("display", "none");
582 582
 		');
583 583
 
584
-		$max_age = (int) $WT_TREE->getPreference('MAX_ALIVE_AGE');
585
-
586
-		// init chart data
587
-		$marr_by_age = array();
588
-		for ($age = 0; $age <= $max_age; $age++) {
589
-			$marr_by_age[$age] = '';
590
-		}
591
-		$birt_by_decade = array();
592
-		$marr_by_decade = array();
593
-		for ($year = 1550; $year < 2030; $year += 10) {
594
-			$birt_by_decade[$year] = '';
595
-			$marr_by_decade[$year] = '';
596
-		}
597
-
598
-		$html = '
584
+        $max_age = (int) $WT_TREE->getPreference('MAX_ALIVE_AGE');
585
+
586
+        // init chart data
587
+        $marr_by_age = array();
588
+        for ($age = 0; $age <= $max_age; $age++) {
589
+            $marr_by_age[$age] = '';
590
+        }
591
+        $birt_by_decade = array();
592
+        $marr_by_decade = array();
593
+        for ($year = 1550; $year < 2030; $year += 10) {
594
+            $birt_by_decade[$year] = '';
595
+            $marr_by_decade[$year] = '';
596
+        }
597
+
598
+        $html = '
599 599
 			<div class="loading-image"></div>
600 600
 			<div class="fam-list">
601 601
 				<table id="' . $table_id . '">
@@ -746,215 +746,215 @@  discard block
 block discarded – undo
746 746
 					</tfoot>
747 747
 					<tbody>';
748 748
 
749
-		$hundred_years_ago = new Date(date('Y') - 100);
750
-
751
-		foreach ($families as $family) {
752
-			// Retrieve husband and wife
753
-			$husb = $family->getHusband();
754
-			if (is_null($husb)) {
755
-				$husb = new Individual('H', '0 @H@ INDI', null, $family->getTree());
756
-			}
757
-			$wife = $family->getWife();
758
-			if (is_null($wife)) {
759
-				$wife = new Individual('W', '0 @W@ INDI', null, $family->getTree());
760
-			}
761
-			if (!$family->canShow()) {
762
-				continue;
763
-			}
764
-			if ($family->isPendingAddtion()) {
765
-				$class = ' class="new"';
766
-			} elseif ($family->isPendingDeletion()) {
767
-				$class = ' class="old"';
768
-			} else {
769
-				$class = '';
770
-			}
771
-			$html .= '<tr' . $class . '>';
772
-			// Husband name(s)
773
-			// Extract Given names and Surnames for sorting
774
-			list($surn_givn, $givn_surn) = self::sortableNames($husb);
775
-
776
-			$html .= '<td colspan="2" data-sort="' . Filter::escapeHtml($givn_surn) . '">';
777
-			foreach ($husb->getAllNames() as $num => $name) {
778
-				if ($name['type'] == 'NAME') {
779
-					$title = '';
780
-				} else {
781
-					$title = 'title="' . strip_tags(GedcomTag::getLabel($name['type'], $husb)) . '"';
782
-				}
783
-				if ($num == $husb->getPrimaryName()) {
784
-					$class             = ' class="name2"';
785
-					$sex_image         = $husb->getSexImage();
786
-				} else {
787
-					$class     = '';
788
-					$sex_image = '';
789
-				}
790
-				// Only show married names if they are the name we are filtering by.
791
-				if ($name['type'] != '_MARNM' || $num == $husb->getPrimaryName()) {
792
-					$html .= '<a ' . $title . ' href="' . $family->getHtmlUrl() . '"' . $class . '>' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>' . $sex_image . '<br>';
793
-				}
794
-			}
795
-			// Husband parents
796
-			$html .= $husb->getPrimaryParentsNames('parents details1', 'none');
797
-			$html .= '</td>';
798
-
799
-			// Hidden column for sortable name
800
-			$html .= '<td hidden data-sort="' . Filter::escapeHtml($surn_givn) . '"></td>';
801
-
802
-			// Husband age
803
-			$mdate = $family->getMarriageDate();
804
-			$hdate = $husb->getBirthDate();
805
-			if ($hdate->isOK() && $mdate->isOK()) {
806
-				if ($hdate->gregorianYear() >= 1550 && $hdate->gregorianYear() < 2030) {
807
-					$birt_by_decade[(int) ($hdate->gregorianYear() / 10) * 10] .= $husb->getSex();
808
-				}
809
-				$hage = Date::getAge($hdate, $mdate, 0);
810
-				if ($hage >= 0 && $hage <= $max_age) {
811
-					$marr_by_age[$hage] .= $husb->getSex();
812
-				}
813
-			}
814
-			$html .= '<td class="center" data=-sort="' . Date::getAge($hdate, $mdate, 1) . '">' . Date::getAge($hdate, $mdate, 2) . '</td>';
815
-
816
-			// Wife name(s)
817
-			// Extract Given names and Surnames for sorting
818
-			list($surn_givn, $givn_surn) = self::sortableNames($wife);
819
-			$html .= '<td colspan="2" data-sort="' . Filter::escapeHtml($givn_surn) . '">';
820
-			foreach ($wife->getAllNames() as $num => $name) {
821
-				if ($name['type'] == 'NAME') {
822
-					$title = '';
823
-				} else {
824
-					$title = 'title="' . strip_tags(GedcomTag::getLabel($name['type'], $wife)) . '"';
825
-				}
826
-				if ($num == $wife->getPrimaryName()) {
827
-					$class             = ' class="name2"';
828
-					$sex_image         = $wife->getSexImage();
829
-				} else {
830
-					$class     = '';
831
-					$sex_image = '';
832
-				}
833
-				// Only show married names if they are the name we are filtering by.
834
-				if ($name['type'] != '_MARNM' || $num == $wife->getPrimaryName()) {
835
-					$html .= '<a ' . $title . ' href="' . $family->getHtmlUrl() . '"' . $class . '>' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>' . $sex_image . '<br>';
836
-				}
837
-			}
838
-			// Wife parents
839
-			$html .= $wife->getPrimaryParentsNames('parents details1', 'none');
840
-			$html .= '</td>';
841
-
842
-			// Hidden column for sortable name
843
-			$html .= '<td hidden data-sort="' . Filter::escapeHtml($surn_givn) . '"></td>';
844
-
845
-			// Wife age
846
-			$mdate = $family->getMarriageDate();
847
-			$wdate = $wife->getBirthDate();
848
-			if ($wdate->isOK() && $mdate->isOK()) {
849
-				if ($wdate->gregorianYear() >= 1550 && $wdate->gregorianYear() < 2030) {
850
-					$birt_by_decade[(int) ($wdate->gregorianYear() / 10) * 10] .= $wife->getSex();
851
-				}
852
-				$wage = Date::getAge($wdate, $mdate, 0);
853
-				if ($wage >= 0 && $wage <= $max_age) {
854
-					$marr_by_age[$wage] .= $wife->getSex();
855
-				}
856
-			}
857
-			$html .= '<td class="center" data-sort="' . Date::getAge($wdate, $mdate, 1) . '">' . Date::getAge($wdate, $mdate, 2) . '</td>';
858
-
859
-			// Marriage date
860
-			$html .= '<td data-sort="' . $family->getMarriageDate()->julianDay() . '">';
861
-			if ($marriage_dates = $family->getAllMarriageDates()) {
862
-				foreach ($marriage_dates as $n => $marriage_date) {
863
-					if ($n) {
864
-						$html .= '<br>';
865
-					}
866
-					$html .= '<div>' . $marriage_date->display(true) . '</div>';
867
-				}
868
-				if ($marriage_dates[0]->gregorianYear() >= 1550 && $marriage_dates[0]->gregorianYear() < 2030) {
869
-					$marr_by_decade[(int) ($marriage_dates[0]->gregorianYear() / 10) * 10] .= $husb->getSex() . $wife->getSex();
870
-				}
871
-			} elseif ($family->getFacts('_NMR')) {
872
-				$html .= I18N::translate('no');
873
-			} elseif ($family->getFacts('MARR')) {
874
-				$html .= I18N::translate('yes');
875
-			} else {
876
-				$html .= '&nbsp;';
877
-			}
878
-			$html .= '</td>';
879
-
880
-			// Marriage anniversary
881
-			$html .= '<td class="center" data-sort="' . -$family->getMarriageDate()->julianDay() . '">' . Date::getAge($family->getMarriageDate(), null, 2) . '</td>';
882
-
883
-			// Marriage place
884
-			$html .= '<td>';
885
-			foreach ($family->getAllMarriagePlaces() as $n => $marriage_place) {
886
-				$tmp = new Place($marriage_place, $family->getTree());
887
-				if ($n) {
888
-					$html .= '<br>';
889
-				}
890
-				$html .= '<a href="' . $tmp->getURL() . '" title="' . strip_tags($tmp->getFullName()) . '">';
891
-				$html .= FunctionsPrint::highlightSearchHits($tmp->getShortName()) . '</a>';
892
-			}
893
-			$html .= '</td>';
894
-
895
-			// Number of children
896
-			$html .= '<td class="center" data-sort="' . $family->getNumberOfChildren() . '">' . I18N::number($family->getNumberOfChildren()) . '</td>';
897
-
898
-			// Last change
899
-			$html .= '<td data-sort="' . $family->lastChangeTimestamp(true) . '">' . $family->lastChangeTimestamp() . '</td>';
900
-
901
-			// Filter by marriage date
902
-			$html .= '<td hidden>';
903
-			if (!$family->canShow() || !$mdate->isOK()) {
904
-				$html .= 'U';
905
-			} else {
906
-				if (Date::compare($mdate, $hundred_years_ago) > 0) {
907
-					$html .= 'Y100';
908
-				} else {
909
-					$html .= 'YES';
910
-				}
911
-			}
912
-			if ($family->getFacts(WT_EVENTS_DIV)) {
913
-				$html .= 'D';
914
-			}
915
-			if (count($husb->getSpouseFamilies()) > 1 || count($wife->getSpouseFamilies()) > 1) {
916
-				$html .= 'M';
917
-			}
918
-			$html .= '</td>';
919
-
920
-			// Filter by alive/dead
921
-			$html .= '<td hidden>';
922
-			if ($husb->isDead() && $wife->isDead()) {
923
-				$html .= 'Y';
924
-			}
925
-			if ($husb->isDead() && !$wife->isDead()) {
926
-				if ($wife->getSex() == 'F') {
927
-					$html .= 'H';
928
-				}
929
-				if ($wife->getSex() == 'M') {
930
-					$html .= 'W';
931
-				} // male partners
932
-			}
933
-			if (!$husb->isDead() && $wife->isDead()) {
934
-				if ($husb->getSex() == 'M') {
935
-					$html .= 'W';
936
-				}
937
-				if ($husb->getSex() == 'F') {
938
-					$html .= 'H';
939
-				} // female partners
940
-			}
941
-			if (!$husb->isDead() && !$wife->isDead()) {
942
-				$html .= 'N';
943
-			}
944
-			$html .= '</td>';
945
-
946
-			// Filter by roots/leaves
947
-			$html .= '<td hidden>';
948
-			if (!$husb->getChildFamilies() && !$wife->getChildFamilies()) {
949
-				$html .= 'R';
950
-			} elseif (!$husb->isDead() && !$wife->isDead() && $family->getNumberOfChildren() === 0) {
951
-				$html .= 'L';
952
-			}
953
-			$html .= '</td>
749
+        $hundred_years_ago = new Date(date('Y') - 100);
750
+
751
+        foreach ($families as $family) {
752
+            // Retrieve husband and wife
753
+            $husb = $family->getHusband();
754
+            if (is_null($husb)) {
755
+                $husb = new Individual('H', '0 @H@ INDI', null, $family->getTree());
756
+            }
757
+            $wife = $family->getWife();
758
+            if (is_null($wife)) {
759
+                $wife = new Individual('W', '0 @W@ INDI', null, $family->getTree());
760
+            }
761
+            if (!$family->canShow()) {
762
+                continue;
763
+            }
764
+            if ($family->isPendingAddtion()) {
765
+                $class = ' class="new"';
766
+            } elseif ($family->isPendingDeletion()) {
767
+                $class = ' class="old"';
768
+            } else {
769
+                $class = '';
770
+            }
771
+            $html .= '<tr' . $class . '>';
772
+            // Husband name(s)
773
+            // Extract Given names and Surnames for sorting
774
+            list($surn_givn, $givn_surn) = self::sortableNames($husb);
775
+
776
+            $html .= '<td colspan="2" data-sort="' . Filter::escapeHtml($givn_surn) . '">';
777
+            foreach ($husb->getAllNames() as $num => $name) {
778
+                if ($name['type'] == 'NAME') {
779
+                    $title = '';
780
+                } else {
781
+                    $title = 'title="' . strip_tags(GedcomTag::getLabel($name['type'], $husb)) . '"';
782
+                }
783
+                if ($num == $husb->getPrimaryName()) {
784
+                    $class             = ' class="name2"';
785
+                    $sex_image         = $husb->getSexImage();
786
+                } else {
787
+                    $class     = '';
788
+                    $sex_image = '';
789
+                }
790
+                // Only show married names if they are the name we are filtering by.
791
+                if ($name['type'] != '_MARNM' || $num == $husb->getPrimaryName()) {
792
+                    $html .= '<a ' . $title . ' href="' . $family->getHtmlUrl() . '"' . $class . '>' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>' . $sex_image . '<br>';
793
+                }
794
+            }
795
+            // Husband parents
796
+            $html .= $husb->getPrimaryParentsNames('parents details1', 'none');
797
+            $html .= '</td>';
798
+
799
+            // Hidden column for sortable name
800
+            $html .= '<td hidden data-sort="' . Filter::escapeHtml($surn_givn) . '"></td>';
801
+
802
+            // Husband age
803
+            $mdate = $family->getMarriageDate();
804
+            $hdate = $husb->getBirthDate();
805
+            if ($hdate->isOK() && $mdate->isOK()) {
806
+                if ($hdate->gregorianYear() >= 1550 && $hdate->gregorianYear() < 2030) {
807
+                    $birt_by_decade[(int) ($hdate->gregorianYear() / 10) * 10] .= $husb->getSex();
808
+                }
809
+                $hage = Date::getAge($hdate, $mdate, 0);
810
+                if ($hage >= 0 && $hage <= $max_age) {
811
+                    $marr_by_age[$hage] .= $husb->getSex();
812
+                }
813
+            }
814
+            $html .= '<td class="center" data=-sort="' . Date::getAge($hdate, $mdate, 1) . '">' . Date::getAge($hdate, $mdate, 2) . '</td>';
815
+
816
+            // Wife name(s)
817
+            // Extract Given names and Surnames for sorting
818
+            list($surn_givn, $givn_surn) = self::sortableNames($wife);
819
+            $html .= '<td colspan="2" data-sort="' . Filter::escapeHtml($givn_surn) . '">';
820
+            foreach ($wife->getAllNames() as $num => $name) {
821
+                if ($name['type'] == 'NAME') {
822
+                    $title = '';
823
+                } else {
824
+                    $title = 'title="' . strip_tags(GedcomTag::getLabel($name['type'], $wife)) . '"';
825
+                }
826
+                if ($num == $wife->getPrimaryName()) {
827
+                    $class             = ' class="name2"';
828
+                    $sex_image         = $wife->getSexImage();
829
+                } else {
830
+                    $class     = '';
831
+                    $sex_image = '';
832
+                }
833
+                // Only show married names if they are the name we are filtering by.
834
+                if ($name['type'] != '_MARNM' || $num == $wife->getPrimaryName()) {
835
+                    $html .= '<a ' . $title . ' href="' . $family->getHtmlUrl() . '"' . $class . '>' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>' . $sex_image . '<br>';
836
+                }
837
+            }
838
+            // Wife parents
839
+            $html .= $wife->getPrimaryParentsNames('parents details1', 'none');
840
+            $html .= '</td>';
841
+
842
+            // Hidden column for sortable name
843
+            $html .= '<td hidden data-sort="' . Filter::escapeHtml($surn_givn) . '"></td>';
844
+
845
+            // Wife age
846
+            $mdate = $family->getMarriageDate();
847
+            $wdate = $wife->getBirthDate();
848
+            if ($wdate->isOK() && $mdate->isOK()) {
849
+                if ($wdate->gregorianYear() >= 1550 && $wdate->gregorianYear() < 2030) {
850
+                    $birt_by_decade[(int) ($wdate->gregorianYear() / 10) * 10] .= $wife->getSex();
851
+                }
852
+                $wage = Date::getAge($wdate, $mdate, 0);
853
+                if ($wage >= 0 && $wage <= $max_age) {
854
+                    $marr_by_age[$wage] .= $wife->getSex();
855
+                }
856
+            }
857
+            $html .= '<td class="center" data-sort="' . Date::getAge($wdate, $mdate, 1) . '">' . Date::getAge($wdate, $mdate, 2) . '</td>';
858
+
859
+            // Marriage date
860
+            $html .= '<td data-sort="' . $family->getMarriageDate()->julianDay() . '">';
861
+            if ($marriage_dates = $family->getAllMarriageDates()) {
862
+                foreach ($marriage_dates as $n => $marriage_date) {
863
+                    if ($n) {
864
+                        $html .= '<br>';
865
+                    }
866
+                    $html .= '<div>' . $marriage_date->display(true) . '</div>';
867
+                }
868
+                if ($marriage_dates[0]->gregorianYear() >= 1550 && $marriage_dates[0]->gregorianYear() < 2030) {
869
+                    $marr_by_decade[(int) ($marriage_dates[0]->gregorianYear() / 10) * 10] .= $husb->getSex() . $wife->getSex();
870
+                }
871
+            } elseif ($family->getFacts('_NMR')) {
872
+                $html .= I18N::translate('no');
873
+            } elseif ($family->getFacts('MARR')) {
874
+                $html .= I18N::translate('yes');
875
+            } else {
876
+                $html .= '&nbsp;';
877
+            }
878
+            $html .= '</td>';
879
+
880
+            // Marriage anniversary
881
+            $html .= '<td class="center" data-sort="' . -$family->getMarriageDate()->julianDay() . '">' . Date::getAge($family->getMarriageDate(), null, 2) . '</td>';
882
+
883
+            // Marriage place
884
+            $html .= '<td>';
885
+            foreach ($family->getAllMarriagePlaces() as $n => $marriage_place) {
886
+                $tmp = new Place($marriage_place, $family->getTree());
887
+                if ($n) {
888
+                    $html .= '<br>';
889
+                }
890
+                $html .= '<a href="' . $tmp->getURL() . '" title="' . strip_tags($tmp->getFullName()) . '">';
891
+                $html .= FunctionsPrint::highlightSearchHits($tmp->getShortName()) . '</a>';
892
+            }
893
+            $html .= '</td>';
894
+
895
+            // Number of children
896
+            $html .= '<td class="center" data-sort="' . $family->getNumberOfChildren() . '">' . I18N::number($family->getNumberOfChildren()) . '</td>';
897
+
898
+            // Last change
899
+            $html .= '<td data-sort="' . $family->lastChangeTimestamp(true) . '">' . $family->lastChangeTimestamp() . '</td>';
900
+
901
+            // Filter by marriage date
902
+            $html .= '<td hidden>';
903
+            if (!$family->canShow() || !$mdate->isOK()) {
904
+                $html .= 'U';
905
+            } else {
906
+                if (Date::compare($mdate, $hundred_years_ago) > 0) {
907
+                    $html .= 'Y100';
908
+                } else {
909
+                    $html .= 'YES';
910
+                }
911
+            }
912
+            if ($family->getFacts(WT_EVENTS_DIV)) {
913
+                $html .= 'D';
914
+            }
915
+            if (count($husb->getSpouseFamilies()) > 1 || count($wife->getSpouseFamilies()) > 1) {
916
+                $html .= 'M';
917
+            }
918
+            $html .= '</td>';
919
+
920
+            // Filter by alive/dead
921
+            $html .= '<td hidden>';
922
+            if ($husb->isDead() && $wife->isDead()) {
923
+                $html .= 'Y';
924
+            }
925
+            if ($husb->isDead() && !$wife->isDead()) {
926
+                if ($wife->getSex() == 'F') {
927
+                    $html .= 'H';
928
+                }
929
+                if ($wife->getSex() == 'M') {
930
+                    $html .= 'W';
931
+                } // male partners
932
+            }
933
+            if (!$husb->isDead() && $wife->isDead()) {
934
+                if ($husb->getSex() == 'M') {
935
+                    $html .= 'W';
936
+                }
937
+                if ($husb->getSex() == 'F') {
938
+                    $html .= 'H';
939
+                } // female partners
940
+            }
941
+            if (!$husb->isDead() && !$wife->isDead()) {
942
+                $html .= 'N';
943
+            }
944
+            $html .= '</td>';
945
+
946
+            // Filter by roots/leaves
947
+            $html .= '<td hidden>';
948
+            if (!$husb->getChildFamilies() && !$wife->getChildFamilies()) {
949
+                $html .= 'R';
950
+            } elseif (!$husb->isDead() && !$wife->isDead() && $family->getNumberOfChildren() === 0) {
951
+                $html .= 'L';
952
+            }
953
+            $html .= '</td>
954 954
 			</tr>';
955
-		}
955
+        }
956 956
 
957
-		$html .= '
957
+        $html .= '
958 958
 					</tbody>
959 959
 				</table>
960 960
 				<div id="fam_list_table-charts_' . $table_id . '" style="display:none">
@@ -970,40 +970,40 @@  discard block
 block discarded – undo
970 970
 				</div>
971 971
 			</div>';
972 972
 
973
-		return $html;
974
-	}
975
-
976
-	/**
977
-	 * Print a table of sources
978
-	 *
979
-	 * @param Source[] $sources
980
-	 *
981
-	 * @return string
982
-	 */
983
-	public static function sourceTable($sources) {
984
-		global $WT_TREE, $controller;
985
-
986
-		// Count the number of linked records. These numbers include private records.
987
-		// It is not good to bypass privacy, but many servers do not have the resources
988
-		// to process privacy for every record in the tree
989
-		$count_individuals = Database::prepare(
990
-			"SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##individuals` JOIN `##link` ON l_from = i_id AND l_file = i_file AND l_type = 'SOUR' GROUP BY l_to, l_file"
991
-		)->fetchAssoc();
992
-		$count_families = Database::prepare(
993
-			"SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##families` JOIN `##link` ON l_from = f_id AND l_file = f_file AND l_type = 'SOUR' GROUP BY l_to, l_file"
994
-		)->fetchAssoc();
995
-		$count_media = Database::prepare(
996
-			"SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##media` JOIN `##link` ON l_from = m_id AND l_file = m_file AND l_type = 'SOUR' GROUP BY l_to, l_file"
997
-		)->fetchAssoc();
998
-		$count_notes = Database::prepare(
999
-			"SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##other` JOIN `##link` ON l_from = o_id AND l_file = o_file AND o_type = 'NOTE' AND l_type = 'SOUR' GROUP BY l_to, l_file"
1000
-		)->fetchAssoc();
1001
-
1002
-		$html     = '';
1003
-		$table_id = 'table-sour-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page
1004
-		$controller
1005
-			->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
1006
-			->addInlineJavascript('
973
+        return $html;
974
+    }
975
+
976
+    /**
977
+     * Print a table of sources
978
+     *
979
+     * @param Source[] $sources
980
+     *
981
+     * @return string
982
+     */
983
+    public static function sourceTable($sources) {
984
+        global $WT_TREE, $controller;
985
+
986
+        // Count the number of linked records. These numbers include private records.
987
+        // It is not good to bypass privacy, but many servers do not have the resources
988
+        // to process privacy for every record in the tree
989
+        $count_individuals = Database::prepare(
990
+            "SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##individuals` JOIN `##link` ON l_from = i_id AND l_file = i_file AND l_type = 'SOUR' GROUP BY l_to, l_file"
991
+        )->fetchAssoc();
992
+        $count_families = Database::prepare(
993
+            "SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##families` JOIN `##link` ON l_from = f_id AND l_file = f_file AND l_type = 'SOUR' GROUP BY l_to, l_file"
994
+        )->fetchAssoc();
995
+        $count_media = Database::prepare(
996
+            "SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##media` JOIN `##link` ON l_from = m_id AND l_file = m_file AND l_type = 'SOUR' GROUP BY l_to, l_file"
997
+        )->fetchAssoc();
998
+        $count_notes = Database::prepare(
999
+            "SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##other` JOIN `##link` ON l_from = o_id AND l_file = o_file AND o_type = 'NOTE' AND l_type = 'SOUR' GROUP BY l_to, l_file"
1000
+        )->fetchAssoc();
1001
+
1002
+        $html     = '';
1003
+        $table_id = 'table-sour-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page
1004
+        $controller
1005
+            ->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
1006
+            ->addInlineJavascript('
1007 1007
 				jQuery.fn.dataTableExt.oSort["text-asc"] = textCompareAsc;
1008 1008
 				jQuery.fn.dataTableExt.oSort["text-desc"] = textCompareDesc;
1009 1009
 				jQuery("#' . $table_id . '").dataTable( {
@@ -1029,108 +1029,108 @@  discard block
 block discarded – undo
1029 1029
 				jQuery(".loading-image").css("display", "none");
1030 1030
 			');
1031 1031
 
1032
-		$html .= '<div class="loading-image"></div>';
1033
-		$html .= '<div class="source-list">';
1034
-		$html .= '<table id="' . $table_id . '"><thead><tr>';
1035
-		$html .= '<th>' . GedcomTag::getLabel('TITL') . '</th>';
1036
-		$html .= '<th>' . GedcomTag::getLabel('AUTH') . '</th>';
1037
-		$html .= '<th>' . I18N::translate('Individuals') . '</th>';
1038
-		$html .= '<th>' . I18N::translate('Families') . '</th>';
1039
-		$html .= '<th>' . I18N::translate('Media objects') . '</th>';
1040
-		$html .= '<th>' . I18N::translate('Shared notes') . '</th>';
1041
-		$html .= '<th>' . GedcomTag::getLabel('CHAN') . '</th>';
1042
-		$html .= '<th>' . I18N::translate('Delete') . '</th>';
1043
-		$html .= '</tr></thead>';
1044
-		$html .= '<tbody>';
1045
-
1046
-		foreach ($sources as $source) {
1047
-			if (!$source->canShow()) {
1048
-				continue;
1049
-			}
1050
-			if ($source->isPendingAddtion()) {
1051
-				$class = ' class="new"';
1052
-			} elseif ($source->isPendingDeletion()) {
1053
-				$class = ' class="old"';
1054
-			} else {
1055
-				$class = '';
1056
-			}
1057
-			$html .= '<tr' . $class . '>';
1058
-			// Source name(s)
1059
-			$html .= '<td data-sort="' . Filter::escapeHtml($source->getSortName()) . '">';
1060
-			foreach ($source->getAllNames() as $n => $name) {
1061
-				if ($n) {
1062
-					$html .= '<br>';
1063
-				}
1064
-				if ($n == $source->getPrimaryName()) {
1065
-					$html .= '<a class="name2" href="' . $source->getHtmlUrl() . '">' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>';
1066
-				} else {
1067
-					$html .= '<a href="' . $source->getHtmlUrl() . '">' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>';
1068
-				}
1069
-			}
1070
-			$html .= '</td>';
1071
-			// Author
1072
-			$auth = $source->getFirstFact('AUTH');
1073
-			if ($auth) {
1074
-				$author = $auth->getValue();
1075
-			} else {
1076
-				$author = '';
1077
-			}
1078
-			$html .= '<td data-sort="' . Filter::escapeHtml($author) . '">' . FunctionsPrint::highlightSearchHits($author) . '</td>';
1079
-			$key = $source->getXref() . '@' . $source->getTree()->getTreeId();
1080
-			// Count of linked individuals
1081
-			$num = array_key_exists($key, $count_individuals) ? $count_individuals[$key] : 0;
1082
-			$html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1083
-			// Count of linked families
1084
-			$num = array_key_exists($key, $count_families) ? $count_families[$key] : 0;
1085
-			$html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1086
-			// Count of linked media objects
1087
-			$num = array_key_exists($key, $count_media) ? $count_media[$key] : 0;
1088
-			$html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1089
-			// Count of linked notes
1090
-			$num = array_key_exists($key, $count_notes) ? $count_notes[$key] : 0;
1091
-			$html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1092
-			// Last change
1093
-			$html .= '<td data-sort="' . $source->lastChangeTimestamp(true) . '">' . $source->lastChangeTimestamp() . '</td>';
1094
-			// Delete
1095
-			$html .= '<td><a href="#" title="' . I18N::translate('Delete') . '" class="deleteicon" onclick="return delete_record(\'' . I18N::translate('Are you sure you want to delete “%s”?', Filter::escapeJs(Filter::unescapeHtml($source->getFullName()))) . "', '" . $source->getXref() . '\');"><span class="link_text">' . I18N::translate('Delete') . '</span></a></td>';
1096
-			$html .= '</tr>';
1097
-		}
1098
-		$html .= '</tbody></table></div>';
1099
-
1100
-		return $html;
1101
-	}
1102
-
1103
-	/**
1104
-	 * Print a table of shared notes
1105
-	 *
1106
-	 * @param Note[] $notes
1107
-	 *
1108
-	 * @return string
1109
-	 */
1110
-	public static function noteTable($notes) {
1111
-		global $WT_TREE, $controller;
1112
-
1113
-		// Count the number of linked records. These numbers include private records.
1114
-		// It is not good to bypass privacy, but many servers do not have the resources
1115
-		// to process privacy for every record in the tree
1116
-		$count_individuals = Database::prepare(
1117
-			"SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##individuals` JOIN `##link` ON l_from = i_id AND l_file = i_file AND l_type = 'NOTE' GROUP BY l_to, l_file"
1118
-		)->fetchAssoc();
1119
-		$count_families = Database::prepare(
1120
-			"SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##families` JOIN `##link` ON l_from = f_id AND l_file = f_file AND l_type = 'NOTE' GROUP BY l_to, l_file"
1121
-		)->fetchAssoc();
1122
-		$count_media = Database::prepare(
1123
-			"SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##media` JOIN `##link` ON l_from = m_id AND l_file = m_file AND l_type = 'NOTE' GROUP BY l_to, l_file"
1124
-		)->fetchAssoc();
1125
-		$count_sources = Database::prepare(
1126
-			"SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##sources` JOIN `##link` ON l_from = s_id AND l_file = s_file AND l_type = 'NOTE' GROUP BY l_to, l_file"
1127
-		)->fetchAssoc();
1128
-
1129
-		$html     = '';
1130
-		$table_id = 'table-note-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page
1131
-		$controller
1132
-			->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
1133
-			->addInlineJavascript('
1032
+        $html .= '<div class="loading-image"></div>';
1033
+        $html .= '<div class="source-list">';
1034
+        $html .= '<table id="' . $table_id . '"><thead><tr>';
1035
+        $html .= '<th>' . GedcomTag::getLabel('TITL') . '</th>';
1036
+        $html .= '<th>' . GedcomTag::getLabel('AUTH') . '</th>';
1037
+        $html .= '<th>' . I18N::translate('Individuals') . '</th>';
1038
+        $html .= '<th>' . I18N::translate('Families') . '</th>';
1039
+        $html .= '<th>' . I18N::translate('Media objects') . '</th>';
1040
+        $html .= '<th>' . I18N::translate('Shared notes') . '</th>';
1041
+        $html .= '<th>' . GedcomTag::getLabel('CHAN') . '</th>';
1042
+        $html .= '<th>' . I18N::translate('Delete') . '</th>';
1043
+        $html .= '</tr></thead>';
1044
+        $html .= '<tbody>';
1045
+
1046
+        foreach ($sources as $source) {
1047
+            if (!$source->canShow()) {
1048
+                continue;
1049
+            }
1050
+            if ($source->isPendingAddtion()) {
1051
+                $class = ' class="new"';
1052
+            } elseif ($source->isPendingDeletion()) {
1053
+                $class = ' class="old"';
1054
+            } else {
1055
+                $class = '';
1056
+            }
1057
+            $html .= '<tr' . $class . '>';
1058
+            // Source name(s)
1059
+            $html .= '<td data-sort="' . Filter::escapeHtml($source->getSortName()) . '">';
1060
+            foreach ($source->getAllNames() as $n => $name) {
1061
+                if ($n) {
1062
+                    $html .= '<br>';
1063
+                }
1064
+                if ($n == $source->getPrimaryName()) {
1065
+                    $html .= '<a class="name2" href="' . $source->getHtmlUrl() . '">' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>';
1066
+                } else {
1067
+                    $html .= '<a href="' . $source->getHtmlUrl() . '">' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>';
1068
+                }
1069
+            }
1070
+            $html .= '</td>';
1071
+            // Author
1072
+            $auth = $source->getFirstFact('AUTH');
1073
+            if ($auth) {
1074
+                $author = $auth->getValue();
1075
+            } else {
1076
+                $author = '';
1077
+            }
1078
+            $html .= '<td data-sort="' . Filter::escapeHtml($author) . '">' . FunctionsPrint::highlightSearchHits($author) . '</td>';
1079
+            $key = $source->getXref() . '@' . $source->getTree()->getTreeId();
1080
+            // Count of linked individuals
1081
+            $num = array_key_exists($key, $count_individuals) ? $count_individuals[$key] : 0;
1082
+            $html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1083
+            // Count of linked families
1084
+            $num = array_key_exists($key, $count_families) ? $count_families[$key] : 0;
1085
+            $html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1086
+            // Count of linked media objects
1087
+            $num = array_key_exists($key, $count_media) ? $count_media[$key] : 0;
1088
+            $html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1089
+            // Count of linked notes
1090
+            $num = array_key_exists($key, $count_notes) ? $count_notes[$key] : 0;
1091
+            $html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1092
+            // Last change
1093
+            $html .= '<td data-sort="' . $source->lastChangeTimestamp(true) . '">' . $source->lastChangeTimestamp() . '</td>';
1094
+            // Delete
1095
+            $html .= '<td><a href="#" title="' . I18N::translate('Delete') . '" class="deleteicon" onclick="return delete_record(\'' . I18N::translate('Are you sure you want to delete “%s”?', Filter::escapeJs(Filter::unescapeHtml($source->getFullName()))) . "', '" . $source->getXref() . '\');"><span class="link_text">' . I18N::translate('Delete') . '</span></a></td>';
1096
+            $html .= '</tr>';
1097
+        }
1098
+        $html .= '</tbody></table></div>';
1099
+
1100
+        return $html;
1101
+    }
1102
+
1103
+    /**
1104
+     * Print a table of shared notes
1105
+     *
1106
+     * @param Note[] $notes
1107
+     *
1108
+     * @return string
1109
+     */
1110
+    public static function noteTable($notes) {
1111
+        global $WT_TREE, $controller;
1112
+
1113
+        // Count the number of linked records. These numbers include private records.
1114
+        // It is not good to bypass privacy, but many servers do not have the resources
1115
+        // to process privacy for every record in the tree
1116
+        $count_individuals = Database::prepare(
1117
+            "SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##individuals` JOIN `##link` ON l_from = i_id AND l_file = i_file AND l_type = 'NOTE' GROUP BY l_to, l_file"
1118
+        )->fetchAssoc();
1119
+        $count_families = Database::prepare(
1120
+            "SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##families` JOIN `##link` ON l_from = f_id AND l_file = f_file AND l_type = 'NOTE' GROUP BY l_to, l_file"
1121
+        )->fetchAssoc();
1122
+        $count_media = Database::prepare(
1123
+            "SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##media` JOIN `##link` ON l_from = m_id AND l_file = m_file AND l_type = 'NOTE' GROUP BY l_to, l_file"
1124
+        )->fetchAssoc();
1125
+        $count_sources = Database::prepare(
1126
+            "SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##sources` JOIN `##link` ON l_from = s_id AND l_file = s_file AND l_type = 'NOTE' GROUP BY l_to, l_file"
1127
+        )->fetchAssoc();
1128
+
1129
+        $html     = '';
1130
+        $table_id = 'table-note-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page
1131
+        $controller
1132
+            ->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
1133
+            ->addInlineJavascript('
1134 1134
 				jQuery.fn.dataTableExt.oSort["text-asc"] = textCompareAsc;
1135 1135
 				jQuery.fn.dataTableExt.oSort["text-desc"] = textCompareDesc;
1136 1136
 				jQuery("#' . $table_id . '").dataTable({
@@ -1155,79 +1155,79 @@  discard block
 block discarded – undo
1155 1155
 				jQuery(".loading-image").css("display", "none");
1156 1156
 			');
1157 1157
 
1158
-		$html .= '<div class="loading-image"></div>';
1159
-		$html .= '<div class="note-list">';
1160
-		$html .= '<table id="' . $table_id . '"><thead><tr>';
1161
-		$html .= '<th>' . GedcomTag::getLabel('TITL') . '</th>';
1162
-		$html .= '<th>' . I18N::translate('Individuals') . '</th>';
1163
-		$html .= '<th>' . I18N::translate('Families') . '</th>';
1164
-		$html .= '<th>' . I18N::translate('Media objects') . '</th>';
1165
-		$html .= '<th>' . I18N::translate('Sources') . '</th>';
1166
-		$html .= '<th>' . GedcomTag::getLabel('CHAN') . '</th>';
1167
-		$html .= '<th>' . I18N::translate('Delete') . '</th>';
1168
-		$html .= '</tr></thead>';
1169
-		$html .= '<tbody>';
1170
-
1171
-		foreach ($notes as $note) {
1172
-			if (!$note->canShow()) {
1173
-				continue;
1174
-			}
1175
-			if ($note->isPendingAddtion()) {
1176
-				$class = ' class="new"';
1177
-			} elseif ($note->isPendingDeletion()) {
1178
-				$class = ' class="old"';
1179
-			} else {
1180
-				$class = '';
1181
-			}
1182
-			$html .= '<tr' . $class . '>';
1183
-			// Count of linked notes
1184
-			$html .= '<td data-sort="' . Filter::escapeHtml($note->getSortName()) . '"><a class="name2" href="' . $note->getHtmlUrl() . '">' . FunctionsPrint::highlightSearchHits($note->getFullName()) . '</a></td>';
1185
-			$key = $note->getXref() . '@' . $note->getTree()->getTreeId();
1186
-			// Count of linked individuals
1187
-			$num = array_key_exists($key, $count_individuals) ? $count_individuals[$key] : 0;
1188
-			$html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1189
-			// Count of linked families
1190
-			$num = array_key_exists($key, $count_families) ? $count_families[$key] : 0;
1191
-			$html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1192
-			// Count of linked media objects
1193
-			$num = array_key_exists($key, $count_media) ? $count_media[$key] : 0;
1194
-			$html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1195
-			// Count of linked sources
1196
-			$num = array_key_exists($key, $count_sources) ? $count_sources[$key] : 0;
1197
-			$html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1198
-			// Last change
1199
-			$html .= '<td data-sort="' . $note->lastChangeTimestamp(true) . '">' . $note->lastChangeTimestamp() . '</td>';
1200
-			// Delete
1201
-			$html .= '<td><a href="#" title="' . I18N::translate('Delete') . '" class="deleteicon" onclick="return delete_record(\'' . I18N::translate('Are you sure you want to delete “%s”?', Filter::escapeJs(Filter::unescapeHtml($note->getFullName()))) . "', '" . $note->getXref() . '\');"><span class="link_text">' . I18N::translate('Delete') . '</span></a></td>';
1202
-			$html .= '</tr>';
1203
-		}
1204
-		$html .= '</tbody></table></div>';
1205
-
1206
-		return $html;
1207
-	}
1208
-
1209
-	/**
1210
-	 * Print a table of repositories
1211
-	 *
1212
-	 * @param Repository[] $repositories
1213
-	 *
1214
-	 * @return string
1215
-	 */
1216
-	public static function repositoryTable($repositories) {
1217
-		global $WT_TREE, $controller;
1218
-
1219
-		// Count the number of linked records. These numbers include private records.
1220
-		// It is not good to bypass privacy, but many servers do not have the resources
1221
-		// to process privacy for every record in the tree
1222
-		$count_sources = Database::prepare(
1223
-			"SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##sources` JOIN `##link` ON l_from = s_id AND l_file = s_file AND l_type = 'REPO' GROUP BY l_to, l_file"
1224
-		)->fetchAssoc();
1225
-
1226
-		$html     = '';
1227
-		$table_id = 'table-repo-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page
1228
-		$controller
1229
-			->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
1230
-			->addInlineJavascript('
1158
+        $html .= '<div class="loading-image"></div>';
1159
+        $html .= '<div class="note-list">';
1160
+        $html .= '<table id="' . $table_id . '"><thead><tr>';
1161
+        $html .= '<th>' . GedcomTag::getLabel('TITL') . '</th>';
1162
+        $html .= '<th>' . I18N::translate('Individuals') . '</th>';
1163
+        $html .= '<th>' . I18N::translate('Families') . '</th>';
1164
+        $html .= '<th>' . I18N::translate('Media objects') . '</th>';
1165
+        $html .= '<th>' . I18N::translate('Sources') . '</th>';
1166
+        $html .= '<th>' . GedcomTag::getLabel('CHAN') . '</th>';
1167
+        $html .= '<th>' . I18N::translate('Delete') . '</th>';
1168
+        $html .= '</tr></thead>';
1169
+        $html .= '<tbody>';
1170
+
1171
+        foreach ($notes as $note) {
1172
+            if (!$note->canShow()) {
1173
+                continue;
1174
+            }
1175
+            if ($note->isPendingAddtion()) {
1176
+                $class = ' class="new"';
1177
+            } elseif ($note->isPendingDeletion()) {
1178
+                $class = ' class="old"';
1179
+            } else {
1180
+                $class = '';
1181
+            }
1182
+            $html .= '<tr' . $class . '>';
1183
+            // Count of linked notes
1184
+            $html .= '<td data-sort="' . Filter::escapeHtml($note->getSortName()) . '"><a class="name2" href="' . $note->getHtmlUrl() . '">' . FunctionsPrint::highlightSearchHits($note->getFullName()) . '</a></td>';
1185
+            $key = $note->getXref() . '@' . $note->getTree()->getTreeId();
1186
+            // Count of linked individuals
1187
+            $num = array_key_exists($key, $count_individuals) ? $count_individuals[$key] : 0;
1188
+            $html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1189
+            // Count of linked families
1190
+            $num = array_key_exists($key, $count_families) ? $count_families[$key] : 0;
1191
+            $html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1192
+            // Count of linked media objects
1193
+            $num = array_key_exists($key, $count_media) ? $count_media[$key] : 0;
1194
+            $html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1195
+            // Count of linked sources
1196
+            $num = array_key_exists($key, $count_sources) ? $count_sources[$key] : 0;
1197
+            $html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1198
+            // Last change
1199
+            $html .= '<td data-sort="' . $note->lastChangeTimestamp(true) . '">' . $note->lastChangeTimestamp() . '</td>';
1200
+            // Delete
1201
+            $html .= '<td><a href="#" title="' . I18N::translate('Delete') . '" class="deleteicon" onclick="return delete_record(\'' . I18N::translate('Are you sure you want to delete “%s”?', Filter::escapeJs(Filter::unescapeHtml($note->getFullName()))) . "', '" . $note->getXref() . '\');"><span class="link_text">' . I18N::translate('Delete') . '</span></a></td>';
1202
+            $html .= '</tr>';
1203
+        }
1204
+        $html .= '</tbody></table></div>';
1205
+
1206
+        return $html;
1207
+    }
1208
+
1209
+    /**
1210
+     * Print a table of repositories
1211
+     *
1212
+     * @param Repository[] $repositories
1213
+     *
1214
+     * @return string
1215
+     */
1216
+    public static function repositoryTable($repositories) {
1217
+        global $WT_TREE, $controller;
1218
+
1219
+        // Count the number of linked records. These numbers include private records.
1220
+        // It is not good to bypass privacy, but many servers do not have the resources
1221
+        // to process privacy for every record in the tree
1222
+        $count_sources = Database::prepare(
1223
+            "SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##sources` JOIN `##link` ON l_from = s_id AND l_file = s_file AND l_type = 'REPO' GROUP BY l_to, l_file"
1224
+        )->fetchAssoc();
1225
+
1226
+        $html     = '';
1227
+        $table_id = 'table-repo-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page
1228
+        $controller
1229
+            ->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
1230
+            ->addInlineJavascript('
1231 1231
 				jQuery.fn.dataTableExt.oSort["text-asc"] = textCompareAsc;
1232 1232
 				jQuery.fn.dataTableExt.oSort["text-desc"] = textCompareDesc;
1233 1233
 				jQuery("#' . $table_id . '").dataTable({
@@ -1249,71 +1249,71 @@  discard block
 block discarded – undo
1249 1249
 				jQuery(".loading-image").css("display", "none");
1250 1250
 			');
1251 1251
 
1252
-		$html .= '<div class="loading-image"></div>';
1253
-		$html .= '<div class="repo-list">';
1254
-		$html .= '<table id="' . $table_id . '"><thead><tr>';
1255
-		$html .= '<th>' . I18N::translate('Repository name') . '</th>';
1256
-		$html .= '<th>' . I18N::translate('Sources') . '</th>';
1257
-		$html .= '<th>' . GedcomTag::getLabel('CHAN') . '</th>';
1258
-		$html .= '<th>' . I18N::translate('Delete') . '</th>';
1259
-		$html .= '</tr></thead>';
1260
-		$html .= '<tbody>';
1261
-
1262
-		foreach ($repositories as $repository) {
1263
-			if (!$repository->canShow()) {
1264
-				continue;
1265
-			}
1266
-			if ($repository->isPendingAddtion()) {
1267
-				$class = ' class="new"';
1268
-			} elseif ($repository->isPendingDeletion()) {
1269
-				$class = ' class="old"';
1270
-			} else {
1271
-				$class = '';
1272
-			}
1273
-			$html .= '<tr' . $class . '>';
1274
-			// Repository name(s)
1275
-			$html .= '<td data-sort="' . Filter::escapeHtml($repository->getSortName()) . '">';
1276
-			foreach ($repository->getAllNames() as $n => $name) {
1277
-				if ($n) {
1278
-					$html .= '<br>';
1279
-				}
1280
-				if ($n == $repository->getPrimaryName()) {
1281
-					$html .= '<a class="name2" href="' . $repository->getHtmlUrl() . '">' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>';
1282
-				} else {
1283
-					$html .= '<a href="' . $repository->getHtmlUrl() . '">' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>';
1284
-				}
1285
-			}
1286
-			$html .= '</td>';
1287
-			$key = $repository->getXref() . '@' . $repository->getTree()->getTreeId();
1288
-			// Count of linked sources
1289
-			$num = array_key_exists($key, $count_sources) ? $count_sources[$key] : 0;
1290
-			$html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1291
-			// Last change
1292
-			$html .= '<td data-sort="' . $repository->lastChangeTimestamp(true) . '">' . $repository->lastChangeTimestamp() . '</td>';
1293
-			// Delete
1294
-			$html .= '<td><a href="#" title="' . I18N::translate('Delete') . '" class="deleteicon" onclick="return delete_record(\'' . I18N::translate('Are you sure you want to delete “%s”?', Filter::escapeJs(Filter::unescapeHtml($repository->getFullName()))) . "', '" . $repository->getXref() . '\');"><span class="link_text">' . I18N::translate('Delete') . '</span></a></td>';
1295
-			$html .= '</tr>';
1296
-		}
1297
-		$html .= '</tbody></table></div>';
1298
-
1299
-		return $html;
1300
-	}
1301
-
1302
-	/**
1303
-	 * Print a table of media objects
1304
-	 *
1305
-	 * @param Media[] $media_objects
1306
-	 *
1307
-	 * @return string
1308
-	 */
1309
-	public static function mediaTable($media_objects) {
1310
-		global $WT_TREE, $controller;
1311
-
1312
-		$html     = '';
1313
-		$table_id = 'table-obje-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page
1314
-		$controller
1315
-			->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
1316
-			->addInlineJavascript('
1252
+        $html .= '<div class="loading-image"></div>';
1253
+        $html .= '<div class="repo-list">';
1254
+        $html .= '<table id="' . $table_id . '"><thead><tr>';
1255
+        $html .= '<th>' . I18N::translate('Repository name') . '</th>';
1256
+        $html .= '<th>' . I18N::translate('Sources') . '</th>';
1257
+        $html .= '<th>' . GedcomTag::getLabel('CHAN') . '</th>';
1258
+        $html .= '<th>' . I18N::translate('Delete') . '</th>';
1259
+        $html .= '</tr></thead>';
1260
+        $html .= '<tbody>';
1261
+
1262
+        foreach ($repositories as $repository) {
1263
+            if (!$repository->canShow()) {
1264
+                continue;
1265
+            }
1266
+            if ($repository->isPendingAddtion()) {
1267
+                $class = ' class="new"';
1268
+            } elseif ($repository->isPendingDeletion()) {
1269
+                $class = ' class="old"';
1270
+            } else {
1271
+                $class = '';
1272
+            }
1273
+            $html .= '<tr' . $class . '>';
1274
+            // Repository name(s)
1275
+            $html .= '<td data-sort="' . Filter::escapeHtml($repository->getSortName()) . '">';
1276
+            foreach ($repository->getAllNames() as $n => $name) {
1277
+                if ($n) {
1278
+                    $html .= '<br>';
1279
+                }
1280
+                if ($n == $repository->getPrimaryName()) {
1281
+                    $html .= '<a class="name2" href="' . $repository->getHtmlUrl() . '">' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>';
1282
+                } else {
1283
+                    $html .= '<a href="' . $repository->getHtmlUrl() . '">' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>';
1284
+                }
1285
+            }
1286
+            $html .= '</td>';
1287
+            $key = $repository->getXref() . '@' . $repository->getTree()->getTreeId();
1288
+            // Count of linked sources
1289
+            $num = array_key_exists($key, $count_sources) ? $count_sources[$key] : 0;
1290
+            $html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1291
+            // Last change
1292
+            $html .= '<td data-sort="' . $repository->lastChangeTimestamp(true) . '">' . $repository->lastChangeTimestamp() . '</td>';
1293
+            // Delete
1294
+            $html .= '<td><a href="#" title="' . I18N::translate('Delete') . '" class="deleteicon" onclick="return delete_record(\'' . I18N::translate('Are you sure you want to delete “%s”?', Filter::escapeJs(Filter::unescapeHtml($repository->getFullName()))) . "', '" . $repository->getXref() . '\');"><span class="link_text">' . I18N::translate('Delete') . '</span></a></td>';
1295
+            $html .= '</tr>';
1296
+        }
1297
+        $html .= '</tbody></table></div>';
1298
+
1299
+        return $html;
1300
+    }
1301
+
1302
+    /**
1303
+     * Print a table of media objects
1304
+     *
1305
+     * @param Media[] $media_objects
1306
+     *
1307
+     * @return string
1308
+     */
1309
+    public static function mediaTable($media_objects) {
1310
+        global $WT_TREE, $controller;
1311
+
1312
+        $html     = '';
1313
+        $table_id = 'table-obje-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page
1314
+        $controller
1315
+            ->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
1316
+            ->addInlineJavascript('
1317 1317
 				jQuery.fn.dataTableExt.oSort["text-asc"] = textCompareAsc;
1318 1318
 				jQuery.fn.dataTableExt.oSort["text-desc"] = textCompareDesc;
1319 1319
 				jQuery("#' . $table_id . '").dataTable({
@@ -1337,75 +1337,75 @@  discard block
 block discarded – undo
1337 1337
 				jQuery(".loading-image").css("display", "none");
1338 1338
 			');
1339 1339
 
1340
-		$html .= '<div class="loading-image"></div>';
1341
-		$html .= '<div class="media-list">';
1342
-		$html .= '<table id="' . $table_id . '"><thead><tr>';
1343
-		$html .= '<th>' . I18N::translate('Media') . '</th>';
1344
-		$html .= '<th>' . GedcomTag::getLabel('TITL') . '</th>';
1345
-		$html .= '<th>' . I18N::translate('Individuals') . '</th>';
1346
-		$html .= '<th>' . I18N::translate('Families') . '</th>';
1347
-		$html .= '<th>' . I18N::translate('Sources') . '</th>';
1348
-		$html .= '<th>' . GedcomTag::getLabel('CHAN') . '</th>';
1349
-		$html .= '</tr></thead>';
1350
-		$html .= '<tbody>';
1351
-
1352
-		foreach ($media_objects as $media_object) {
1353
-			if ($media_object->canShow()) {
1354
-				$name = $media_object->getFullName();
1355
-				if ($media_object->isPendingAddtion()) {
1356
-					$class = ' class="new"';
1357
-				} elseif ($media_object->isPendingDeletion()) {
1358
-					$class = ' class="old"';
1359
-				} else {
1360
-					$class = '';
1361
-				}
1362
-				$html .= '<tr' . $class . '>';
1363
-				// Media object thumbnail
1364
-				$html .= '<td>' . $media_object->displayImage() . '</td>';
1365
-				// Media object name(s)
1366
-				$html .= '<td data-sort="' . Filter::escapeHtml($media_object->getSortName()) . '">';
1367
-				$html .= '<a href="' . $media_object->getHtmlUrl() . '" class="list_item name2">';
1368
-				$html .= FunctionsPrint::highlightSearchHits($name) . '</a>';
1369
-				if (Auth::isEditor($media_object->getTree())) {
1370
-					$html .= '<br><a href="' . $media_object->getHtmlUrl() . '">' . basename($media_object->getFilename()) . '</a>';
1371
-				}
1372
-				$html .= '</td>';
1373
-
1374
-				// Count of linked individuals
1375
-				$num = count($media_object->linkedIndividuals('OBJE'));
1376
-				$html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1377
-				// Count of linked families
1378
-				$num = count($media_object->linkedFamilies('OBJE'));
1379
-				$html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1380
-				// Count of linked sources
1381
-				$num = count($media_object->linkedSources('OBJE'));
1382
-				$html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1383
-				// Last change
1384
-				$html .= '<td data-sort="' . $media_object->lastChangeTimestamp(true) . '">' . $media_object->lastChangeTimestamp() . '</td>';
1385
-				$html .= '</tr>';
1386
-			}
1387
-		}
1388
-		$html .= '</tbody></table></div>';
1389
-
1390
-		return $html;
1391
-	}
1392
-
1393
-	/**
1394
-	 * Print a table of surnames, for the top surnames block, the indi/fam lists, etc.
1395
-	 *
1396
-	 * @param string[][] $surnames array (of SURN, of array of SPFX_SURN, of array of PID)
1397
-	 * @param string $script "indilist.php" (counts of individuals) or "famlist.php" (counts of spouses)
1398
-	 * @param Tree $tree generate links for this tree
1399
-	 *
1400
-	 * @return string
1401
-	 */
1402
-	public static function surnameTable($surnames, $script, Tree $tree) {
1403
-		global $controller;
1404
-
1405
-		$html = '';
1406
-		$controller
1407
-			->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
1408
-			->addInlineJavascript('
1340
+        $html .= '<div class="loading-image"></div>';
1341
+        $html .= '<div class="media-list">';
1342
+        $html .= '<table id="' . $table_id . '"><thead><tr>';
1343
+        $html .= '<th>' . I18N::translate('Media') . '</th>';
1344
+        $html .= '<th>' . GedcomTag::getLabel('TITL') . '</th>';
1345
+        $html .= '<th>' . I18N::translate('Individuals') . '</th>';
1346
+        $html .= '<th>' . I18N::translate('Families') . '</th>';
1347
+        $html .= '<th>' . I18N::translate('Sources') . '</th>';
1348
+        $html .= '<th>' . GedcomTag::getLabel('CHAN') . '</th>';
1349
+        $html .= '</tr></thead>';
1350
+        $html .= '<tbody>';
1351
+
1352
+        foreach ($media_objects as $media_object) {
1353
+            if ($media_object->canShow()) {
1354
+                $name = $media_object->getFullName();
1355
+                if ($media_object->isPendingAddtion()) {
1356
+                    $class = ' class="new"';
1357
+                } elseif ($media_object->isPendingDeletion()) {
1358
+                    $class = ' class="old"';
1359
+                } else {
1360
+                    $class = '';
1361
+                }
1362
+                $html .= '<tr' . $class . '>';
1363
+                // Media object thumbnail
1364
+                $html .= '<td>' . $media_object->displayImage() . '</td>';
1365
+                // Media object name(s)
1366
+                $html .= '<td data-sort="' . Filter::escapeHtml($media_object->getSortName()) . '">';
1367
+                $html .= '<a href="' . $media_object->getHtmlUrl() . '" class="list_item name2">';
1368
+                $html .= FunctionsPrint::highlightSearchHits($name) . '</a>';
1369
+                if (Auth::isEditor($media_object->getTree())) {
1370
+                    $html .= '<br><a href="' . $media_object->getHtmlUrl() . '">' . basename($media_object->getFilename()) . '</a>';
1371
+                }
1372
+                $html .= '</td>';
1373
+
1374
+                // Count of linked individuals
1375
+                $num = count($media_object->linkedIndividuals('OBJE'));
1376
+                $html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1377
+                // Count of linked families
1378
+                $num = count($media_object->linkedFamilies('OBJE'));
1379
+                $html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1380
+                // Count of linked sources
1381
+                $num = count($media_object->linkedSources('OBJE'));
1382
+                $html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1383
+                // Last change
1384
+                $html .= '<td data-sort="' . $media_object->lastChangeTimestamp(true) . '">' . $media_object->lastChangeTimestamp() . '</td>';
1385
+                $html .= '</tr>';
1386
+            }
1387
+        }
1388
+        $html .= '</tbody></table></div>';
1389
+
1390
+        return $html;
1391
+    }
1392
+
1393
+    /**
1394
+     * Print a table of surnames, for the top surnames block, the indi/fam lists, etc.
1395
+     *
1396
+     * @param string[][] $surnames array (of SURN, of array of SPFX_SURN, of array of PID)
1397
+     * @param string $script "indilist.php" (counts of individuals) or "famlist.php" (counts of spouses)
1398
+     * @param Tree $tree generate links for this tree
1399
+     *
1400
+     * @return string
1401
+     */
1402
+    public static function surnameTable($surnames, $script, Tree $tree) {
1403
+        global $controller;
1404
+
1405
+        $html = '';
1406
+        $controller
1407
+            ->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
1408
+            ->addInlineJavascript('
1409 1409
 				jQuery.fn.dataTableExt.oSort["text-asc"] = textCompareAsc;
1410 1410
 				jQuery.fn.dataTableExt.oSort["text-desc"] = textCompareDesc;
1411 1411
 				jQuery(".surname-list").dataTable({
@@ -1422,203 +1422,203 @@  discard block
 block discarded – undo
1422 1422
 				});
1423 1423
 			');
1424 1424
 
1425
-		if ($script == 'famlist.php') {
1426
-			$col_heading = I18N::translate('Spouses');
1427
-		} else {
1428
-			$col_heading = I18N::translate('Individuals');
1429
-		}
1430
-
1431
-		$html .=
1432
-			'<table class="surname-list">' .
1433
-			'<thead>' .
1434
-			'<tr>' .
1435
-			'<th>' . GedcomTag::getLabel('SURN') . '</th>' .
1436
-			'<th>' . $col_heading . '</th>' .
1437
-			'</tr>' .
1438
-			'</thead>';
1439
-
1440
-		$html .= '<tbody>';
1441
-		foreach ($surnames as $surn => $surns) {
1442
-			// Each surname links back to the indi/fam surname list
1443
-			if ($surn) {
1444
-				$url = $script . '?surname=' . rawurlencode($surn) . '&amp;ged=' . $tree->getNameUrl();
1445
-			} else {
1446
-				$url = $script . '?alpha=,&amp;ged=' . $tree->getNameUrl();
1447
-			}
1448
-			$html .= '<tr>';
1449
-			// Surname
1450
-			$html .= '<td data-sort="' . Filter::escapeHtml($surn) . '">';
1451
-			// Multiple surname variants, e.g. von Groot, van Groot, van der Groot, etc.
1452
-			foreach ($surns as $spfxsurn => $indis) {
1453
-				if ($spfxsurn) {
1454
-					$html .= '<a href="' . $url . '" dir="auto">' . Filter::escapeHtml($spfxsurn) . '</a><br>';
1455
-				} else {
1456
-					// No surname, but a value from "2 SURN"? A common workaround for toponyms, etc.
1457
-					$html .= '<a href="' . $url . '" dir="auto">' . Filter::escapeHtml($surn) . '</a><br>';
1458
-				}
1459
-			}
1460
-			$html .= '</td>';
1461
-			// Surname count
1462
-			$subtotal = 0;
1463
-			foreach ($surns as $indis) {
1464
-				$subtotal += count($indis);
1465
-			}
1466
-			$html .= '<td class="center" data-sort="' . $subtotal . '">';
1467
-			foreach ($surns as $indis) {
1468
-				$html .= I18N::number(count($indis)) . '<br>';
1469
-			}
1470
-			if (count($surns) > 1) {
1471
-				// More than one surname variant? Show a subtotal
1472
-				$html .= I18N::number($subtotal);
1473
-			}
1474
-			$html .= '</td>';
1475
-			$html .= '</tr>';
1476
-		}
1477
-		$html .= '</tbody></table>';
1478
-
1479
-		return $html;
1480
-	}
1481
-
1482
-	/**
1483
-	 * Print a tagcloud of surnames.
1484
-	 *
1485
-	 * @param string[][] $surnames array (of SURN, of array of SPFX_SURN, of array of PID)
1486
-	 * @param string $script indilist or famlist
1487
-	 * @param bool $totals show totals after each name
1488
-	 * @param Tree $tree generate links to this tree
1489
-	 *
1490
-	 * @return string
1491
-	 */
1492
-	public static function surnameTagCloud($surnames, $script, $totals, Tree $tree) {
1493
-		$minimum = PHP_INT_MAX;
1494
-		$maximum = 1;
1495
-		foreach ($surnames as $surn => $surns) {
1496
-			foreach ($surns as $spfxsurn => $indis) {
1497
-				$maximum = max($maximum, count($indis));
1498
-				$minimum = min($minimum, count($indis));
1499
-			}
1500
-		}
1501
-
1502
-		$html = '';
1503
-		foreach ($surnames as $surn => $surns) {
1504
-			foreach ($surns as $spfxsurn => $indis) {
1505
-				if ($maximum === $minimum) {
1506
-					// All surnames occur the same number of times
1507
-					$size = 150.0;
1508
-				} else {
1509
-					$size = 75.0 + 125.0 * (count($indis) - $minimum) / ($maximum - $minimum);
1510
-				}
1511
-				$html .= '<a style="font-size:' . $size . '%" href="' . $script . '?surname=' . Filter::escapeUrl($surn) . '&amp;ged=' . $tree->getNameUrl() . '">';
1512
-				if ($totals) {
1513
-					$html .= I18N::translate('%1$s (%2$s)', '<span dir="auto">' . $spfxsurn . '</span>', I18N::number(count($indis)));
1514
-				} else {
1515
-					$html .= $spfxsurn;
1516
-				}
1517
-				$html .= '</a> ';
1518
-			}
1519
-		}
1520
-
1521
-		return '<div class="tag_cloud">' . $html . '</div>';
1522
-	}
1523
-
1524
-	/**
1525
-	 * Print a list of surnames.
1526
-	 *
1527
-	 * @param string[][] $surnames array (of SURN, of array of SPFX_SURN, of array of PID)
1528
-	 * @param int $style 1=bullet list, 2=semicolon-separated list, 3=tabulated list with up to 4 columns
1529
-	 * @param bool $totals show totals after each name
1530
-	 * @param string $script indilist or famlist
1531
-	 * @param Tree $tree Link back to the individual list in this tree
1532
-	 *
1533
-	 * @return string
1534
-	 */
1535
-	public static function surnameList($surnames, $style, $totals, $script, Tree $tree) {
1536
-		$html = array();
1537
-		foreach ($surnames as $surn => $surns) {
1538
-			// Each surname links back to the indilist
1539
-			if ($surn) {
1540
-				$url = $script . '?surname=' . urlencode($surn) . '&amp;ged=' . $tree->getNameUrl();
1541
-			} else {
1542
-				$url = $script . '?alpha=,&amp;ged=' . $tree->getNameUrl();
1543
-			}
1544
-			// If all the surnames are just case variants, then merge them into one
1545
-			// Comment out this block if you want SMITH listed separately from Smith
1546
-			$first_spfxsurn = null;
1547
-			foreach ($surns as $spfxsurn => $indis) {
1548
-				if ($first_spfxsurn) {
1549
-					if (I18N::strtoupper($spfxsurn) == I18N::strtoupper($first_spfxsurn)) {
1550
-						$surns[$first_spfxsurn] = array_merge($surns[$first_spfxsurn], $surns[$spfxsurn]);
1551
-						unset($surns[$spfxsurn]);
1552
-					}
1553
-				} else {
1554
-					$first_spfxsurn = $spfxsurn;
1555
-				}
1556
-			}
1557
-			$subhtml = '<a href="' . $url . '" dir="auto">' . Filter::escapeHtml(implode(I18N::$list_separator, array_keys($surns))) . '</a>';
1558
-
1559
-			if ($totals) {
1560
-				$subtotal = 0;
1561
-				foreach ($surns as $indis) {
1562
-					$subtotal += count($indis);
1563
-				}
1564
-				$subhtml .= '&nbsp;(' . I18N::number($subtotal) . ')';
1565
-			}
1566
-			$html[] = $subhtml;
1567
-
1568
-		}
1569
-		switch ($style) {
1570
-		case 1:
1571
-			return '<ul><li>' . implode('</li><li>', $html) . '</li></ul>';
1572
-		case 2:
1573
-			return implode(I18N::$list_separator, $html);
1574
-		case 3:
1575
-			$i     = 0;
1576
-			$count = count($html);
1577
-			if ($count > 36) {
1578
-				$col = 4;
1579
-			} elseif ($count > 18) {
1580
-				$col = 3;
1581
-			} elseif ($count > 6) {
1582
-				$col = 2;
1583
-			} else {
1584
-				$col = 1;
1585
-			}
1586
-			$newcol = ceil($count / $col);
1587
-			$html2  = '<table class="list_table"><tr>';
1588
-			$html2 .= '<td class="list_value" style="padding: 14px;">';
1589
-
1590
-			foreach ($html as $surns) {
1591
-				$html2 .= $surns . '<br>';
1592
-				$i++;
1593
-				if ($i == $newcol && $i < $count) {
1594
-					$html2 .= '</td><td class="list_value" style="padding: 14px;">';
1595
-					$newcol = $i + ceil($count / $col);
1596
-				}
1597
-			}
1598
-			$html2 .= '</td></tr></table>';
1599
-
1600
-			return $html2;
1601
-		}
1602
-	}
1603
-	/**
1604
-	 * Print a table of events
1605
-	 *
1606
-	 * @param int $startjd
1607
-	 * @param int $endjd
1608
-	 * @param string $events
1609
-	 * @param bool $only_living
1610
-	 * @param string $sort_by
1611
-	 *
1612
-	 * @return string
1613
-	 */
1614
-	public static function eventsTable($startjd, $endjd, $events = 'BIRT MARR DEAT', $only_living = false, $sort_by = 'anniv') {
1615
-		global $controller, $WT_TREE;
1616
-
1617
-		$html     = '';
1618
-		$table_id = 'table-even-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page
1619
-		$controller
1620
-			->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
1621
-			->addInlineJavascript('
1425
+        if ($script == 'famlist.php') {
1426
+            $col_heading = I18N::translate('Spouses');
1427
+        } else {
1428
+            $col_heading = I18N::translate('Individuals');
1429
+        }
1430
+
1431
+        $html .=
1432
+            '<table class="surname-list">' .
1433
+            '<thead>' .
1434
+            '<tr>' .
1435
+            '<th>' . GedcomTag::getLabel('SURN') . '</th>' .
1436
+            '<th>' . $col_heading . '</th>' .
1437
+            '</tr>' .
1438
+            '</thead>';
1439
+
1440
+        $html .= '<tbody>';
1441
+        foreach ($surnames as $surn => $surns) {
1442
+            // Each surname links back to the indi/fam surname list
1443
+            if ($surn) {
1444
+                $url = $script . '?surname=' . rawurlencode($surn) . '&amp;ged=' . $tree->getNameUrl();
1445
+            } else {
1446
+                $url = $script . '?alpha=,&amp;ged=' . $tree->getNameUrl();
1447
+            }
1448
+            $html .= '<tr>';
1449
+            // Surname
1450
+            $html .= '<td data-sort="' . Filter::escapeHtml($surn) . '">';
1451
+            // Multiple surname variants, e.g. von Groot, van Groot, van der Groot, etc.
1452
+            foreach ($surns as $spfxsurn => $indis) {
1453
+                if ($spfxsurn) {
1454
+                    $html .= '<a href="' . $url . '" dir="auto">' . Filter::escapeHtml($spfxsurn) . '</a><br>';
1455
+                } else {
1456
+                    // No surname, but a value from "2 SURN"? A common workaround for toponyms, etc.
1457
+                    $html .= '<a href="' . $url . '" dir="auto">' . Filter::escapeHtml($surn) . '</a><br>';
1458
+                }
1459
+            }
1460
+            $html .= '</td>';
1461
+            // Surname count
1462
+            $subtotal = 0;
1463
+            foreach ($surns as $indis) {
1464
+                $subtotal += count($indis);
1465
+            }
1466
+            $html .= '<td class="center" data-sort="' . $subtotal . '">';
1467
+            foreach ($surns as $indis) {
1468
+                $html .= I18N::number(count($indis)) . '<br>';
1469
+            }
1470
+            if (count($surns) > 1) {
1471
+                // More than one surname variant? Show a subtotal
1472
+                $html .= I18N::number($subtotal);
1473
+            }
1474
+            $html .= '</td>';
1475
+            $html .= '</tr>';
1476
+        }
1477
+        $html .= '</tbody></table>';
1478
+
1479
+        return $html;
1480
+    }
1481
+
1482
+    /**
1483
+     * Print a tagcloud of surnames.
1484
+     *
1485
+     * @param string[][] $surnames array (of SURN, of array of SPFX_SURN, of array of PID)
1486
+     * @param string $script indilist or famlist
1487
+     * @param bool $totals show totals after each name
1488
+     * @param Tree $tree generate links to this tree
1489
+     *
1490
+     * @return string
1491
+     */
1492
+    public static function surnameTagCloud($surnames, $script, $totals, Tree $tree) {
1493
+        $minimum = PHP_INT_MAX;
1494
+        $maximum = 1;
1495
+        foreach ($surnames as $surn => $surns) {
1496
+            foreach ($surns as $spfxsurn => $indis) {
1497
+                $maximum = max($maximum, count($indis));
1498
+                $minimum = min($minimum, count($indis));
1499
+            }
1500
+        }
1501
+
1502
+        $html = '';
1503
+        foreach ($surnames as $surn => $surns) {
1504
+            foreach ($surns as $spfxsurn => $indis) {
1505
+                if ($maximum === $minimum) {
1506
+                    // All surnames occur the same number of times
1507
+                    $size = 150.0;
1508
+                } else {
1509
+                    $size = 75.0 + 125.0 * (count($indis) - $minimum) / ($maximum - $minimum);
1510
+                }
1511
+                $html .= '<a style="font-size:' . $size . '%" href="' . $script . '?surname=' . Filter::escapeUrl($surn) . '&amp;ged=' . $tree->getNameUrl() . '">';
1512
+                if ($totals) {
1513
+                    $html .= I18N::translate('%1$s (%2$s)', '<span dir="auto">' . $spfxsurn . '</span>', I18N::number(count($indis)));
1514
+                } else {
1515
+                    $html .= $spfxsurn;
1516
+                }
1517
+                $html .= '</a> ';
1518
+            }
1519
+        }
1520
+
1521
+        return '<div class="tag_cloud">' . $html . '</div>';
1522
+    }
1523
+
1524
+    /**
1525
+     * Print a list of surnames.
1526
+     *
1527
+     * @param string[][] $surnames array (of SURN, of array of SPFX_SURN, of array of PID)
1528
+     * @param int $style 1=bullet list, 2=semicolon-separated list, 3=tabulated list with up to 4 columns
1529
+     * @param bool $totals show totals after each name
1530
+     * @param string $script indilist or famlist
1531
+     * @param Tree $tree Link back to the individual list in this tree
1532
+     *
1533
+     * @return string
1534
+     */
1535
+    public static function surnameList($surnames, $style, $totals, $script, Tree $tree) {
1536
+        $html = array();
1537
+        foreach ($surnames as $surn => $surns) {
1538
+            // Each surname links back to the indilist
1539
+            if ($surn) {
1540
+                $url = $script . '?surname=' . urlencode($surn) . '&amp;ged=' . $tree->getNameUrl();
1541
+            } else {
1542
+                $url = $script . '?alpha=,&amp;ged=' . $tree->getNameUrl();
1543
+            }
1544
+            // If all the surnames are just case variants, then merge them into one
1545
+            // Comment out this block if you want SMITH listed separately from Smith
1546
+            $first_spfxsurn = null;
1547
+            foreach ($surns as $spfxsurn => $indis) {
1548
+                if ($first_spfxsurn) {
1549
+                    if (I18N::strtoupper($spfxsurn) == I18N::strtoupper($first_spfxsurn)) {
1550
+                        $surns[$first_spfxsurn] = array_merge($surns[$first_spfxsurn], $surns[$spfxsurn]);
1551
+                        unset($surns[$spfxsurn]);
1552
+                    }
1553
+                } else {
1554
+                    $first_spfxsurn = $spfxsurn;
1555
+                }
1556
+            }
1557
+            $subhtml = '<a href="' . $url . '" dir="auto">' . Filter::escapeHtml(implode(I18N::$list_separator, array_keys($surns))) . '</a>';
1558
+
1559
+            if ($totals) {
1560
+                $subtotal = 0;
1561
+                foreach ($surns as $indis) {
1562
+                    $subtotal += count($indis);
1563
+                }
1564
+                $subhtml .= '&nbsp;(' . I18N::number($subtotal) . ')';
1565
+            }
1566
+            $html[] = $subhtml;
1567
+
1568
+        }
1569
+        switch ($style) {
1570
+        case 1:
1571
+            return '<ul><li>' . implode('</li><li>', $html) . '</li></ul>';
1572
+        case 2:
1573
+            return implode(I18N::$list_separator, $html);
1574
+        case 3:
1575
+            $i     = 0;
1576
+            $count = count($html);
1577
+            if ($count > 36) {
1578
+                $col = 4;
1579
+            } elseif ($count > 18) {
1580
+                $col = 3;
1581
+            } elseif ($count > 6) {
1582
+                $col = 2;
1583
+            } else {
1584
+                $col = 1;
1585
+            }
1586
+            $newcol = ceil($count / $col);
1587
+            $html2  = '<table class="list_table"><tr>';
1588
+            $html2 .= '<td class="list_value" style="padding: 14px;">';
1589
+
1590
+            foreach ($html as $surns) {
1591
+                $html2 .= $surns . '<br>';
1592
+                $i++;
1593
+                if ($i == $newcol && $i < $count) {
1594
+                    $html2 .= '</td><td class="list_value" style="padding: 14px;">';
1595
+                    $newcol = $i + ceil($count / $col);
1596
+                }
1597
+            }
1598
+            $html2 .= '</td></tr></table>';
1599
+
1600
+            return $html2;
1601
+        }
1602
+    }
1603
+    /**
1604
+     * Print a table of events
1605
+     *
1606
+     * @param int $startjd
1607
+     * @param int $endjd
1608
+     * @param string $events
1609
+     * @param bool $only_living
1610
+     * @param string $sort_by
1611
+     *
1612
+     * @return string
1613
+     */
1614
+    public static function eventsTable($startjd, $endjd, $events = 'BIRT MARR DEAT', $only_living = false, $sort_by = 'anniv') {
1615
+        global $controller, $WT_TREE;
1616
+
1617
+        $html     = '';
1618
+        $table_id = 'table-even-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page
1619
+        $controller
1620
+            ->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
1621
+            ->addInlineJavascript('
1622 1622
 				jQuery.fn.dataTableExt.oSort["text-asc"] = textCompareAsc;
1623 1623
 				jQuery.fn.dataTableExt.oSort["text-desc"] = textCompareDesc;
1624 1624
 				jQuery("#' . $table_id . '").dataTable({
@@ -1640,341 +1640,341 @@  discard block
 block discarded – undo
1640 1640
 				});
1641 1641
 			');
1642 1642
 
1643
-		// Did we have any output? Did we skip anything?
1644
-		$filter          = 0;
1645
-		$filtered_events = array();
1646
-
1647
-		foreach (FunctionsDb::getEventsList($startjd, $endjd, $events, $WT_TREE) as $fact) {
1648
-			$record = $fact->getParent();
1649
-			// Only living people ?
1650
-			if ($only_living) {
1651
-				if ($record instanceof Individual && $record->isDead()) {
1652
-					$filter++;
1653
-					continue;
1654
-				}
1655
-				if ($record instanceof Family) {
1656
-					$husb = $record->getHusband();
1657
-					if (is_null($husb) || $husb->isDead()) {
1658
-						$filter++;
1659
-						continue;
1660
-					}
1661
-					$wife = $record->getWife();
1662
-					if (is_null($wife) || $wife->isDead()) {
1663
-						$filter++;
1664
-						continue;
1665
-					}
1666
-				}
1667
-			}
1668
-
1669
-			$filtered_events[] = $fact;
1670
-		}
1671
-
1672
-		if (!empty($filtered_events)) {
1673
-			$html .= '<table id="' . $table_id . '" class="width100">';
1674
-			$html .= '<thead><tr>';
1675
-			$html .= '<th>' . I18N::translate('Record') . '</th>';
1676
-			$html .= '<th>' . GedcomTag::getLabel('DATE') . '</th>';
1677
-			$html .= '<th><i class="icon-reminder" title="' . I18N::translate('Anniversary') . '"></i></th>';
1678
-			$html .= '<th>' . GedcomTag::getLabel('EVEN') . '</th>';
1679
-			$html .= '</tr></thead><tbody>';
1680
-
1681
-			foreach ($filtered_events as $n => $fact) {
1682
-				$record = $fact->getParent();
1683
-				$html .= '<tr>';
1684
-				$html .= '<td data-sort="' . Filter::escapeHtml($record->getSortName()) . '">';
1685
-				$html .= '<a href="' . $record->getHtmlUrl() . '">' . $record->getFullName() . '</a>';
1686
-				if ($record instanceof Individual) {
1687
-					$html .= $record->getSexImage();
1688
-				}
1689
-				$html .= '</td>';
1690
-				$html .= '<td data-sort="' . $fact->getDate()->minimumJulianDay() . '">';
1691
-				$html .= $fact->getDate()->display();
1692
-				$html .= '</td>';
1693
-				$html .= '<td class="center" data-sort="' . $fact->anniv . '">';
1694
-				$html .= ($fact->anniv ? I18N::number($fact->anniv) : '');
1695
-				$html .= '</td>';
1696
-				$html .= '<td class="center">' . $fact->getLabel() . '</td>';
1697
-				$html .= '</tr>';
1698
-			}
1699
-
1700
-			$html .= '</tbody></table>';
1701
-		} else {
1702
-			if ($endjd === WT_CLIENT_JD) {
1703
-				// We're dealing with the Today’s Events block
1704
-				if ($filter === 0) {
1705
-					$html .=  I18N::translate('No events exist for today.');
1706
-				} else {
1707
-					$html .=  I18N::translate('No events for living individuals exist for today.');
1708
-				}
1709
-			} else {
1710
-				// We're dealing with the Upcoming Events block
1711
-				if ($filter === 0) {
1712
-					if ($endjd === $startjd) {
1713
-						$html .=  I18N::translate('No events exist for tomorrow.');
1714
-					} else {
1715
-						$html .=  /* I18N: translation for %s==1 is unused; it is translated separately as “tomorrow” */ I18N::plural('No events exist for the next %s day.', 'No events exist for the next %s days.', $endjd - $startjd + 1, I18N::number($endjd - $startjd + 1));
1716
-					}
1717
-				} else {
1718
-					if ($endjd === $startjd) {
1719
-						$html .=  I18N::translate('No events for living individuals exist for tomorrow.');
1720
-					} else {
1721
-						// I18N: translation for %s==1 is unused; it is translated separately as “tomorrow”
1722
-						$html .=  I18N::plural('No events for living people exist for the next %s day.', 'No events for living people exist for the next %s days.', $endjd - $startjd + 1, I18N::number($endjd - $startjd + 1));
1723
-					}
1724
-				}
1725
-			}
1726
-		}
1727
-
1728
-		return $html;
1729
-	}
1730
-
1731
-	/**
1732
-	 * Print a list of events
1733
-	 *
1734
-	 * This performs the same function as print_events_table(), but formats the output differently.
1735
-	 *
1736
-	 * @param int $startjd
1737
-	 * @param int $endjd
1738
-	 * @param string $events
1739
-	 * @param bool $only_living
1740
-	 * @param string $sort_by
1741
-	 *
1742
-	 * @return string
1743
-	 */
1744
-	public static function eventsList($startjd, $endjd, $events = 'BIRT MARR DEAT', $only_living = false, $sort_by = 'anniv') {
1745
-		global $WT_TREE;
1746
-
1747
-		// Did we have any output? Did we skip anything?
1748
-		$output          = 0;
1749
-		$filter          = 0;
1750
-		$filtered_events = array();
1751
-		$html            = '';
1752
-		foreach (FunctionsDb::getEventsList($startjd, $endjd, $events, $WT_TREE) as $fact) {
1753
-			$record = $fact->getParent();
1754
-			// only living people ?
1755
-			if ($only_living) {
1756
-				if ($record instanceof Individual && $record->isDead()) {
1757
-					$filter++;
1758
-					continue;
1759
-				}
1760
-				if ($record instanceof Family) {
1761
-					$husb = $record->getHusband();
1762
-					if (is_null($husb) || $husb->isDead()) {
1763
-						$filter++;
1764
-						continue;
1765
-					}
1766
-					$wife = $record->getWife();
1767
-					if (is_null($wife) || $wife->isDead()) {
1768
-						$filter++;
1769
-						continue;
1770
-					}
1771
-				}
1772
-			}
1773
-
1774
-			$output++;
1775
-
1776
-			$filtered_events[] = $fact;
1777
-		}
1778
-
1779
-		// Now we've filtered the list, we can sort by event, if required
1780
-		switch ($sort_by) {
1781
-		case 'anniv':
1782
-			// Data is already sorted by anniversary date
1783
-			break;
1784
-		case 'alpha':
1785
-			uasort($filtered_events, function (Fact $x, Fact $y) {
1786
-				return GedcomRecord::compare($x->getParent(), $y->getParent());
1787
-			});
1788
-			break;
1789
-		}
1790
-
1791
-		foreach ($filtered_events as $fact) {
1792
-			$record = $fact->getParent();
1793
-			$html .= '<a href="' . $record->getHtmlUrl() . '" class="list_item name2">' . $record->getFullName() . '</a>';
1794
-			if ($record instanceof Individual) {
1795
-				$html .= $record->getSexImage();
1796
-			}
1797
-			$html .= '<br><div class="indent">';
1798
-			$html .= $fact->getLabel() . ' — ' . $fact->getDate()->display(true);
1799
-			if ($fact->anniv) {
1800
-				$html .= ' (' . I18N::translate('%s year anniversary', I18N::number($fact->anniv)) . ')';
1801
-			}
1802
-			if (!$fact->getPlace()->isEmpty()) {
1803
-				$html .= ' — <a href="' . $fact->getPlace()->getURL() . '">' . $fact->getPlace()->getFullName() . '</a>';
1804
-			}
1805
-			$html .= '</div>';
1806
-		}
1807
-
1808
-		// Print a final summary message about restricted/filtered facts
1809
-		$summary = '';
1810
-		if ($endjd == WT_CLIENT_JD) {
1811
-			// We're dealing with the Today’s Events block
1812
-			if ($output == 0) {
1813
-				if ($filter == 0) {
1814
-					$summary = I18N::translate('No events exist for today.');
1815
-				} else {
1816
-					$summary = I18N::translate('No events for living individuals exist for today.');
1817
-				}
1818
-			}
1819
-		} else {
1820
-			// We're dealing with the Upcoming Events block
1821
-			if ($output == 0) {
1822
-				if ($filter == 0) {
1823
-					if ($endjd == $startjd) {
1824
-						$summary = I18N::translate('No events exist for tomorrow.');
1825
-					} else {
1826
-						// I18N: translation for %s==1 is unused; it is translated separately as “tomorrow”
1827
-						$summary = I18N::plural('No events exist for the next %s day.', 'No events exist for the next %s days.', $endjd - $startjd + 1, I18N::number($endjd - $startjd + 1));
1828
-					}
1829
-				} else {
1830
-					if ($endjd == $startjd) {
1831
-						$summary = I18N::translate('No events for living individuals exist for tomorrow.');
1832
-					} else {
1833
-						// I18N: translation for %s==1 is unused; it is translated separately as “tomorrow”
1834
-						$summary = I18N::plural('No events for living people exist for the next %s day.', 'No events for living people exist for the next %s days.', $endjd - $startjd + 1, I18N::number($endjd - $startjd + 1));
1835
-					}
1836
-				}
1837
-			}
1838
-		}
1839
-		if ($summary) {
1840
-			$html .= '<b>' . $summary . '</b>';
1841
-		}
1842
-
1843
-		return $html;
1844
-	}
1845
-
1846
-	/**
1847
-	 * Print a chart by age using Google chart API
1848
-	 *
1849
-	 * @param int[] $data
1850
-	 * @param string $title
1851
-	 *
1852
-	 * @return string
1853
-	 */
1854
-	public static function chartByAge($data, $title) {
1855
-		$count  = 0;
1856
-		$agemax = 0;
1857
-		$vmax   = 0;
1858
-		$avg    = 0;
1859
-		foreach ($data as $age => $v) {
1860
-			$n      = strlen($v);
1861
-			$vmax   = max($vmax, $n);
1862
-			$agemax = max($agemax, $age);
1863
-			$count += $n;
1864
-			$avg += $age * $n;
1865
-		}
1866
-		if ($count < 1) {
1867
-			return '';
1868
-		}
1869
-		$avg       = round($avg / $count);
1870
-		$chart_url = "https://chart.googleapis.com/chart?cht=bvs"; // chart type
1871
-		$chart_url .= "&amp;chs=725x150"; // size
1872
-		$chart_url .= "&amp;chbh=3,2,2"; // bvg : 4,1,2
1873
-		$chart_url .= "&amp;chf=bg,s,FFFFFF99"; //background color
1874
-		$chart_url .= "&amp;chco=0000FF,FFA0CB,FF0000"; // bar color
1875
-		$chart_url .= "&amp;chdl=" . rawurlencode(I18N::translate('Males')) . "|" . rawurlencode(I18N::translate('Females')) . "|" . rawurlencode(I18N::translate('Average age') . ": " . $avg); // legend & average age
1876
-		$chart_url .= "&amp;chtt=" . rawurlencode($title); // title
1877
-		$chart_url .= "&amp;chxt=x,y,r"; // axis labels specification
1878
-		$chart_url .= "&amp;chm=V,FF0000,0," . ($avg - 0.3) . ",1"; // average age line marker
1879
-		$chart_url .= "&amp;chxl=0:|"; // label
1880
-		for ($age = 0; $age <= $agemax; $age += 5) {
1881
-			$chart_url .= $age . "|||||"; // x axis
1882
-		}
1883
-		$chart_url .= "|1:||" . rawurlencode(I18N::percentage($vmax / $count)); // y axis
1884
-		$chart_url .= "|2:||";
1885
-		$step = $vmax;
1886
-		for ($d = $vmax; $d > 0; $d--) {
1887
-			if ($vmax < ($d * 10 + 1) && ($vmax % $d) == 0) {
1888
-				$step = $d;
1889
-			}
1890
-		}
1891
-		if ($step == $vmax) {
1892
-			for ($d = $vmax - 1; $d > 0; $d--) {
1893
-				if (($vmax - 1) < ($d * 10 + 1) && (($vmax - 1) % $d) == 0) {
1894
-					$step = $d;
1895
-				}
1896
-			}
1897
-		}
1898
-		for ($n = $step; $n < $vmax; $n += $step) {
1899
-			$chart_url .= $n . "|";
1900
-		}
1901
-		$chart_url .= rawurlencode($vmax . " / " . $count); // r axis
1902
-		$chart_url .= "&amp;chg=100," . round(100 * $step / $vmax, 1) . ",1,5"; // grid
1903
-		$chart_url .= "&amp;chd=s:"; // data : simple encoding from A=0 to 9=61
1904
-		$CHART_ENCODING61 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
1905
-		for ($age = 0; $age <= $agemax; $age++) {
1906
-			$chart_url .= $CHART_ENCODING61[(int) (substr_count($data[$age], "M") * 61 / $vmax)];
1907
-		}
1908
-		$chart_url .= ",";
1909
-		for ($age = 0; $age <= $agemax; $age++) {
1910
-			$chart_url .= $CHART_ENCODING61[(int) (substr_count($data[$age], "F") * 61 / $vmax)];
1911
-		}
1912
-		$html = '<img src="' . $chart_url . '" alt="' . $title . '" title="' . $title . '" class="gchart">';
1913
-
1914
-		return $html;
1915
-	}
1916
-
1917
-	/**
1918
-	 * Print a chart by decade using Google chart API
1919
-	 *
1920
-	 * @param int[] $data
1921
-	 * @param string $title
1922
-	 *
1923
-	 * @return string
1924
-	 */
1925
-	public static function chartByDecade($data, $title) {
1926
-		$count = 0;
1927
-		$vmax  = 0;
1928
-		foreach ($data as $v) {
1929
-			$n    = strlen($v);
1930
-			$vmax = max($vmax, $n);
1931
-			$count += $n;
1932
-		}
1933
-		if ($count < 1) {
1934
-			return '';
1935
-		}
1936
-		$chart_url = "https://chart.googleapis.com/chart?cht=bvs"; // chart type
1937
-		$chart_url .= "&amp;chs=360x150"; // size
1938
-		$chart_url .= "&amp;chbh=3,3"; // bvg : 4,1,2
1939
-		$chart_url .= "&amp;chf=bg,s,FFFFFF99"; //background color
1940
-		$chart_url .= "&amp;chco=0000FF,FFA0CB"; // bar color
1941
-		$chart_url .= "&amp;chtt=" . rawurlencode($title); // title
1942
-		$chart_url .= "&amp;chxt=x,y,r"; // axis labels specification
1943
-		$chart_url .= "&amp;chxl=0:|&lt;|||"; // <1570
1944
-		for ($y = 1600; $y < 2030; $y += 50) {
1945
-			$chart_url .= $y . "|||||"; // x axis
1946
-		}
1947
-		$chart_url .= "|1:||" . rawurlencode(I18N::percentage($vmax / $count)); // y axis
1948
-		$chart_url .= "|2:||";
1949
-		$step = $vmax;
1950
-		for ($d = $vmax; $d > 0; $d--) {
1951
-			if ($vmax < ($d * 10 + 1) && ($vmax % $d) == 0) {
1952
-				$step = $d;
1953
-			}
1954
-		}
1955
-		if ($step == $vmax) {
1956
-			for ($d = $vmax - 1; $d > 0; $d--) {
1957
-				if (($vmax - 1) < ($d * 10 + 1) && (($vmax - 1) % $d) == 0) {
1958
-					$step = $d;
1959
-				}
1960
-			}
1961
-		}
1962
-		for ($n = $step; $n < $vmax; $n += $step) {
1963
-			$chart_url .= $n . "|";
1964
-		}
1965
-		$chart_url .= rawurlencode($vmax . " / " . $count); // r axis
1966
-		$chart_url .= "&amp;chg=100," . round(100 * $step / $vmax, 1) . ",1,5"; // grid
1967
-		$chart_url .= "&amp;chd=s:"; // data : simple encoding from A=0 to 9=61
1968
-		$CHART_ENCODING61 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
1969
-		for ($y = 1570; $y < 2030; $y += 10) {
1970
-			$chart_url .= $CHART_ENCODING61[(int) (substr_count($data[$y], "M") * 61 / $vmax)];
1971
-		}
1972
-		$chart_url .= ",";
1973
-		for ($y = 1570; $y < 2030; $y += 10) {
1974
-			$chart_url .= $CHART_ENCODING61[(int) (substr_count($data[$y], "F") * 61 / $vmax)];
1975
-		}
1976
-		$html = '<img src="' . $chart_url . '" alt="' . $title . '" title="' . $title . '" class="gchart">';
1977
-
1978
-		return $html;
1979
-	}
1643
+        // Did we have any output? Did we skip anything?
1644
+        $filter          = 0;
1645
+        $filtered_events = array();
1646
+
1647
+        foreach (FunctionsDb::getEventsList($startjd, $endjd, $events, $WT_TREE) as $fact) {
1648
+            $record = $fact->getParent();
1649
+            // Only living people ?
1650
+            if ($only_living) {
1651
+                if ($record instanceof Individual && $record->isDead()) {
1652
+                    $filter++;
1653
+                    continue;
1654
+                }
1655
+                if ($record instanceof Family) {
1656
+                    $husb = $record->getHusband();
1657
+                    if (is_null($husb) || $husb->isDead()) {
1658
+                        $filter++;
1659
+                        continue;
1660
+                    }
1661
+                    $wife = $record->getWife();
1662
+                    if (is_null($wife) || $wife->isDead()) {
1663
+                        $filter++;
1664
+                        continue;
1665
+                    }
1666
+                }
1667
+            }
1668
+
1669
+            $filtered_events[] = $fact;
1670
+        }
1671
+
1672
+        if (!empty($filtered_events)) {
1673
+            $html .= '<table id="' . $table_id . '" class="width100">';
1674
+            $html .= '<thead><tr>';
1675
+            $html .= '<th>' . I18N::translate('Record') . '</th>';
1676
+            $html .= '<th>' . GedcomTag::getLabel('DATE') . '</th>';
1677
+            $html .= '<th><i class="icon-reminder" title="' . I18N::translate('Anniversary') . '"></i></th>';
1678
+            $html .= '<th>' . GedcomTag::getLabel('EVEN') . '</th>';
1679
+            $html .= '</tr></thead><tbody>';
1680
+
1681
+            foreach ($filtered_events as $n => $fact) {
1682
+                $record = $fact->getParent();
1683
+                $html .= '<tr>';
1684
+                $html .= '<td data-sort="' . Filter::escapeHtml($record->getSortName()) . '">';
1685
+                $html .= '<a href="' . $record->getHtmlUrl() . '">' . $record->getFullName() . '</a>';
1686
+                if ($record instanceof Individual) {
1687
+                    $html .= $record->getSexImage();
1688
+                }
1689
+                $html .= '</td>';
1690
+                $html .= '<td data-sort="' . $fact->getDate()->minimumJulianDay() . '">';
1691
+                $html .= $fact->getDate()->display();
1692
+                $html .= '</td>';
1693
+                $html .= '<td class="center" data-sort="' . $fact->anniv . '">';
1694
+                $html .= ($fact->anniv ? I18N::number($fact->anniv) : '');
1695
+                $html .= '</td>';
1696
+                $html .= '<td class="center">' . $fact->getLabel() . '</td>';
1697
+                $html .= '</tr>';
1698
+            }
1699
+
1700
+            $html .= '</tbody></table>';
1701
+        } else {
1702
+            if ($endjd === WT_CLIENT_JD) {
1703
+                // We're dealing with the Today’s Events block
1704
+                if ($filter === 0) {
1705
+                    $html .=  I18N::translate('No events exist for today.');
1706
+                } else {
1707
+                    $html .=  I18N::translate('No events for living individuals exist for today.');
1708
+                }
1709
+            } else {
1710
+                // We're dealing with the Upcoming Events block
1711
+                if ($filter === 0) {
1712
+                    if ($endjd === $startjd) {
1713
+                        $html .=  I18N::translate('No events exist for tomorrow.');
1714
+                    } else {
1715
+                        $html .=  /* I18N: translation for %s==1 is unused; it is translated separately as “tomorrow” */ I18N::plural('No events exist for the next %s day.', 'No events exist for the next %s days.', $endjd - $startjd + 1, I18N::number($endjd - $startjd + 1));
1716
+                    }
1717
+                } else {
1718
+                    if ($endjd === $startjd) {
1719
+                        $html .=  I18N::translate('No events for living individuals exist for tomorrow.');
1720
+                    } else {
1721
+                        // I18N: translation for %s==1 is unused; it is translated separately as “tomorrow”
1722
+                        $html .=  I18N::plural('No events for living people exist for the next %s day.', 'No events for living people exist for the next %s days.', $endjd - $startjd + 1, I18N::number($endjd - $startjd + 1));
1723
+                    }
1724
+                }
1725
+            }
1726
+        }
1727
+
1728
+        return $html;
1729
+    }
1730
+
1731
+    /**
1732
+     * Print a list of events
1733
+     *
1734
+     * This performs the same function as print_events_table(), but formats the output differently.
1735
+     *
1736
+     * @param int $startjd
1737
+     * @param int $endjd
1738
+     * @param string $events
1739
+     * @param bool $only_living
1740
+     * @param string $sort_by
1741
+     *
1742
+     * @return string
1743
+     */
1744
+    public static function eventsList($startjd, $endjd, $events = 'BIRT MARR DEAT', $only_living = false, $sort_by = 'anniv') {
1745
+        global $WT_TREE;
1746
+
1747
+        // Did we have any output? Did we skip anything?
1748
+        $output          = 0;
1749
+        $filter          = 0;
1750
+        $filtered_events = array();
1751
+        $html            = '';
1752
+        foreach (FunctionsDb::getEventsList($startjd, $endjd, $events, $WT_TREE) as $fact) {
1753
+            $record = $fact->getParent();
1754
+            // only living people ?
1755
+            if ($only_living) {
1756
+                if ($record instanceof Individual && $record->isDead()) {
1757
+                    $filter++;
1758
+                    continue;
1759
+                }
1760
+                if ($record instanceof Family) {
1761
+                    $husb = $record->getHusband();
1762
+                    if (is_null($husb) || $husb->isDead()) {
1763
+                        $filter++;
1764
+                        continue;
1765
+                    }
1766
+                    $wife = $record->getWife();
1767
+                    if (is_null($wife) || $wife->isDead()) {
1768
+                        $filter++;
1769
+                        continue;
1770
+                    }
1771
+                }
1772
+            }
1773
+
1774
+            $output++;
1775
+
1776
+            $filtered_events[] = $fact;
1777
+        }
1778
+
1779
+        // Now we've filtered the list, we can sort by event, if required
1780
+        switch ($sort_by) {
1781
+        case 'anniv':
1782
+            // Data is already sorted by anniversary date
1783
+            break;
1784
+        case 'alpha':
1785
+            uasort($filtered_events, function (Fact $x, Fact $y) {
1786
+                return GedcomRecord::compare($x->getParent(), $y->getParent());
1787
+            });
1788
+            break;
1789
+        }
1790
+
1791
+        foreach ($filtered_events as $fact) {
1792
+            $record = $fact->getParent();
1793
+            $html .= '<a href="' . $record->getHtmlUrl() . '" class="list_item name2">' . $record->getFullName() . '</a>';
1794
+            if ($record instanceof Individual) {
1795
+                $html .= $record->getSexImage();
1796
+            }
1797
+            $html .= '<br><div class="indent">';
1798
+            $html .= $fact->getLabel() . ' — ' . $fact->getDate()->display(true);
1799
+            if ($fact->anniv) {
1800
+                $html .= ' (' . I18N::translate('%s year anniversary', I18N::number($fact->anniv)) . ')';
1801
+            }
1802
+            if (!$fact->getPlace()->isEmpty()) {
1803
+                $html .= ' — <a href="' . $fact->getPlace()->getURL() . '">' . $fact->getPlace()->getFullName() . '</a>';
1804
+            }
1805
+            $html .= '</div>';
1806
+        }
1807
+
1808
+        // Print a final summary message about restricted/filtered facts
1809
+        $summary = '';
1810
+        if ($endjd == WT_CLIENT_JD) {
1811
+            // We're dealing with the Today’s Events block
1812
+            if ($output == 0) {
1813
+                if ($filter == 0) {
1814
+                    $summary = I18N::translate('No events exist for today.');
1815
+                } else {
1816
+                    $summary = I18N::translate('No events for living individuals exist for today.');
1817
+                }
1818
+            }
1819
+        } else {
1820
+            // We're dealing with the Upcoming Events block
1821
+            if ($output == 0) {
1822
+                if ($filter == 0) {
1823
+                    if ($endjd == $startjd) {
1824
+                        $summary = I18N::translate('No events exist for tomorrow.');
1825
+                    } else {
1826
+                        // I18N: translation for %s==1 is unused; it is translated separately as “tomorrow”
1827
+                        $summary = I18N::plural('No events exist for the next %s day.', 'No events exist for the next %s days.', $endjd - $startjd + 1, I18N::number($endjd - $startjd + 1));
1828
+                    }
1829
+                } else {
1830
+                    if ($endjd == $startjd) {
1831
+                        $summary = I18N::translate('No events for living individuals exist for tomorrow.');
1832
+                    } else {
1833
+                        // I18N: translation for %s==1 is unused; it is translated separately as “tomorrow”
1834
+                        $summary = I18N::plural('No events for living people exist for the next %s day.', 'No events for living people exist for the next %s days.', $endjd - $startjd + 1, I18N::number($endjd - $startjd + 1));
1835
+                    }
1836
+                }
1837
+            }
1838
+        }
1839
+        if ($summary) {
1840
+            $html .= '<b>' . $summary . '</b>';
1841
+        }
1842
+
1843
+        return $html;
1844
+    }
1845
+
1846
+    /**
1847
+     * Print a chart by age using Google chart API
1848
+     *
1849
+     * @param int[] $data
1850
+     * @param string $title
1851
+     *
1852
+     * @return string
1853
+     */
1854
+    public static function chartByAge($data, $title) {
1855
+        $count  = 0;
1856
+        $agemax = 0;
1857
+        $vmax   = 0;
1858
+        $avg    = 0;
1859
+        foreach ($data as $age => $v) {
1860
+            $n      = strlen($v);
1861
+            $vmax   = max($vmax, $n);
1862
+            $agemax = max($agemax, $age);
1863
+            $count += $n;
1864
+            $avg += $age * $n;
1865
+        }
1866
+        if ($count < 1) {
1867
+            return '';
1868
+        }
1869
+        $avg       = round($avg / $count);
1870
+        $chart_url = "https://chart.googleapis.com/chart?cht=bvs"; // chart type
1871
+        $chart_url .= "&amp;chs=725x150"; // size
1872
+        $chart_url .= "&amp;chbh=3,2,2"; // bvg : 4,1,2
1873
+        $chart_url .= "&amp;chf=bg,s,FFFFFF99"; //background color
1874
+        $chart_url .= "&amp;chco=0000FF,FFA0CB,FF0000"; // bar color
1875
+        $chart_url .= "&amp;chdl=" . rawurlencode(I18N::translate('Males')) . "|" . rawurlencode(I18N::translate('Females')) . "|" . rawurlencode(I18N::translate('Average age') . ": " . $avg); // legend & average age
1876
+        $chart_url .= "&amp;chtt=" . rawurlencode($title); // title
1877
+        $chart_url .= "&amp;chxt=x,y,r"; // axis labels specification
1878
+        $chart_url .= "&amp;chm=V,FF0000,0," . ($avg - 0.3) . ",1"; // average age line marker
1879
+        $chart_url .= "&amp;chxl=0:|"; // label
1880
+        for ($age = 0; $age <= $agemax; $age += 5) {
1881
+            $chart_url .= $age . "|||||"; // x axis
1882
+        }
1883
+        $chart_url .= "|1:||" . rawurlencode(I18N::percentage($vmax / $count)); // y axis
1884
+        $chart_url .= "|2:||";
1885
+        $step = $vmax;
1886
+        for ($d = $vmax; $d > 0; $d--) {
1887
+            if ($vmax < ($d * 10 + 1) && ($vmax % $d) == 0) {
1888
+                $step = $d;
1889
+            }
1890
+        }
1891
+        if ($step == $vmax) {
1892
+            for ($d = $vmax - 1; $d > 0; $d--) {
1893
+                if (($vmax - 1) < ($d * 10 + 1) && (($vmax - 1) % $d) == 0) {
1894
+                    $step = $d;
1895
+                }
1896
+            }
1897
+        }
1898
+        for ($n = $step; $n < $vmax; $n += $step) {
1899
+            $chart_url .= $n . "|";
1900
+        }
1901
+        $chart_url .= rawurlencode($vmax . " / " . $count); // r axis
1902
+        $chart_url .= "&amp;chg=100," . round(100 * $step / $vmax, 1) . ",1,5"; // grid
1903
+        $chart_url .= "&amp;chd=s:"; // data : simple encoding from A=0 to 9=61
1904
+        $CHART_ENCODING61 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
1905
+        for ($age = 0; $age <= $agemax; $age++) {
1906
+            $chart_url .= $CHART_ENCODING61[(int) (substr_count($data[$age], "M") * 61 / $vmax)];
1907
+        }
1908
+        $chart_url .= ",";
1909
+        for ($age = 0; $age <= $agemax; $age++) {
1910
+            $chart_url .= $CHART_ENCODING61[(int) (substr_count($data[$age], "F") * 61 / $vmax)];
1911
+        }
1912
+        $html = '<img src="' . $chart_url . '" alt="' . $title . '" title="' . $title . '" class="gchart">';
1913
+
1914
+        return $html;
1915
+    }
1916
+
1917
+    /**
1918
+     * Print a chart by decade using Google chart API
1919
+     *
1920
+     * @param int[] $data
1921
+     * @param string $title
1922
+     *
1923
+     * @return string
1924
+     */
1925
+    public static function chartByDecade($data, $title) {
1926
+        $count = 0;
1927
+        $vmax  = 0;
1928
+        foreach ($data as $v) {
1929
+            $n    = strlen($v);
1930
+            $vmax = max($vmax, $n);
1931
+            $count += $n;
1932
+        }
1933
+        if ($count < 1) {
1934
+            return '';
1935
+        }
1936
+        $chart_url = "https://chart.googleapis.com/chart?cht=bvs"; // chart type
1937
+        $chart_url .= "&amp;chs=360x150"; // size
1938
+        $chart_url .= "&amp;chbh=3,3"; // bvg : 4,1,2
1939
+        $chart_url .= "&amp;chf=bg,s,FFFFFF99"; //background color
1940
+        $chart_url .= "&amp;chco=0000FF,FFA0CB"; // bar color
1941
+        $chart_url .= "&amp;chtt=" . rawurlencode($title); // title
1942
+        $chart_url .= "&amp;chxt=x,y,r"; // axis labels specification
1943
+        $chart_url .= "&amp;chxl=0:|&lt;|||"; // <1570
1944
+        for ($y = 1600; $y < 2030; $y += 50) {
1945
+            $chart_url .= $y . "|||||"; // x axis
1946
+        }
1947
+        $chart_url .= "|1:||" . rawurlencode(I18N::percentage($vmax / $count)); // y axis
1948
+        $chart_url .= "|2:||";
1949
+        $step = $vmax;
1950
+        for ($d = $vmax; $d > 0; $d--) {
1951
+            if ($vmax < ($d * 10 + 1) && ($vmax % $d) == 0) {
1952
+                $step = $d;
1953
+            }
1954
+        }
1955
+        if ($step == $vmax) {
1956
+            for ($d = $vmax - 1; $d > 0; $d--) {
1957
+                if (($vmax - 1) < ($d * 10 + 1) && (($vmax - 1) % $d) == 0) {
1958
+                    $step = $d;
1959
+                }
1960
+            }
1961
+        }
1962
+        for ($n = $step; $n < $vmax; $n += $step) {
1963
+            $chart_url .= $n . "|";
1964
+        }
1965
+        $chart_url .= rawurlencode($vmax . " / " . $count); // r axis
1966
+        $chart_url .= "&amp;chg=100," . round(100 * $step / $vmax, 1) . ",1,5"; // grid
1967
+        $chart_url .= "&amp;chd=s:"; // data : simple encoding from A=0 to 9=61
1968
+        $CHART_ENCODING61 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
1969
+        for ($y = 1570; $y < 2030; $y += 10) {
1970
+            $chart_url .= $CHART_ENCODING61[(int) (substr_count($data[$y], "M") * 61 / $vmax)];
1971
+        }
1972
+        $chart_url .= ",";
1973
+        for ($y = 1570; $y < 2030; $y += 10) {
1974
+            $chart_url .= $CHART_ENCODING61[(int) (substr_count($data[$y], "F") * 61 / $vmax)];
1975
+        }
1976
+        $html = '<img src="' . $chart_url . '" alt="' . $title . '" title="' . $title . '" class="gchart">';
1977
+
1978
+        return $html;
1979
+    }
1980 1980
 }
Please login to merge, or discard this patch.