/** * 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 a fact for an individual. * * @param Fact $event */ public function printTimeFact(Fact $event) { global $basexoffset, $baseyoffset, $factcount, $placements; $desc = $event->getValue(); // check if this is a family fact $gdate = $event->getDate(); $date = $gdate->minimumDate(); $date = $date->convertToCalendar('gregorian'); $year = $date->y; $month = max(1, $date->m); $day = max(1, $date->d); $xoffset = $basexoffset + 22; $yoffset = $baseyoffset + ($year - $this->baseyear) * $this->scale - $this->scale; $yoffset = $yoffset + $month / 12 * $this->scale; $yoffset = $yoffset + $day / 30 * ($this->scale / 12); $yoffset = (int) $yoffset; $place = (int) ($yoffset / $this->bheight); $i = 1; $j = 0; $tyoffset = 0; while (isset($placements[$place])) { if ($i === $j) { $tyoffset = $this->bheight * $i; $i++; } else { $tyoffset = -1 * $this->bheight * $j; $j++; } $place = (int) (($yoffset + $tyoffset) / $this->bheight); } $yoffset += $tyoffset; $xoffset += abs($tyoffset); $placements[$place] = $yoffset; echo "<div id=\"fact{$factcount}\" style=\"position:absolute; " . (I18N::direction() === 'ltr' ? 'left: ' . $xoffset : 'right: ' . $xoffset) . 'px; top:' . $yoffset . "px; font-size: 8pt; height: " . $this->bheight . "px;\" onmousedown=\"factMouseDown(this, '" . $factcount . "', " . ($yoffset - $tyoffset) . ");\">"; echo '<table cellspacing="0" cellpadding="0" border="0" style="cursor: hand;"><tr><td>'; echo '<img src="' . Theme::theme()->parameter('image-hline') . '" name="boxline' . $factcount . '" id="boxline' . $factcount . '" height="3" width="10" style="padding-'; if (I18N::direction() === 'ltr') { echo 'left: 3px;">'; } else { echo 'right: 3px;">'; } $col = array_search($event->getParent(), $this->people); if ($col === false) { // Marriage event - use the color of the husband $col = array_search($event->getParent()->getHusband(), $this->people); } if ($col === false) { // Marriage event - use the color of the wife $col = array_search($event->getParent()->getWife(), $this->people); } $col = $col % 6; echo '</td><td class="person' . $col . '">'; if (count($this->people) > 6) { // We only have six colours, so show naes if more than this number echo $event->getParent()->getFullName() . ' — '; } $record = $event->getParent(); echo $event->getLabel(); echo ' — '; if ($record instanceof Individual) { echo FunctionsPrint::formatFactDate($event, $record, false, false); } elseif ($record instanceof Family) { echo $gdate->display(); if ($record->getHusband() && $record->getHusband()->getBirthDate()->isOK()) { $ageh = FunctionsDate::getAgeAtEvent(Date::getAgeGedcom($record->getHusband()->getBirthDate(), $gdate)); } else { $ageh = null; } if ($record->getWife() && $record->getWife()->getBirthDate()->isOK()) { $agew = FunctionsDate::getAgeAtEvent(Date::getAgeGedcom($record->getWife()->getBirthDate(), $gdate)); } else { $agew = null; } if ($ageh && $agew) { echo '<span class="age"> ', I18N::translate('Husband’s age'), ' ', $ageh, ' ', I18N::translate('Wife’s age'), ' ', $agew, '</span>'; } elseif ($ageh) { echo '<span class="age"> ', I18N::translate('Age'), ' ', $ageh, '</span>'; } elseif ($agew) { echo '<span class="age"> ', I18N::translate('Age'), ' ', $ageh, '</span>'; } } echo ' ' . Filter::escapeHtml($desc); if (!$event->getPlace()->isEmpty()) { echo ' — ' . $event->getPlace()->getShortName(); } // Print spouses names for family events if ($event->getParent() instanceof Family) { echo ' — <a href="', $event->getParent()->getHtmlUrl(), '">', $event->getParent()->getFullName(), '</a>'; } echo '</td></tr></table>'; echo '</div>'; if (I18N::direction() === 'ltr') { $img = 'image-dline2'; $ypos = '0%'; } else { $img = 'image-dline'; $ypos = '100%'; } $dyoffset = $yoffset - $tyoffset + $this->bheight / 3; if ($tyoffset < 0) { $dyoffset = $yoffset + $this->bheight / 3; if (I18N::direction() === 'ltr') { $img = 'image-dline'; $ypos = '100%'; } else { $img = 'image-dline2'; $ypos = '0%'; } } // Print the diagonal line echo '<div id="dbox' . $factcount . '" style="position:absolute; ' . (I18N::direction() === 'ltr' ? 'left: ' . ($basexoffset + 25) : 'right: ' . ($basexoffset + 25)) . 'px; top:' . $dyoffset . 'px; font-size: 8pt; height: ' . abs($tyoffset) . 'px; width: ' . abs($tyoffset) . 'px;'; echo ' background-image: url(\'' . Theme::theme()->parameter($img) . '\');'; echo ' background-position: 0% ' . $ypos . ';">'; echo '</div>'; }
/** * Extract/format the first fact from a list of facts. * * @param string $facts * @param int $style * * @return string */ public function formatFirstMajorFact($facts, $style) { foreach ($this->getFacts($facts, true) as $event) { // Only display if it has a date or place (or both) if ($event->getDate()->isOK() || !$event->getPlace()->isEmpty()) { switch ($style) { case 1: return '<br><em>' . $event->getLabel() . ' ' . FunctionsPrint::formatFactDate($event, $this, false, false) . ' ' . FunctionsPrint::formatFactPlace($event) . '</em>'; case 2: return '<dl><dt class="label">' . $event->getLabel() . '</dt><dd class="field">' . FunctionsPrint::formatFactDate($event, $this, false, false) . ' ' . FunctionsPrint::formatFactPlace($event) . '</dd></dl>'; } } } return ''; }