.
*/
namespace Fisharebest\Webtrees\Functions;
use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Date;
use Fisharebest\Webtrees\Fact;
use Fisharebest\Webtrees\Family;
use Fisharebest\Webtrees\Filter;
use Fisharebest\Webtrees\GedcomCode\GedcomCodeAdop;
use Fisharebest\Webtrees\GedcomCode\GedcomCodeQuay;
use Fisharebest\Webtrees\GedcomCode\GedcomCodeRela;
use Fisharebest\Webtrees\GedcomRecord;
use Fisharebest\Webtrees\GedcomTag;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Individual;
use Fisharebest\Webtrees\Media;
use Fisharebest\Webtrees\Module;
use Fisharebest\Webtrees\Module\CensusAssistantModule;
use Fisharebest\Webtrees\Note;
use Fisharebest\Webtrees\Repository;
use Fisharebest\Webtrees\Source;
use Fisharebest\Webtrees\Theme;
use Fisharebest\Webtrees\User;
use Rhumsaa\Uuid\Uuid;
/**
* Class FunctionsPrintFacts - common functions
*/
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
*/
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 '
';
echo '';
if ($fact->getParent()->getTree()->getPreference('SHOW_FACT_ICONS')) {
echo Theme::theme()->icon($fact), ' ';
}
if ($fact->getFactId() != 'histo' && $fact->canEdit()) {
?>
getTag()) {
case '_BIRT_CHIL':
echo ' ', /* I18N: Abbreviation for "number %s" */
I18N::translate('#%s', ++$n_chil);
break;
case '_BIRT_GCHI':
case '_BIRT_GCH1':
case '_BIRT_GCH2':
echo ' ', I18N::translate('#%s', ++$n_gchi);
break;
}
echo ' | ';
// Event from another record?
if ($parent !== $record) {
if ($parent instanceof Family) {
foreach ($parent->getSpouses() as $spouse) {
if ($record !== $spouse) {
echo '', $spouse->getFullName(), ' — ';
}
}
echo '', I18N::translate('View this family'), ' ';
} elseif ($parent instanceof Individual) {
echo '', $parent->getFullName(), ' ';
}
}
// Print the value of this fact/event
switch ($fact->getTag()) {
case 'ADDR':
echo $fact->getValue();
break;
case 'AFN':
echo '';
break;
case 'ASSO':
// we handle this later, in format_asso_rela_record()
break;
case 'EMAIL':
case 'EMAI':
case '_EMAIL':
echo '';
break;
case 'FILE':
if (Auth::isEditor($fact->getParent()->getTree())) {
echo '', Filter::escapeHtml($fact->getValue()), ' ';
}
break;
case 'RESN':
echo '';
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 ' ', 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 Filter::escapeHtml($fact->getValue());
break;
}
echo ' ';
break;
case 'PUBL': // Publication details might contain URLs.
echo '', Filter::expandUrls($fact->getValue()), ' ';
break;
case 'REPO':
if (preg_match('/^@(' . WT_REGEX_XREF . ')@$/', $fact->getValue(), $match)) {
self::printRepositoryRecord($match[1]);
} else {
echo '', Filter::escapeHtml($fact->getValue()), ' ';
}
break;
case 'URL':
case '_URL':
case 'WWW':
echo '';
break;
case 'TEXT': // 0 SOUR / 1 TEXT
echo '', nl2br(Filter::escapeHtml($fact->getValue()), false), ' ';
break;
default:
// Display the value for all other facts/events
switch ($fact->getValue()) {
case '':
// Nothing to display
break;
case 'N':
// Not valid GEDCOM
echo '', I18N::translate('No'), ' ';
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 '';
} else {
echo '', Filter::escapeHtml($fact->getValue()), ' ';
}
} else {
echo '', Filter::escapeHtml($fact->getValue()), ' ';
}
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 '', FunctionsPrint::formatFactPlace($fact, true, true, true), ' ';
// A blank line between the primary attributes (value, date, place) and the secondary ones
echo ' ';
$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 '_UID':
case 'RIN':
// These don't belong at level 2, so do not display them.
// They are only shown when editing.
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', '' . $family->getFullName() . '');
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', '' . $match[2] . '');
}
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', ' ' . I18N::translate('Show to visitors'));
break;
case 'privacy':
echo GedcomTag::getLabelValue('RESN', ' ' . I18N::translate('Show to members'));
break;
case 'confidential':
echo GedcomTag::getLabelValue('RESN', ' ' . I18N::translate('Show to managers'));
break;
case 'locked':
echo GedcomTag::getLabelValue('RESN', ' ' . 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 = '' . Filter::escapeHtml($match[2]) . '';
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 = '' . $linked_record->getFullName() . '';
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 ' |
';
}
/**
* 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) {
$parent = $event->getParent();
// To whom is this record an assocate?
if ($parent instanceof Individual) {
// On an individual page, we just show links to the person
$associates = array($parent);
} elseif ($parent instanceof Family) {
// On a family page, we show links to both spouses
$associates = $parent->getSpouses();
} else {
// On other pages, it does not make sense to show associates
return '';
}
preg_match_all('/^1 ASSO @(' . WT_REGEX_XREF . ')@((\n[2-9].*)*)/', $event->getGedcom(), $amatches1, PREG_SET_ORDER);
preg_match_all('/\n2 _?ASSO @(' . WT_REGEX_XREF . ')@((\n[3-9].*)*)/', $event->getGedcom(), $amatches2, PREG_SET_ORDER);
$html = '';
// For each ASSO record
foreach (array_merge($amatches1, $amatches2) as $amatch) {
$person = Individual::getInstance($amatch[1], $event->getParent()->getTree());
if ($person && $person->canShowName()) {
// Is there a "RELA" tag
if (preg_match('/\n[23] RELA (.+)/', $amatch[2], $rmatch)) {
// Use the supplied relationship as a label
$label = GedcomCodeRela::getValue($rmatch[1], $person);
} else {
// Use a default label
$label = GedcomTag::getLabel('ASSO', $person);
}
$values = array('' . $person->getFullName() . '');
foreach ($associates as $associate) {
$relationship_name = Functions::getAssociateRelationshipName($associate, $person);
if (!$relationship_name) {
$relationship_name = GedcomTag::getLabel('RELA');
}
if ($parent instanceof Family) {
// For family ASSO records (e.g. MARR), identify the spouse with a sex icon
$relationship_name .= $associate->getSexImage();
}
$values[] = '' . $relationship_name . '';
}
$value = implode(' — ', $values);
// Use same markup as GedcomTag::getLabelValue()
$asso = I18N::translate('%1$s: %2$s', $label, $value);
} elseif (!$person && Auth::isEditor($event->getParent()->getTree())) {
$asso = GedcomTag::getLabelValue('ASSO', '' . $amatch[1] . '');
} else {
$asso = '';
}
$html .= '' . $asso . '
';
}
return $html;
}
/**
* print a repository record
*
* find and print repository information attached to a source
*
* @param string $xref the Gedcom Xref ID of the repository to print
*/
public static function printRepositoryRecord($xref) {
global $WT_TREE;
$repository = Repository::getInstance($xref, $WT_TREE);
if ($repository && $repository->canShow()) {
echo '', $repository->getFullName(), '
';
echo '
';
echo FunctionsPrint::printFactNotes($repository->getGedcom(), 1);
}
}
/**
* 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 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($factrec, $level) {
global $WT_TREE;
$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 (strpos($match[$j][1], '@') === false) {
$source = Filter::escapeHtml($match[$j][1] . preg_replace('/\n\d CONT ?/', "\n", $match[$j][2]));
$data .= '' . I18N::translate('Source') . ': ' . Filter::formatText($source, $WT_TREE) . '
';
}
}
// 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 = Source::getInstance($sid, $WT_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 .= '';
$elementID = Uuid::uuid4();
if ($WT_TREE->getPreference('EXPAND_SOURCES')) {
$plusminus = 'icon-minus';
} else {
$plusminus = 'icon-plus';
}
if ($lt > 0) {
$data .= '
';
}
$data .= GedcomTag::getLabelValue('SOUR', '
' . $source->getFullName() . '', null, 'span');
$data .= '
';
$data .= "getPreference('EXPAND_SOURCES')) {
$data .= ' style="display:block"';
}
$data .= ' class="source_citations">';
// PUBL
$publ = $source->getFirstFact('PUBL');
if ($publ) {
$data .= GedcomTag::getLabelValue('PUBL', $publ->getValue());
}
$data .= self::printSourceStructure(self::getSourceStructure($srec));
$data .= '
';
ob_start();
self::printMediaLinks($srec, $nlevel);
$data .= ob_get_clean();
$data .= FunctionsPrint::printFactNotes($srec, $nlevel, false);
$data .= '
';
$data .= '
';
} else {
// Here we could show that we do actually have sources for this data,
// but not the details. For example “Sources: ”.
// But not by default, based on user feedback.
// https://webtrees.net/index.php/en/forum/3-help-for-beta-and-svn-versions/27002-source-media-privacy-issue
}
} else {
$data .= GedcomTag::getLabelValue('SOUR', '' . $sid . '');
}
}
return $data;
}
/**
* Print the links to media objects
*
* @param string $factrec
* @param int $level
*/
public static function printMediaLinks($factrec, $level) {
global $WT_TREE;
$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 = Media::getInstance($media_id, $WT_TREE);
if ($media) {
if ($media->canShow()) {
if ($objectNum > 0) {
echo '
';
}
echo ''; //close div "media-display"
}
} elseif (!$WT_TREE->getPreference('HIDE_GEDCOM_ERRORS')) {
echo '', $media_id, '
';
}
$objectNum++;
}
}
/**
* Print a row for the sources tab on the individual page.
*
* @param Fact $fact
* @param int $level
*/
public static function printMainSources(Fact $fact, $level) {
$factrec = $fact->getGedcom();
$fact_id = $fact->getFactId();
$parent = $fact->getParent();
$pid = $parent->getXref();
$nlevel = $level + 1;
if ($fact->isPendingAddition()) {
$styleadd = 'new';
$can_edit = $level == 1 && $fact->canEdit();
} elseif ($fact->isPendingDeletion()) {
$styleadd = 'old';
$can_edit = false;
} else {
$styleadd = '';
$can_edit = $level == 1 && $fact->canEdit();
}
// -- 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 = trim($match[$j][2], '@');
$spos1 = strpos($factrec, $match[$j][1], $spos2);
$spos2 = strpos($factrec, "\n$level", $spos1);
if (!$spos2) {
$spos2 = strlen($factrec);
}
$srec = substr($factrec, $spos1, $spos2 - $spos1);
$source = Source::getInstance($sid, $fact->getParent()->getTree());
// 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 '';
$factlines = explode("\n", $factrec); // 1 BIRT Y\n2 SOUR ...
$factwords = explode(" ", $factlines[0]); // 1 BIRT Y
$factname = $factwords[1]; // BIRT
if ($factname == 'EVEN' || $factname == 'FACT') {
// Add ' EVEN' to provide sensible output for an event with an empty TYPE record
$ct = preg_match("/2 TYPE (.*)/", $factrec, $ematch);
if ($ct > 0) {
$factname = trim($ematch[1]);
echo $factname;
} else {
echo GedcomTag::getLabel($factname, $parent);
}
} elseif ($can_edit) {
echo "';
if ($fact->getParent()->getTree()->getPreference('SHOW_FACT_ICONS')) {
if ($level == 1) {
echo ' ';
}
}
echo GedcomTag::getLabel($factname, $parent), '';
echo '';
if (preg_match('/^@.+@$/', $match[$j][2])) {
// 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 " ";
echo ' ';
}
echo " ";
echo ' ';
} else {
echo GedcomTag::getLabel($factname, $parent);
}
echo ' | ';
echo '';
if ($source) {
echo '', $source->getFullName(), '';
// PUBL
$publ = $source->getFirstFact('PUBL');
if ($publ) {
echo GedcomTag::getLabelValue('PUBL', $publ->getValue());
}
// 2 RESN tags. Note, there can be more than one, such as "privacy" and "locked"
if (preg_match_all("/\n2 RESN (.+)/", $factrec, $rmatches)) {
foreach ($rmatches[1] as $rmatch) {
echo ' ', GedcomTag::getLabel('RESN'), ': ';
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 '';
}
}
$cs = preg_match("/$nlevel EVEN (.*)/", $srec, $cmatch);
if ($cs > 0) {
echo ' ', GedcomTag::getLabel('EVEN'), ' ', $cmatch[1], '';
$cs = preg_match("/" . ($nlevel + 1) . " ROLE (.*)/", $srec, $cmatch);
if ($cs > 0) {
echo ' ', GedcomTag::getLabel('ROLE'), ' ', $cmatch[1], '';
}
}
echo self::printSourceStructure(self::getSourceStructure($srec));
echo '';
self::printMediaLinks($srec, $nlevel);
if ($nlevel == 2) {
self::printMediaLinks($source->getGedcom(), 1);
}
echo FunctionsPrint::printFactNotes($srec, $nlevel);
if ($nlevel == 2) {
echo FunctionsPrint::printFactNotes($source->getGedcom(), 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 string[] $textSOUR
*
* @return string
*/
public static function printSourceStructure($textSOUR) {
global $WT_TREE;
$html = '';
if ($textSOUR['PAGE']) {
$html .= GedcomTag::getLabelValue('PAGE', Filter::expandUrls($textSOUR['PAGE']));
}
if ($textSOUR['EVEN']) {
$html .= GedcomTag::getLabelValue('EVEN', Filter::escapeHtml($textSOUR['EVEN']));
if ($textSOUR['ROLE']) {
$html .= GedcomTag::getLabelValue('ROLE', Filter::escapeHtml($textSOUR['ROLE']));
}
}
if ($textSOUR['DATE'] || count($textSOUR['TEXT'])) {
if ($textSOUR['DATE']) {
$date = new Date($textSOUR['DATE']);
$html .= GedcomTag::getLabelValue('DATA:DATE', $date->display());
}
foreach ($textSOUR['TEXT'] as $text) {
$html .= GedcomTag::getLabelValue('TEXT', Filter::formatText($text, $WT_TREE));
}
}
if ($textSOUR['QUAY'] != '') {
$html .= GedcomTag::getLabelValue('QUAY', GedcomCodeQuay::getValue($textSOUR['QUAY']));
}
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 string[]
*/
public static function getSourceStructure($srec) {
// Set up the output array
$textSOUR = array(
'PAGE' => '',
'EVEN' => '',
'ROLE' => '',
'DATA' => '',
'DATE' => '',
'TEXT' => array(),
'QUAY' => '',
);
if ($srec) {
$subrecords = explode("\n", $srec);
for ($i = 0; $i < count($subrecords); $i++) {
$tag = substr($subrecords[$i], 2, 4);
$text = substr($subrecords[$i], 7);
$i++;
for (; $i < count($subrecords); $i++) {
$nextTag = substr($subrecords[$i], 2, 4);
if ($nextTag != 'CONT') {
$i--;
break;
}
if ($nextTag == 'CONT') {
$text .= "\n";
}
$text .= rtrim(substr($subrecords[$i], 7));
}
if ($tag == 'TEXT') {
$textSOUR[$tag][] = $text;
} else {
$textSOUR[$tag] = $text;
}
}
}
return $textSOUR;
}
/**
* Print a row for the notes tab on the individual page.
*
* @param Fact $fact
* @param int $level
*/
public static function printMainNotes(Fact $fact, $level) {
$factrec = $fact->getGedcom();
$fact_id = $fact->getFactId();
$parent = $fact->getParent();
$pid = $parent->getXref();
if ($fact->isPendingAddition()) {
$styleadd = ' new';
$can_edit = $level == 1 && $fact->canEdit();
} elseif ($fact->isPendingDeletion()) {
$styleadd = ' 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 = Note::getInstance($nmatch[1], $fact->getParent()->getTree());
if ($note && !$note->canShow()) {
continue;
}
} else {
$note = null;
}
if ($level >= 2) {
echo '';
} else {
echo ' |
';
}
if ($can_edit) {
echo '';
if ($level < 2) {
if ($fact->getParent()->getTree()->getPreference('SHOW_FACT_ICONS')) {
echo ' ';
}
if ($note) {
echo GedcomTag::getLabel('SHARED_NOTE');
} else {
echo GedcomTag::getLabel('NOTE');
}
echo '';
echo '';
}
} else {
if ($level < 2) {
if ($fact->getParent()->getTree()->getPreference('SHOW_FACT_ICONS')) {
echo ' ';
}
if ($note) {
echo GedcomTag::getLabel('SHARED_NOTE');
} else {
echo GedcomTag::getLabel('NOTE');
}
}
$factlines = explode("\n", $factrec); // 1 BIRT Y\n2 NOTE ...
$factwords = explode(" ", $factlines[0]); // 1 BIRT Y
$factname = $factwords[1]; // BIRT
$parent = GedcomRecord::getInstance($pid, $fact->getParent()->getTree());
if ($factname == 'EVEN' || $factname == 'FACT') {
// Add ' EVEN' to provide sensible output for an event with an empty TYPE record
$ct = preg_match("/2 TYPE (.*)/", $factrec, $ematch);
if ($ct > 0) {
$factname = trim($ematch[1]);
echo $factname;
} else {
echo GedcomTag::getLabel($factname, $parent);
}
} elseif ($factname != 'NOTE') {
// Note is already printed
echo GedcomTag::getLabel($factname, $parent);
if ($note) {
echo '';
}
}
}
echo ' | ';
if ($note) {
// Note objects
if (Module::getModuleByName('GEDFact_assistant')) {
// If Census assistant installed, allow it to format the note
$text = CensusAssistantModule::formatCensusNote($note);
} else {
$text = Filter::formatText($note->getNote(), $fact->getParent()->getTree());
}
} else {
// Inline notes
$nrec = Functions::getSubRecord($level, "$level NOTE", $factrec, $j + 1);
$text = $match[$j][1] . Functions::getCont($level + 1, $nrec);
$text = Filter::formatText($text, $fact->getParent()->getTree());
}
echo '';
echo $text;
if (!empty($noterec)) {
echo self::printFactSources($noterec, 1);
}
// 2 RESN tags. Note, there can be more than one, such as "privacy" and "locked"
if (preg_match_all("/\n2 RESN (.+)/", $factrec, $matches)) {
foreach ($matches[1] as $match) {
echo ' ', GedcomTag::getLabel('RESN'), ': ';
switch ($match) {
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 $match;
break;
}
echo '';
}
}
echo ' |
';
}
}
/**
* Print a row for the media tab on the individual page.
*
* @param Fact $fact
* @param int $level
*/
public static function printMainMedia(Fact $fact, $level) {
$factrec = $fact->getGedcom();
$parent = $fact->getParent();
if ($fact->isPendingAddition()) {
$styleadd = 'new';
$can_edit = $level == 1 && $fact->canEdit();
} elseif ($fact->isPendingDeletion()) {
$styleadd = 'old';
$can_edit = false;
} else {
$styleadd = '';
$can_edit = $level == 1 && $fact->canEdit();
}
// -- find source for each fact
preg_match_all('/(?:^|\n)' . $level . ' OBJE @(.*)@/', $factrec, $matches);
foreach ($matches[1] as $xref) {
$media = Media::getInstance($xref, $fact->getParent()->getTree());
// Allow access to "1 OBJE @non_existent_source@", so it can be corrected/deleted
if (!$media || $media->canShow()) {
if ($level > 1) {
echo '';
} else {
echo '
';
}
echo '';
preg_match("/^\d (\w*)/", $factrec, $factname);
$factlines = explode("\n", $factrec); // 1 BIRT Y\n2 SOUR ...
$factwords = explode(" ", $factlines[0]); // 1 BIRT Y
$factname = $factwords[1]; // BIRT
if ($factname == 'EVEN' || $factname == 'FACT') {
// Add ' EVEN' to provide sensible output for an event with an empty TYPE record
$ct = preg_match("/2 TYPE (.*)/", $factrec, $ematch);
if ($ct > 0) {
$factname = $ematch[1];
echo $factname;
} else {
echo GedcomTag::getLabel($factname, $parent);
}
} elseif ($can_edit) {
echo '';
echo GedcomTag::getLabel($factname, $parent), '';
echo '';
echo ' ';
echo ' ';
echo ' ';
echo ' ';
} else {
echo GedcomTag::getLabel($factname, $parent);
}
echo ' | ';
echo '';
if ($media) {
echo '';
echo $media->displayImage();
echo '';
echo '';
foreach ($media->getAllNames() as $name) {
if ($name['type'] != 'TITL') {
echo ' ';
}
echo $name['full'];
}
echo '';
echo '';
echo '';
echo GedcomTag::getLabelValue('FORM', $media->mimeType());
$imgsize = $media->getImageAttributes('main');
if (!empty($imgsize['WxH'])) {
echo GedcomTag::getLabelValue('__IMAGE_SIZE__', $imgsize['WxH']);
}
if ($media->getFilesizeraw() > 0) {
echo GedcomTag::getLabelValue('__FILE_SIZE__', $media->getFilesize());
}
$mediatype = $media->getMediaType();
if ($mediatype) {
echo GedcomTag::getLabelValue('TYPE', GedcomTag::getFileFormTypeValue($mediatype));
}
switch ($media->isPrimary()) {
case 'Y':
echo GedcomTag::getLabelValue('_PRIM', I18N::translate('yes'));
break;
case 'N':
echo GedcomTag::getLabelValue('_PRIM', I18N::translate('no'));
break;
}
echo FunctionsPrint::printFactNotes($media->getGedcom(), 1);
echo self::printFactSources($media->getGedcom(), 1);
} else {
echo $xref;
}
echo ' |
';
}
}
}
}