.
*/
declare(strict_types=1);
namespace Fisharebest\Webtrees\Functions;
use Fisharebest\Webtrees\Age;
use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Elements\SubmitterText;
use Fisharebest\Webtrees\Elements\UnknownElement;
use Fisharebest\Webtrees\Fact;
use Fisharebest\Webtrees\Family;
use Fisharebest\Webtrees\Gedcom;
use Fisharebest\Webtrees\GedcomRecord;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Individual;
use Fisharebest\Webtrees\Media;
use Fisharebest\Webtrees\Module\ModuleChartInterface;
use Fisharebest\Webtrees\Module\ModuleInterface;
use Fisharebest\Webtrees\Module\RelationshipsChartModule;
use Fisharebest\Webtrees\Note;
use Fisharebest\Webtrees\Registry;
use Fisharebest\Webtrees\Services\ModuleService;
use Fisharebest\Webtrees\Services\RelationshipService;
use Fisharebest\Webtrees\Tree;
use Ramsey\Uuid\Uuid;
use function app;
use function array_merge;
use function count;
use function e;
use function explode;
use function implode;
use function ob_get_clean;
use function ob_start;
use function preg_match;
use function preg_match_all;
use function preg_replace;
use function str_contains;
use function strip_tags;
use function strlen;
use function strpos;
use function substr;
use function trim;
use function view;
use const PREG_SET_ORDER;
/**
* Class FunctionsPrintFacts - common functions
*
* @deprecated since 2.0.6. Will be removed in 2.1.0
*/
class FunctionsPrintFacts
{
/**
* Print a fact record, for the individual/family/source/repository/etc. pages.
* Although a Fact has a parent object, we also need to know
* the GedcomRecord for which we are printing it. For example,
* we can show the death of X on the page of Y, or the marriage
* of X+Y on the page of Z. We need to know both records to
* calculate ages, relationships, etc.
*
* @param Fact $fact
* @param GedcomRecord $record
*
* @return void
*/
public static function printFact(Fact $fact, GedcomRecord $record): void
{
$parent = $fact->record();
$tree = $parent->tree();
[, $tag] = explode(':', $fact->tag());
$label = $fact->label();
$value = $fact->value();
$type = $fact->attribute('TYPE');
$id = $fact->id();
$element = Registry::elementFactory()->make($fact->tag());
// This preference is named HIDE instead of SHOW
$hide_errors = $tree->getPreference('HIDE_GEDCOM_ERRORS') === '0';
// Some facts don't get printed here ...
switch ($tag) {
case 'NOTE':
self::printMainNotes($fact, 1);
return;
case 'SOUR':
self::printMainSources($fact, 1);
return;
case 'OBJE':
self::printMainMedia($fact, 1);
return;
case 'FAMC':
case 'FAMS':
case 'CHIL':
case 'HUSB':
case 'WIFE':
// These are internal links, not facts
return;
case '_WT_OBJE_SORT':
// These links were once used internally to record the sort order.
return;
default:
if ($element instanceof UnknownElement && $hide_errors) {
return;
}
break;
}
// New or deleted facts need different styling
$styles = [];
if ($fact->isPendingAddition()) {
$styles[] = 'wt-new';
}
if ($fact->isPendingDeletion()) {
$styles[] = 'wt-old';
}
// Event of close relative
if ($tag === 'EVEN' && $value === 'CLOSE_RELATIVE') {
$value = '';
$styles[] = 'wt-relation-fact collapse';
}
// Event of close associates
if ($id === 'asso') {
$styles[] = 'wt-associate-fact';
$styles[] = 'collapse';
}
// historical facts
if ($id === 'histo') {
$styles[] = 'wt-historic-fact';
$styles[] = 'collapse';
}
// Use marriage type as the label. e.g. "Civil partnership"
if ($tag === 'MARR') {
$label = $fact->label();
$type = '';
}
echo '
';
echo '| ';
echo $label;
if ($id !== 'histo' && $id !== 'asso' && $fact->canEdit()) {
echo ' ';
echo view('edit/icon-fact-edit', ['fact' => $fact, 'url' => $record->url()]);
echo view('edit/icon-fact-copy', ['fact' => $fact]);
echo view('edit/icon-fact-delete', ['fact' => $fact]);
echo ' ';
}
if ($tree->getPreference('SHOW_FACT_ICONS')) {
echo '';
}
echo ' | ';
echo '';
// Primary attributes (value/type/date/place)
echo '';
// Event from another record?
if ($parent !== $record) {
if ($parent instanceof Family) {
foreach ($parent->spouses() as $spouse) {
if ($record !== $spouse) {
echo ' ', $spouse->fullName(), ' — ';
}
}
echo ' ', I18N::translate('View this family'), '';
} elseif ($parent instanceof Individual) {
echo ' ', $parent->fullName(), '';
}
}
// Print the value of this fact/event
// we handle ASSO later, in formatAssociateRelationship()
if ($tag !== 'ASSO') {
echo ' ', $element->value($value, $tree), ' ';
}
// Print the type of this fact/event
if ($type !== '' && $tag !== 'EVEN' && $tag !== 'FACT') {
// Allow (custom) translations for other types
$type = I18N::translate($type);
echo Registry::elementFactory()->make($fact->tag() . ':TYPE')->labelValue($type, $tree);
}
echo FunctionsPrint::formatFactDate($fact, $record, true, true);
echo ' ', FunctionsPrint::formatFactPlace($fact, $fact->id() !== 'histo', true, true), ' ';
echo ' ';
// Secondary attributes
echo '';
preg_match_all('/\n2 (' . Gedcom::REGEX_TAG . ') ?(.*(?:\n3 CONT.*)*)((\n[3-9].*)*)/', $fact->gedcom(), $l2_matches, PREG_SET_ORDER);
foreach ($l2_matches as $l2_match) {
$value = preg_replace('/\n3 CONT ?/', "\n", $l2_match[2]);
switch ($l2_match[1]) {
case 'DATE':
case 'TIME':
case 'AGE':
case 'HUSB':
case 'WIFE':
case 'PLAC':
case 'ALIA':
case 'ASSO':
case '_ASSO':
case 'DESC':
case 'RELA':
case 'STAT':
case 'TEMP':
case 'TYPE':
case 'FAMS':
case 'CONT':
// These were already shown at the beginning
break;
case 'NOTE':
case 'OBJE':
case 'SOUR':
// These will be shown at the end
break;
default:
$subtag = $record::RECORD_TYPE . ':' . $tag . ':' . $l2_match[1];
$element = Registry::elementFactory()->make($subtag);
if ($element instanceof UnknownElement && $hide_errors) {
break;
}
echo $element->labelValue($value, $tree);
preg_match_all('/\n3 (' . Gedcom::REGEX_TAG . ') ?(.*)((\n[4-9].*)*)/', $l2_match[3], $l3_matches, PREG_SET_ORDER);
foreach ($l3_matches as $l3_match) {
$subsubtag = $subtag . ':' . $l3_match[1];
$element = Registry::elementFactory()->make($subsubtag);
if ($element instanceof UnknownElement && $hide_errors) {
continue;
}
echo $element->labelValue($l3_match[2], $tree);
preg_match_all('/\n4 (' . Gedcom::REGEX_TAG . ') ?(.*)((\n[5-9].*)*)/', $l3_match[3], $l4_matches, PREG_SET_ORDER);
foreach ($l4_matches as $l4_match) {
$subsubsubtag = $subsubtag . ':' . $l4_match[1];
$element = Registry::elementFactory()->make($subsubsubtag);
if ($element instanceof UnknownElement && $hide_errors) {
continue;
}
echo $element->labelValue($l4_match[2], $tree);
preg_match_all('/\n5 (' . Gedcom::REGEX_TAG . ') ?(.*)((\n[6-9].*)*)/', $l4_match[3], $l5_matches, PREG_SET_ORDER);
foreach ($l5_matches as $l5_match) {
$subsubsubsubtag = $subsubsubtag . ':' . $l5_match[1];
$element = Registry::elementFactory()->make($subsubsubsubtag);
if ($element instanceof UnknownElement && $hide_errors) {
continue;
}
echo $element->labelValue($l5_match[2], $tree);
}
}
}
break;
}
}
echo ' ';
// Print the associates of this fact/event
if ($id !== 'asso') {
echo '';
echo self::formatAssociateRelationship($fact);
echo ' ';
}
echo self::printFactSources($tree, $fact->gedcom(), 2);
echo FunctionsPrint::printFactNotes($tree, $fact->gedcom(), 2);
self::printMediaLinks($tree, $fact->gedcom(), 2);
echo ' |
';
}
/**
* Print the associations from the associated individuals in $event to the individuals in $record
*
* @param Fact $event
*
* @return string
*/
private static function formatAssociateRelationship(Fact $event): string
{
$parent = $event->record();
// To whom is this record an assocate?
if ($parent instanceof Individual) {
// On an individual page, we just show links to the person
$associates = [$parent];
} elseif ($parent instanceof Family) {
// On a family page, we show links to both spouses
$associates = $parent->spouses();
} else {
// On other pages, it does not make sense to show associates
return '';
}
preg_match_all('/^1 (ASSO) @(' . Gedcom::REGEX_XREF . ')@((\n[2-9].*)*)/', $event->gedcom(), $amatches1, PREG_SET_ORDER);
preg_match_all('/\n2 (_?ASSO) @(' . Gedcom::REGEX_XREF . ')@((\n[3-9].*)*)/', $event->gedcom(), $amatches2, PREG_SET_ORDER);
$html = '';
// For each ASSO record
foreach (array_merge($amatches1, $amatches2) as $amatch) {
$person = Registry::individualFactory()->make($amatch[2], $event->record()->tree());
if ($person && $person->canShowName()) {
// Is there a "RELA" tag
if (preg_match('/\n([23]) RELA (.+)/', $amatch[3], $rmatch)) {
if ($rmatch[1] === '2') {
$rela_tag = $event->record()->tag() . ':' . $amatch[1] . ':RELA';
} else {
$rela_tag = $event->tag() . ':' . $amatch[1] . ':RELA';
}
// Use the supplied relationship as a label
$label = Registry::elementFactory()->make($rela_tag)->value($rmatch[2], $parent->tree());
} elseif (preg_match('/^1 _?ASSO/', $event->gedcom())) {
// Use a default label
$label = Registry::elementFactory()->make($event->tag())->label();
} else {
// Use a default label
$label = Registry::elementFactory()->make($event->tag() . ':_ASSO')->label();
}
if ($person->getBirthDate()->isOK() && $event->date()->isOK()) {
$age = new Age($person->getBirthDate(), $event->date());
switch ($person->sex()) {
case 'M':
$age_text = ' ' . I18N::translateContext('Male', '(aged %s)', (string) $age);
break;
case 'F':
$age_text = ' ' . I18N::translateContext('Female', '(aged %s)', (string) $age);
break;
default:
$age_text = ' ' . I18N::translate('(aged %s)', (string) $age);
break;
}
} else {
$age_text = '';
}
$values = ['' . $person->fullName() . '' . $age_text];
$module = app(ModuleService::class)->findByComponent(ModuleChartInterface::class, $person->tree(), Auth::user())->first(static function (ModuleInterface $module) {
return $module instanceof RelationshipsChartModule;
});
if ($module instanceof RelationshipsChartModule) {
foreach ($associates as $associate) {
$relationship_name = app(RelationshipService::class)->getCloseRelationshipName($associate, $person);
if ($relationship_name === '') {
$relationship_name = I18N::translate('Relationship');
}
if ($parent instanceof Family) {
// For family ASSO records (e.g. MARR), identify the spouse with a sex icon
$relationship_name .= '' . view('icons/sex', ['sex' => $associate->sex()]) . '';
}
$values[] = '' . $relationship_name . '';
}
}
$value = implode(' — ', $values);
$asso = I18N::translate('%1$s: %2$s', $label, $value);
} elseif (!$person && Auth::isEditor($event->record()->tree())) {
$value = '' . $amatch[2] . '';
$asso = I18N::translate('%1$s: %2$s', I18N::translate('Associate'), $value);
} else {
$asso = '';
}
$html .= '' . $asso . '
';
}
return $html;
}
/**
* print a source linked to a fact (2 SOUR)
* this function is called by the FunctionsPrintFacts::print_fact function and other functions to
* print any source information attached to the fact
*
* @param Tree $tree
* @param string $factrec The fact record to look for sources in
* @param int $level The level to look for sources at
*
* @return string HTML text
*/
public static function printFactSources(Tree $tree, string $factrec, int $level): string
{
$data = '';
$nlevel = $level + 1;
// Systems not using source records
// The old style is not supported when entering or editing sources, but may be found in imported trees.
// Also, the old style sources allow histo.* files to use tree independent source citations, which
// will display nicely when markdown is used.
$ct = preg_match_all('/' . $level . ' SOUR (.*)((?:\n\d CONT.*)*)/', $factrec, $match, PREG_SET_ORDER);
for ($j = 0; $j < $ct; $j++) {
if (!str_contains($match[$j][1], '@')) {
$source = e($match[$j][1] . preg_replace('/\n\d CONT ?/', "\n", $match[$j][2]));
$data .= '' . I18N::translate('Source') . ': ';
if ($tree->getPreference('FORMAT_TEXT') === 'markdown') {
$data .= '';
$data .= Registry::markdownFactory()->markdown($source, $tree);
$data .= '
';
} else {
$data .= '';
$data .= Registry::markdownFactory()->autolink($source, $tree);
$data .= '
';
}
$data .= ' ';
}
}
// Find source for each fact
$ct = preg_match_all("/$level SOUR @(.*)@/", $factrec, $match, PREG_SET_ORDER);
$spos2 = 0;
for ($j = 0; $j < $ct; $j++) {
$sid = $match[$j][1];
$source = Registry::sourceFactory()->make($sid, $tree);
if ($source) {
if ($source->canShow()) {
$spos1 = strpos($factrec, "$level SOUR @" . $sid . '@', $spos2);
$spos2 = strpos($factrec, "\n$level", $spos1);
if (!$spos2) {
$spos2 = strlen($factrec);
}
$srec = substr($factrec, $spos1, $spos2 - $spos1);
$lt = preg_match_all("/$nlevel \w+/", $srec, $matches);
$data .= '';
$data .= '';
$data .= self::printSourceStructure($tree, self::getSourceStructure($srec));
$data .= '
';
ob_start();
self::printMediaLinks($tree, $srec, $nlevel);
$data .= ob_get_clean();
$data .= FunctionsPrint::printFactNotes($tree, $srec, $nlevel);
$data .= '
';
$data .= '
';
}
} else {
$value = '' . $sid . '';
$data .= I18N::translate('%1$s: %2$s', I18N::translate('Source'), $value);
}
}
return $data;
}
/**
* Print the links to media objects
*
* @param Tree $tree
* @param string $factrec
* @param int $level
*
* @return void
*/
public static function printMediaLinks(Tree $tree, string $factrec, int $level): void
{
$nlevel = $level + 1;
if (preg_match_all("/$level OBJE @(.*)@/", $factrec, $omatch, PREG_SET_ORDER) === 0) {
return;
}
$objectNum = 0;
while ($objectNum < count($omatch)) {
$media_id = $omatch[$objectNum][1];
$media = Registry::mediaFactory()->make($media_id, $tree);
if ($media) {
if ($media->canShow()) {
echo '';
foreach ($media->mediaFiles() as $media_file) {
echo $media_file->displayImage(100, 100, 'contain', []);
}
echo '
';
echo '
';
echo '
', $media->fullName(), '';
// NOTE: echo the notes of the media
echo '
';
echo FunctionsPrint::printFactNotes($tree, $media->gedcom(), 1);
//-- print spouse name for marriage events
echo FunctionsPrint::printFactNotes($tree, $media->gedcom(), $nlevel);
echo self::printFactSources($tree, $media->gedcom(), $nlevel);
echo '
'; //close div "media-display-title"
echo '
'; //close div "media-display"
}
} elseif ($tree->getPreference('HIDE_GEDCOM_ERRORS') === '1') {
echo '', $media_id, '
';
}
$objectNum++;
}
}
/**
* Print a row for the sources tab on the individual page.
*
* @param Fact $fact
* @param int $level
*
* @return void
*/
public static function printMainSources(Fact $fact, int $level): void
{
$factrec = $fact->gedcom();
$tree = $fact->record()->tree();
$nlevel = $level + 1;
if ($fact->isPendingAddition()) {
$styleadd = 'wt-new';
$can_edit = $level === 1 && $fact->canEdit();
} elseif ($fact->isPendingDeletion()) {
$styleadd = 'wt-old';
$can_edit = false;
} else {
$styleadd = '';
$can_edit = $level === 1 && $fact->canEdit();
}
// -- find source for each fact
preg_match_all('/(?:^|\n)(' . $level . ' SOUR (.*)(?:\n[' . $nlevel . '-9] .*)*)/', $fact->gedcom(), $matches, PREG_SET_ORDER);
foreach ($matches as $match) {
$srec = $match[1];
$sid = $match[2];
$source = Registry::sourceFactory()->make(trim($sid, '@'), $tree);
// Allow access to "1 SOUR @non_existent_source@", so it can be corrected/deleted
if (!$source || $source->canShow()) {
if ($level > 1) {
echo '';
} else {
echo '
';
}
echo '| ';
echo $fact->label();
if ($can_edit) {
echo ' ';
if (preg_match('/^@.+@$/', $sid)) {
// Inline sources can't be edited. Attempting to save one will convert it
// into a link, and delete it.
// e.g. "1 SOUR my source" becomes "1 SOUR @my source@" which does not exist.
echo view('edit/icon-fact-edit', ['fact' => $fact]);
echo view('edit/icon-fact-copy', ['fact' => $fact]);
}
echo view('edit/icon-fact-delete', ['fact' => $fact]);
}
echo ' | ';
echo '';
if ($source) {
echo '', $source->fullName(), '';
// 2 RESN tags. Note, there can be more than one, such as "privacy" and "locked"
if (preg_match_all("/\n2 RESN (.+)/", $factrec, $rmatches)) {
$label = Registry::elementFactory()->make($fact->tag() . ':RESN')->label();
foreach ($rmatches[1] as $rmatch) {
echo ' ', $label, ': ';
switch ($rmatch) {
case 'none':
// Note: "2 RESN none" is not valid gedcom, and the GUI will not let you add it.
// However, webtrees privacy rules will interpret it as "show an otherwise private fact to public".
echo ' ', I18N::translate('Show to visitors');
break;
case 'privacy':
echo ' ', I18N::translate('Show to members');
break;
case 'confidential':
echo ' ', I18N::translate('Show to managers');
break;
case 'locked':
echo ' ', I18N::translate('Only managers can edit');
break;
default:
echo $rmatch;
break;
}
echo '';
}
}
echo self::printSourceStructure($tree, self::getSourceStructure($srec));
echo '';
self::printMediaLinks($tree, $srec, $nlevel);
if ($nlevel === 2) {
self::printMediaLinks($tree, $source->gedcom(), 1);
}
echo FunctionsPrint::printFactNotes($tree, $srec, $nlevel);
if ($nlevel === 2) {
echo FunctionsPrint::printFactNotes($tree, $source->gedcom(), 1);
}
echo ' ';
} else {
echo $sid;
}
echo ' |
';
}
}
}
/**
* Print SOUR structure
* This function prints the input array of SOUR sub-records built by the
* getSourceStructure() function.
*
* @param Tree $tree
* @param array> $textSOUR
*
* @return string
*/
public static function printSourceStructure(Tree $tree, array $textSOUR): string
{
$html = '';
if ($textSOUR['PAGE'] !== '') {
$html .= Registry::elementFactory()->make('INDI:SOUR:PAGE')->labelValue($textSOUR['PAGE'], $tree);
}
if ($textSOUR['EVEN'] !== '') {
$html .= Registry::elementFactory()->make('INDI:SOUR:EVEN')->labelValue($textSOUR['EVEN'], $tree);
if ($textSOUR['ROLE']) {
$html .= Registry::elementFactory()->make('INDI:SOUR:EVEN:ROLE')->labelValue($textSOUR['ROLE'], $tree);
}
}
if ($textSOUR['DATE'] !== '') {
$html .= Registry::elementFactory()->make('INDI:SOUR:DATA:DATE')->labelValue($textSOUR['DATE'], $tree);
}
foreach ($textSOUR['TEXT'] as $text) {
$html .= Registry::elementFactory()->make('INDI:SOUR:DATA:TEXT')->labelValue($text, $tree);
}
if ($textSOUR['QUAY'] !== '') {
$html .= Registry::elementFactory()->make('INDI:SOUR:QUAY')->labelValue($textSOUR['QUAY'], $tree);
}
return '' . $html . '
';
}
/**
* Extract SOUR structure from the incoming Source sub-record
* The output array is defined as follows:
* $textSOUR['PAGE'] = Source citation
* $textSOUR['EVEN'] = Event type
* $textSOUR['ROLE'] = Role in event
* $textSOUR['DATA'] = place holder (no text in this sub-record)
* $textSOUR['DATE'] = Entry recording date
* $textSOUR['TEXT'] = (array) Text from source
* $textSOUR['QUAY'] = Certainty assessment
*
* @param string $srec
*
* @return array>
*/
public static function getSourceStructure(string $srec): array
{
// Set up the output array
$textSOUR = [
'PAGE' => '',
'EVEN' => '',
'ROLE' => '',
'DATE' => '',
'TEXT' => [],
'QUAY' => '',
];
preg_match_all('/^\d (PAGE|EVEN|ROLE|DATE|TEXT|QUAY) ?(.*(\n\d CONT.*)*)$/m', $srec, $matches, PREG_SET_ORDER);
foreach ($matches as $match) {
$tag = $match[1];
$value = $match[2];
$value = preg_replace('/\n\d CONT ?/', "\n", $value);
if ($tag === 'TEXT') {
$textSOUR[$tag][] = $value;
} else {
$textSOUR[$tag] = $value;
}
}
return $textSOUR;
}
/**
* Print a row for the notes tab on the individual page.
*
* @param Fact $fact
* @param int $level
*
* @return void
*/
public static function printMainNotes(Fact $fact, int $level): void
{
$factrec = $fact->gedcom();
$tree = $fact->record()->tree();
if ($fact->isPendingAddition()) {
$styleadd = 'wt-new ';
$can_edit = $level === 1 && $fact->canEdit();
} elseif ($fact->isPendingDeletion()) {
$styleadd = 'wt-old ';
$can_edit = false;
} else {
$styleadd = '';
$can_edit = $level === 1 && $fact->canEdit();
}
$ct = preg_match_all("/$level NOTE (.*)/", $factrec, $match, PREG_SET_ORDER);
for ($j = 0; $j < $ct; $j++) {
// Note object, or inline note?
if (preg_match("/$level NOTE @(.*)@/", $match[$j][0], $nmatch)) {
$note = Registry::noteFactory()->make($nmatch[1], $tree);
if ($note && !$note->canShow()) {
continue;
}
} else {
$note = null;
}
if ($level >= 2) {
echo '| ';
} else {
echo ' |
|---|
| ';
}
if ($note instanceof Note) {
echo '';
echo I18N::translate('Shared note');
echo view('icons/note');
echo '';
} else {
echo I18N::translate('Note');
}
if ($can_edit) {
echo ' ';
echo view('edit/icon-fact-edit', ['fact' => $fact]);
echo view('edit/icon-fact-copy', ['fact' => $fact]);
echo view('edit/icon-fact-delete', ['fact' => $fact]);
echo ' ';
}
echo ' | ';
if ($note instanceof Note) {
$text = $note->getNote();
} else {
$nrec = Functions::getSubRecord($level, "$level NOTE", $factrec, $j + 1);
$text = $match[$j][1] . Functions::getCont($level + 1, $nrec);
}
$element = new SubmitterText('');
echo '';
echo $element->value($text, $tree);
echo ' |
';
}
}
/**
* Print a row for the media tab on the individual page.
*
* @param Fact $fact
* @param int $level
*
* @return void
*/
public static function printMainMedia(Fact $fact, int $level): void
{
$tree = $fact->record()->tree();
if ($fact->isPendingAddition()) {
$styleadd = 'wt-new';
} elseif ($fact->isPendingDeletion()) {
$styleadd = 'wt-old';
} else {
$styleadd = '';
}
// -- find source for each fact
preg_match_all('/(?:^|\n)' . $level . ' OBJE @(.*)@/', $fact->gedcom(), $matches);
foreach ($matches[1] as $xref) {
$media = Registry::mediaFactory()->make($xref, $tree);
// Allow access to "1 OBJE @non_existent_source@", so it can be corrected/deleted
if (!$media instanceof Media || $media->canShow()) {
echo '';
echo '| ';
echo $fact->label();
if ($level === 1 && $fact->canEdit()) {
echo ' ';
echo view('edit/icon-fact-copy', ['fact' => $fact]);
echo view('edit/icon-fact-delete', ['fact' => $fact]);
echo ' ';
}
echo ' | ';
echo '';
if ($media instanceof Media) {
foreach ($media->mediaFiles() as $media_file) {
echo '';
}
echo FunctionsPrint::printFactNotes($tree, $media->gedcom(), 1);
echo self::printFactSources($tree, $media->gedcom(), 1);
} else {
echo $xref;
}
echo ' |
';
}
}
}
}