/** * 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 */ public static function printFact(Fact $fact, GedcomRecord $record) { static $n_chil = 0, $n_gchi = 0; $parent = $fact->getParent(); // Some facts don't get printed here ... switch ($fact->getTag()) { 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 are used internally to record the sort order. return; default: // Hide unrecognized/custom tags? if ($fact->getParent()->getTree()->getPreference('HIDE_GEDCOM_ERRORS') && !GedcomTag::isTag($fact->getTag())) { return; } break; } // Who is this fact about? Need it to translate fact label correctly if ($parent instanceof Family && $record instanceof Individual) { // Family event $label_person = $fact->getParent()->getSpouse($record); } else { // Individual event $label_person = $parent; } // New or deleted facts need different styling $styleadd = ''; if ($fact->isPendingAddition()) { $styleadd = 'new'; } if ($fact->isPendingDeletion()) { $styleadd = 'old'; } // Event of close relative if (preg_match('/^_[A-Z_]{3,5}_[A-Z0-9]{4}$/', $fact->getTag())) { $styleadd = trim($styleadd . ' rela'); } // Event of close associates if ($fact->getFactId() == 'asso') { $styleadd = trim($styleadd . ' rela'); } // historical facts if ($fact->getFactId() == 'histo') { $styleadd = trim($styleadd . ' histo'); } // Does this fact have a type? if (preg_match('/\\n2 TYPE (.+)/', $fact->getGedcom(), $match)) { $type = $match[1]; } else { $type = ''; } switch ($fact->getTag()) { case 'EVEN': case 'FACT': if (GedcomTag::isTag($type)) { // Some users (just Meliza?) use "1 EVEN/2 TYPE BIRT". Translate the TYPE. $label = GedcomTag::getLabel($type, $label_person); $type = ''; // Do not print this again } elseif ($type) { // We don't have a translation for $type - but a custom translation might exist. $label = I18N::translate(Filter::escapeHtml($type)); $type = ''; // Do not print this again } else { // An unspecified fact/event $label = $fact->getLabel(); } break; case 'MARR': // This is a hack for a proprietory extension. Is it still used/needed? $utype = strtoupper($type); if ($utype == 'CIVIL' || $utype == 'PARTNERS' || $utype == 'RELIGIOUS') { $label = GedcomTag::getLabel('MARR_' . $utype, $label_person); $type = ''; // Do not print this again } else { $label = $fact->getLabel(); } break; default: // Normal fact/event $label = $fact->getLabel(); break; } echo '<tr class="', $styleadd, '">'; echo '<td class="descriptionbox width20">'; if ($fact->getParent()->getTree()->getPreference('SHOW_FACT_ICONS')) { echo Theme::theme()->icon($fact), ' '; } if ($fact->getFactId() != 'histo' && $fact->canEdit()) { ?> <a href="#" title="<?php echo I18N::translate('Edit'); ?> " onclick="return edit_record('<?php echo $parent->getXref(); ?> ', '<?php echo $fact->getFactId(); ?> ');" ><?php echo $label; ?> </a> <div class="editfacts"> <div class="editlink"> <a href="#" title="<?php echo I18N::translate('Edit'); ?> " class="editicon" onclick="return edit_record('<?php echo $parent->getXref(); ?> ', '<?php echo $fact->getFactId(); ?> ');" ><span class="link_text"><?php echo I18N::translate('Edit'); ?> </span></a> </div> <div class="copylink"> <a href="#" title="<?php echo I18N::translate('Copy'); ?> " class="copyicon" onclick="return copy_fact('<?php echo $parent->getXref(); ?> ', '<?php echo $fact->getFactId(); ?> ');" ><span class="link_text"><?php echo I18N::translate('Copy'); ?> </span></a> </div> <div class="deletelink"> <a href="#" title="<?php echo I18N::translate('Delete'); ?> " class="deleteicon" onclick="return delete_fact('<?php echo I18N::translate('Are you sure you want to delete this fact?'); ?> ', '<?php echo $parent->getXref(); ?> ', '<?php echo $fact->getFactId(); ?> ');" ><span class="link_text"><?php echo I18N::translate('Delete'); ?> </span></a> </div> </div> <?php } else { echo $label; } switch ($fact->getTag()) { case '_BIRT_CHIL': echo '<br>', I18N::translate('#%s', ++$n_chil); break; case '_BIRT_GCHI': case '_BIRT_GCH1': case '_BIRT_GCH2': echo '<br>', I18N::translate('#%s', ++$n_gchi); break; } echo '</td><td class="optionbox ', $styleadd, ' wrap">'; // Event from another record? if ($parent !== $record) { if ($parent instanceof Family) { foreach ($parent->getSpouses() as $spouse) { if ($record !== $spouse) { echo '<a href="', $spouse->getHtmlUrl(), '">', $spouse->getFullName(), '</a> — '; } } echo '<a href="', $parent->getHtmlUrl(), '">', I18N::translate('View family'), '</a><br>'; } elseif ($parent instanceof Individual) { echo '<a href="', $parent->getHtmlUrl(), '">', $parent->getFullName(), '</a><br>'; } } // Print the value of this fact/event switch ($fact->getTag()) { case 'ADDR': echo $fact->getValue(); break; case 'AFN': echo '<div class="field"><a href="https://familysearch.org/search/tree/results#count=20&query=afn:', rawurlencode($fact->getValue()), '" target="new">', Filter::escapeHtml($fact->getValue()), '</a></div>'; break; case 'ASSO': // we handle this later, in format_asso_rela_record() break; case 'EMAIL': case 'EMAI': case '_EMAIL': echo '<div class="field"><a href="mailto:', Filter::escapeHtml($fact->getValue()), '">', Filter::escapeHtml($fact->getValue()), '</a></div>'; break; case 'FILE': if (Auth::isEditor($fact->getParent()->getTree())) { echo '<div class="field">', Filter::escapeHtml($fact->getValue()), '</div>'; } break; case 'RESN': echo '<div class="field">'; switch ($fact->getValue()) { case 'none': // Note: "1 RESN none" is not valid gedcom. // However, webtrees privacy rules will interpret it as "show an otherwise private record to public". echo '<i class="icon-resn-none"></i> ', I18N::translate('Show to visitors'); break; case 'privacy': echo '<i class="icon-class-none"></i> ', I18N::translate('Show to members'); break; case 'confidential': echo '<i class="icon-confidential-none"></i> ', I18N::translate('Show to managers'); break; case 'locked': echo '<i class="icon-locked-none"></i> ', I18N::translate('Only managers can edit'); break; default: echo Filter::escapeHtml($fact->getValue()); break; } echo '</div>'; break; case 'PUBL': // Publication details might contain URLs. echo '<div class="field">', Filter::expandUrls($fact->getValue()), '</div>'; break; case 'REPO': if (preg_match('/^@(' . WT_REGEX_XREF . ')@$/', $fact->getValue(), $match)) { self::printRepositoryRecord($match[1]); } else { echo '<div class="error">', Filter::escapeHtml($fact->getValue()), '</div>'; } break; case 'URL': case '_URL': case 'WWW': echo '<div class="field"><a href="', Filter::escapeHtml($fact->getValue()), '">', Filter::escapeHtml($fact->getValue()), '</a></div>'; break; case 'TEXT': // 0 SOUR / 1 TEXT echo '<div class="field">', nl2br(Filter::escapeHtml($fact->getValue()), false), '</div>'; break; default: // Display the value for all other facts/events switch ($fact->getValue()) { case '': // Nothing to display break; case 'N': // Not valid GEDCOM echo '<div class="field">', I18N::translate('No'), '</div>'; break; case 'Y': // Do not display "Yes". break; default: if (preg_match('/^@(' . WT_REGEX_XREF . ')@$/', $fact->getValue(), $match)) { $target = GedcomRecord::getInstance($match[1], $fact->getParent()->getTree()); if ($target) { echo '<div><a href="', $target->getHtmlUrl(), '">', $target->getFullName(), '</a></div>'; } else { echo '<div class="error">', Filter::escapeHtml($fact->getValue()), '</div>'; } } else { echo '<div class="field"><span dir="auto">', Filter::escapeHtml($fact->getValue()), '</span></div>'; } break; } break; } // Print the type of this fact/event if ($type) { $utype = strtoupper($type); // Events of close relatives, e.g. _MARR_CHIL if (substr($fact->getTag(), 0, 6) == '_MARR_' && ($utype == 'CIVIL' || $utype == 'PARTNERS' || $utype == 'RELIGIOUS')) { // Translate MARR/TYPE using the code that supports MARR_CIVIL, etc. tags $type = GedcomTag::getLabel('MARR_' . $utype); } else { // Allow (custom) translations for other types $type = I18N::translate($type); } echo GedcomTag::getLabelValue('TYPE', Filter::escapeHtml($type)); } // Print the date of this fact/event echo FunctionsPrint::formatFactDate($fact, $record, true, true); // Print the place of this fact/event echo '<div class="place">', FunctionsPrint::formatFactPlace($fact, true, true, true), '</div>'; // A blank line between the primary attributes (value, date, place) and the secondary ones echo '<br>'; $addr = $fact->getAttribute('ADDR'); if ($addr) { echo GedcomTag::getLabelValue('ADDR', $addr); } // Print the associates of this fact/event if ($fact->getFactId() !== 'asso') { echo self::formatAssociateRelationship($fact); } // Print any other "2 XXXX" attributes, in the order in which they appear. preg_match_all('/\\n2 (' . WT_REGEX_TAG . ') (.+)/', $fact->getGedcom(), $matches, PREG_SET_ORDER); foreach ($matches as $match) { switch ($match[1]) { case 'DATE': case 'TIME': case 'AGE': case 'PLAC': case 'ADDR': 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; case 'EVEN': // 0 SOUR / 1 DATA / 2 EVEN / 3 DATE / 3 PLAC $events = array(); foreach (preg_split('/ *, */', $match[2]) as $event) { $events[] = GedcomTag::getLabel($event); } if (count($events) == 1) { echo GedcomTag::getLabelValue('EVEN', $event); } else { echo GedcomTag::getLabelValue('EVEN', implode(I18N::$list_separator, $events)); } if (preg_match('/\\n3 DATE (.+)/', $fact->getGedcom(), $date_match)) { $date = new Date($date_match[1]); echo GedcomTag::getLabelValue('DATE', $date->display()); } if (preg_match('/\\n3 PLAC (.+)/', $fact->getGedcom(), $plac_match)) { echo GedcomTag::getLabelValue('PLAC', $plac_match[1]); } break; case 'FAMC': // 0 INDI / 1 ADOP / 2 FAMC / 3 ADOP $family = Family::getInstance(str_replace('@', '', $match[2]), $fact->getParent()->getTree()); if ($family) { echo GedcomTag::getLabelValue('FAM', '<a href="' . $family->getHtmlUrl() . '">' . $family->getFullName() . '</a>'); if (preg_match('/\\n3 ADOP (HUSB|WIFE|BOTH)/', $fact->getGedcom(), $match)) { echo GedcomTag::getLabelValue('ADOP', GedcomCodeAdop::getValue($match[1], $label_person)); } } else { echo GedcomTag::getLabelValue('FAM', '<span class="error">' . $match[2] . '</span>'); } break; case '_WT_USER': $user = User::findByIdentifier($match[2]); // may not exist if ($user) { echo GedcomTag::getLabelValue('_WT_USER', $user->getRealNameHtml()); } else { echo GedcomTag::getLabelValue('_WT_USER', Filter::escapeHtml($match[2])); } break; case 'RESN': switch ($match[2]) { case 'none': // Note: "2 RESN none" is not valid gedcom. // However, webtrees privacy rules will interpret it as "show an otherwise private fact to public". echo GedcomTag::getLabelValue('RESN', '<i class="icon-resn-none"></i> ' . I18N::translate('Show to visitors')); break; case 'privacy': echo GedcomTag::getLabelValue('RESN', '<i class="icon-resn-privacy"></i> ' . I18N::translate('Show to members')); break; case 'confidential': echo GedcomTag::getLabelValue('RESN', '<i class="icon-resn-confidential"></i> ' . I18N::translate('Show to managers')); break; case 'locked': echo GedcomTag::getLabelValue('RESN', '<i class="icon-resn-locked"></i> ' . I18N::translate('Only managers can edit')); break; default: echo GedcomTag::getLabelValue('RESN', Filter::escapeHtml($match[2])); break; } break; case 'CALN': echo GedcomTag::getLabelValue('CALN', Filter::expandUrls($match[2])); break; case 'FORM': // 0 OBJE / 1 FILE / 2 FORM / 3 TYPE echo GedcomTag::getLabelValue('FORM', $match[2]); if (preg_match('/\\n3 TYPE (.+)/', $fact->getGedcom(), $type_match)) { echo GedcomTag::getLabelValue('TYPE', GedcomTag::getFileFormTypeValue($type_match[1])); } break; case 'URL': case '_URL': case 'WWW': $link = '<a href="' . Filter::escapeHtml($match[2]) . '">' . Filter::escapeHtml($match[2]) . '</a>'; echo GedcomTag::getLabelValue($fact->getTag() . ':' . $match[1], $link); break; default: if (!$fact->getParent()->getTree()->getPreference('HIDE_GEDCOM_ERRORS') || GedcomTag::isTag($match[1])) { if (preg_match('/^@(' . WT_REGEX_XREF . ')@$/', $match[2], $xmatch)) { // Links $linked_record = GedcomRecord::getInstance($xmatch[1], $fact->getParent()->getTree()); if ($linked_record) { $link = '<a href="' . $linked_record->getHtmlUrl() . '">' . $linked_record->getFullName() . '</a>'; echo GedcomTag::getLabelValue($fact->getTag() . ':' . $match[1], $link); } else { echo GedcomTag::getLabelValue($fact->getTag() . ':' . $match[1], Filter::escapeHtml($match[2])); } } else { // Non links echo GedcomTag::getLabelValue($fact->getTag() . ':' . $match[1], Filter::escapeHtml($match[2])); } } break; } } echo self::printFactSources($fact->getGedcom(), 2); echo FunctionsPrint::printFactNotes($fact->getGedcom(), 2); self::printMediaLinks($fact->getGedcom(), 2); echo '</td></tr>'; }
/** * Print an edit control for a ADOP field. * * @param string $name * @param string $selected * @param string $extra * @param Individual|null $individual * * @return string */ public static function editFieldAdoption($name, $selected = '', $extra = '', Individual $individual = null) { return self::selectEditControl($name, GedcomCodeAdop::getValues($individual), null, $selected, $extra); }