. */ namespace Fisharebest\Webtrees\Functions; use Fisharebest\Webtrees\Controller\SearchController; use Fisharebest\Webtrees\Date; use Fisharebest\Webtrees\Fact; use Fisharebest\Webtrees\Family; use Fisharebest\Webtrees\Filter; use Fisharebest\Webtrees\GedcomCode\GedcomCodeStat; use Fisharebest\Webtrees\GedcomCode\GedcomCodeTemp; use Fisharebest\Webtrees\GedcomRecord; use Fisharebest\Webtrees\GedcomTag; use Fisharebest\Webtrees\I18N; use Fisharebest\Webtrees\Individual; use Fisharebest\Webtrees\Module; use Fisharebest\Webtrees\Module\CensusAssistantModule; use Fisharebest\Webtrees\Note; use Fisharebest\Webtrees\Place; use Fisharebest\Webtrees\Session; use Fisharebest\Webtrees\Theme; use Fisharebest\Webtrees\Tree; use Rhumsaa\Uuid\Uuid; /** * Class FunctionsPrint - common functions */ class FunctionsPrint { /** * print the information for an individual chart box * * find and print a given individuals information for a pedigree chart * * @param Individual $person The person to print * @param int $show_full The style to print the box in, 0 for smaller boxes, 1 for larger boxes */ public static function printPedigreePerson(Individual $person = null, $show_full = 1) { switch ($show_full) { case 0: if ($person) { echo Theme::theme()->individualBoxSmall($person); } else { echo Theme::theme()->individualBoxSmallEmpty(); } break; case 1: if ($person) { echo Theme::theme()->individualBox($person); } else { echo Theme::theme()->individualBoxEmpty(); } break; } } /** * print a note record * * @param string $text * @param int $nlevel the level of the note record * @param string $nrec the note record to print * @param bool $textOnly Don't print the "Note: " introduction * * @return string */ public static function printNoteRecord($text, $nlevel, $nrec, $textOnly = false) { global $WT_TREE; $text .= Functions::getCont($nlevel, $nrec); // Check if shared note (we have already checked that it exists) if (preg_match('/^0 @(' . WT_REGEX_XREF . ')@ NOTE/', $nrec, $match)) { $note = Note::getInstance($match[1], $WT_TREE); $label = 'SHARED_NOTE'; // If Census assistant installed, allow it to format the note if (Module::getModuleByName('GEDFact_assistant')) { $html = CensusAssistantModule::formatCensusNote($note); } else { $html = Filter::formatText($note->getNote(), $WT_TREE); } } else { $note = null; $label = 'NOTE'; $html = Filter::formatText($text, $WT_TREE); } if ($textOnly) { return strip_tags($text); } if (strpos($text, "\n") === false) { // A one-line note? strip the block-level tags, so it displays inline return GedcomTag::getLabelValue($label, strip_tags($html, '')); } elseif ($WT_TREE->getPreference('EXPAND_NOTES')) { // A multi-line note, and we're expanding notes by default return GedcomTag::getLabelValue($label, $html); } else { // A multi-line note, with an expand/collapse option $element_id = Uuid::uuid4(); // NOTE: class "note-details" is (currently) used only by some third-party themes if ($note) { $first_line = '' . $note->getFullName() . ''; } else { list($text) = explode("\n", strip_tags($html)); $first_line = strlen($text) > 100 ? mb_substr($text, 0, 100) . I18N::translate('…') : $text; } return '
' . ' ' . GedcomTag::getLabel($label) . ': ' . '' . $first_line . '' . '
' . ''; } } /** * Print all of the notes in this fact record * * @param string $factrec The factrecord to print the notes from * @param int $level The level of the factrecord * @param bool $textOnly Don't print the "Note: " introduction * * @return string HTML */ public static function printFactNotes($factrec, $level, $textOnly = false) { global $WT_TREE; $data = ''; $previous_spos = 0; $nlevel = $level + 1; $ct = preg_match_all("/$level NOTE (.*)/", $factrec, $match, PREG_SET_ORDER); for ($j = 0; $j < $ct; $j++) { $spos1 = strpos($factrec, $match[$j][0], $previous_spos); $spos2 = strpos($factrec . "\n$level", "\n$level", $spos1 + 1); if (!$spos2) { $spos2 = strlen($factrec); } $previous_spos = $spos2; $nrec = substr($factrec, $spos1, $spos2 - $spos1); if (!isset($match[$j][1])) { $match[$j][1] = ''; } if (!preg_match('/@(.*)@/', $match[$j][1], $nmatch)) { $data .= self::printNoteRecord($match[$j][1], $nlevel, $nrec, $textOnly); } else { $note = Note::getInstance($nmatch[1], $WT_TREE); if ($note) { if ($note->canShow()) { $noterec = $note->getGedcom(); $nt = preg_match("/0 @$nmatch[1]@ NOTE (.*)/", $noterec, $n1match); $data .= self::printNoteRecord(($nt > 0) ? $n1match[1] : "", 1, $noterec, $textOnly); if (!$textOnly) { if (strpos($noterec, '1 SOUR') !== false) { $data .= FunctionsPrintFacts::printFactSources($noterec, 1); } } } } else { $data = '
' . I18N::translate('Note') . ': ' . $nmatch[1] . '
'; } } if (!$textOnly) { if (strpos($factrec, "$nlevel SOUR") !== false) { $data .= "
"; $data .= FunctionsPrintFacts::printFactSources($nrec, $nlevel); $data .= "
"; } } } return $data; } /** * Print a link for a popup help window. * * @param string $help_topic * @param string $module * * @return string */ public static function helpLink($help_topic, $module = '') { return ' '; } /** * Print an external help link to the wiki site. * * @deprecated - nothing should be so complicated that it needs lengthy instructions! * * @param string $topic * * @return string */ public static function wikiHelpLink($topic) { return ''; } /** * When a user has searched for text, highlight any matches in * the displayed string. * * @param string $string * * @return string */ public static function highlightSearchHits($string) { global $controller; if ($controller instanceof SearchController && $controller->query) { // TODO: when a search contains multiple words, we search independently. // e.g. searching for "FOO BAR" will find records containing both FOO and BAR. // However, we only highlight the original search string, not the search terms. // The controller needs to provide its "query_terms" array. $regex = array(); foreach (array($controller->query) as $search_term) { $regex[] = preg_quote($search_term, '/'); } // Match these strings, provided they do not occur inside HTML tags $regex = '(' . implode('|', $regex) . ')(?![^<]*>)'; return preg_replace('/' . $regex . '/i', '$1', $string); } else { return $string; } } /** * Format age of parents in HTML * * @param Individual $person child * @param Date $birth_date * * @return string HTML */ public static function formatParentsAges(Individual $person, Date $birth_date) { $html = ''; $families = $person->getChildFamilies(); // Multiple sets of parents (e.g. adoption) cause complications, so ignore. if ($birth_date->isOK() && count($families) == 1) { $family = current($families); foreach ($family->getSpouses() as $parent) { if ($parent->getBirthDate()->isOK()) { $sex = $parent->getSexImage(); $age = Date::getAge($parent->getBirthDate(), $birth_date, 2); $deatdate = $parent->getDeathDate(); switch ($parent->getSex()) { case 'F': // Highlight mothers who die in childbirth or shortly afterwards if ($deatdate->isOK() && $deatdate->maximumJulianDay() < $birth_date->minimumJulianDay() + 90) { $html .= ' ' . $sex . $age . ''; } else { $html .= ' ' . $sex . $age . ''; } break; case 'M': // Highlight fathers who die before the birth if ($deatdate->isOK() && $deatdate->maximumJulianDay() < $birth_date->minimumJulianDay()) { $html .= ' ' . $sex . $age . ''; } else { $html .= ' ' . $sex . $age . ''; } break; default: $html .= ' ' . $sex . $age . ''; break; } } } if ($html) { $html = '' . $html . ''; } } return $html; } /** * Print fact DATE/TIME * * @param Fact $event event containing the date/age * @param GedcomRecord $record the person (or couple) whose ages should be printed * @param bool $anchor option to print a link to calendar * @param bool $time option to print TIME value * * @return string */ public static function formatFactDate(Fact $event, GedcomRecord $record, $anchor, $time) { global $pid; $factrec = $event->getGedcom(); $html = ''; // Recorded age if (preg_match('/\n2 AGE (.+)/', $factrec, $match)) { $fact_age = $match[1]; } else { $fact_age = ''; } if (preg_match('/\n2 HUSB\n3 AGE (.+)/', $factrec, $match)) { $husb_age = $match[1]; } else { $husb_age = ''; } if (preg_match('/\n2 WIFE\n3 AGE (.+)/', $factrec, $match)) { $wife_age = $match[1]; } else { $wife_age = ''; } // Calculated age if (preg_match('/\n2 DATE (.+)/', $factrec, $match)) { $date = new Date($match[1]); $html .= ' ' . $date->display($anchor); // time if ($time && preg_match('/\n3 TIME (.+)/', $factrec, $match)) { $html .= ' – ' . $match[1] . ''; } $fact = $event->getTag(); if ($record instanceof Individual) { if ($fact === 'BIRT' && $record->getTree()->getPreference('SHOW_PARENTS_AGE')) { // age of parents at child birth $html .= self::formatParentsAges($record, $date); } elseif ($fact !== 'BIRT' && $fact !== 'CHAN' && $fact !== '_TODO') { // age at event $birth_date = $record->getBirthDate(); // Can't use getDeathDate(), as this also gives BURI/CREM events, which // wouldn't give the correct "days after death" result for people with // no DEAT. $death_event = $record->getFirstFact('DEAT'); if ($death_event) { $death_date = $death_event->getDate(); } else { $death_date = new Date(''); } $ageText = ''; if ((Date::compare($date, $death_date) <= 0 || !$record->isDead()) || $fact == 'DEAT') { // Before death, print age $age = Date::getAgeGedcom($birth_date, $date); // Only show calculated age if it differs from recorded age if ($age != '') { if ( $fact_age != '' && $fact_age != $age || $fact_age == '' && $husb_age == '' && $wife_age == '' || $husb_age != '' && $record->getSex() == 'M' && $husb_age != $age || $wife_age != '' && $record->getSex() == 'F' && $wife_age != $age ) { if ($age != "0d") { $ageText = '(' . I18N::translate('Age') . ' ' . FunctionsDate::getAgeAtEvent($age) . ')'; } } } } if ($fact != 'DEAT' && Date::compare($date, $death_date) >= 0) { // After death, print time since death $age = FunctionsDate::getAgeAtEvent(Date::getAgeGedcom($death_date, $date)); if ($age != '') { if (Date::getAgeGedcom($death_date, $date) == "0d") { $ageText = '(' . I18N::translate('on the date of death') . ')'; } else { $ageText = '(' . $age . ' ' . I18N::translate('after death') . ')'; // Family events which occur after death are probably errors if ($event->getParent() instanceof Family) { $ageText .= ''; } } } } if ($ageText) { $html .= ' ' . $ageText . ''; } } } elseif ($record instanceof Family) { $indi = Individual::getInstance($pid, $record->getTree()); if ($indi) { $birth_date = $indi->getBirthDate(); $death_date = $indi->getDeathDate(); $ageText = ''; if (Date::compare($date, $death_date) <= 0) { $age = Date::getAgeGedcom($birth_date, $date); // Only show calculated age if it differs from recorded age if ($age != '' && $age > 0) { if ( $fact_age != '' && $fact_age != $age || $fact_age == '' && $husb_age == '' && $wife_age == '' || $husb_age != '' && $indi->getSex() == 'M' && $husb_age != $age || $wife_age != '' && $indi->getSex() == 'F' && $wife_age != $age ) { $ageText = '(' . I18N::translate('Age') . ' ' . FunctionsDate::getAgeAtEvent($age) . ')'; } } } if ($ageText) { $html .= ' ' . $ageText . ''; } } } } elseif (strpos($factrec, "\n2 PLAC ") === false) { // There is no DATE. If there is also no PLAC, then print "yes" $html .= I18N::translate('yes'); } // print gedcom ages foreach (array(GedcomTag::getLabel('AGE') => $fact_age, GedcomTag::getLabel('HUSB') => $husb_age, GedcomTag::getLabel('WIFE') => $wife_age) as $label => $age) { if ($age != '') { $html .= ' ' . $label . ': ' . FunctionsDate::getAgeAtEvent($age) . ''; } } return $html; } /** * print fact PLACe TEMPle STATus * * @param Fact $event gedcom fact record * @param bool $anchor to print a link to placelist * @param bool $sub_records to print place subrecords * @param bool $lds to print LDS TEMPle and STATus * * @return string HTML */ public static function formatFactPlace(Fact $event, $anchor = false, $sub_records = false, $lds = false) { if ($anchor) { // Show the full place name, for facts/events tab $html = '' . $event->getPlace()->getFullName() . ''; } else { // Abbreviate the place name, for chart boxes return $event->getPlace()->getShortName(); } if ($sub_records) { $placerec = Functions::getSubRecord(2, '2 PLAC', $event->getGedcom()); if (!empty($placerec)) { if (preg_match_all('/\n3 (?:_HEB|ROMN) (.+)/', $placerec, $matches)) { foreach ($matches[1] as $match) { $wt_place = new Place($match, $event->getParent()->getTree()); $html .= ' - ' . $wt_place->getFullName(); } } $map_lati = ""; $cts = preg_match('/\d LATI (.*)/', $placerec, $match); if ($cts > 0) { $map_lati = $match[1]; $html .= '
' . GedcomTag::getLabel('LATI') . ': ' . $map_lati; } $map_long = ''; $cts = preg_match('/\d LONG (.*)/', $placerec, $match); if ($cts > 0) { $map_long = $match[1]; $html .= ' ' . GedcomTag::getLabel('LONG') . ': ' . $map_long; } if ($map_lati && $map_long) { $map_lati = trim(strtr($map_lati, "NSEW,�", " - -. ")); // S5,6789 ==> -5.6789 $map_long = trim(strtr($map_long, "NSEW,�", " - -. ")); // E3.456� ==> 3.456 $html .= ' '; $html .= ' '; $html .= ' '; } if (preg_match('/\d NOTE (.*)/', $placerec, $match)) { $html .= '
' . self::printFactNotes($placerec, 3); } } } if ($lds) { if (preg_match('/2 TEMP (.*)/', $event->getGedcom(), $match)) { $html .= '
' . I18N::translate('LDS temple') . ': ' . GedcomCodeTemp::templeName($match[1]); } if (preg_match('/2 STAT (.*)/', $event->getGedcom(), $match)) { $html .= '
' . I18N::translate('Status') . ': ' . GedcomCodeStat::statusName($match[1]); if (preg_match('/3 DATE (.*)/', $event->getGedcom(), $match)) { $date = new Date($match[1]); $html .= ', ' . GedcomTag::getLabel('STAT:DATE') . ': ' . $date->display(); } } } return $html; } /** * Check for facts that may exist only once for a certain record type. * If the fact already exists in the second array, delete it from the first one. * * @param string[] $uniquefacts * @param Fact[] $recfacts * @param string $type * * @return string[] */ public static function checkFactUnique($uniquefacts, $recfacts, $type) { foreach ($recfacts as $factarray) { $fact = false; if (is_object($factarray)) { $fact = $factarray->getTag(); } else { if ($type === 'SOUR' || $type === 'REPO') { $factrec = $factarray[0]; } if ($type === 'FAM' || $type === 'INDI') { $factrec = $factarray[1]; } $ft = preg_match("/1 (\w+)(.*)/", $factrec, $match); if ($ft > 0) { $fact = trim($match[1]); } } if ($fact !== false) { $key = array_search($fact, $uniquefacts); if ($key !== false) { unset($uniquefacts[$key]); } } } return $uniquefacts; } /** * Print a new fact box on details pages * * @param string $id the id of the person, family, source etc the fact will be added to * @param array $usedfacts an array of facts already used in this record * @param string $type the type of record INDI, FAM, SOUR etc */ public static function printAddNewFact($id, $usedfacts, $type) { global $WT_TREE; // -- Add from clipboard if (is_array(Session::get('clipboard'))) { $newRow = true; foreach (array_reverse(Session::get('clipboard'), true) as $fact_id => $fact) { if ($fact["type"] == $type || $fact["type"] == 'all') { if ($newRow) { $newRow = false; echo ''; echo I18N::translate('Add from clipboard'), ''; echo '
'; echo ''; echo '   "; echo '
', "\n"; } } // -- Add from pick list switch ($type) { case "INDI": $addfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('INDI_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY); $uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('INDI_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY); $quickfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('INDI_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY); break; case "FAM": $addfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('FAM_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY); $uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('FAM_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY); $quickfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('FAM_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY); break; case "SOUR": $addfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('SOUR_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY); $uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('SOUR_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY); $quickfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('SOUR_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY); break; case "NOTE": $addfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('NOTE_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY); $uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('NOTE_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY); $quickfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('NOTE_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY); break; case "REPO": $addfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('REPO_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY); $uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('REPO_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY); $quickfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('REPO_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY); break; default: return; } $addfacts = array_merge(self::checkFactUnique($uniquefacts, $usedfacts, $type), $addfacts); $quickfacts = array_intersect($quickfacts, $addfacts); $translated_addfacts = array(); foreach ($addfacts as $addfact) { $translated_addfacts[$addfact] = GedcomTag::getLabel($addfact); } uasort($translated_addfacts, function ($x, $y) { return I18N::strcasecmp(I18N::translate($x), I18N::translate($y)); }); echo ''; echo I18N::translate('Fact or event'); echo ''; echo ''; echo '
'; echo ''; echo ''; echo ''; foreach ($quickfacts as $fact) { echo '', GedcomTag::getLabel($fact), ''; } echo '
'; echo ''; } /** * javascript declaration for calendar popup */ public static function initializeCalendarPopup() { global $controller; $controller->addInlineJavascript(' cal_setMonthNames( "' . I18N::translateContext('NOMINATIVE', 'January') . '", "' . I18N::translateContext('NOMINATIVE', 'February') . '", "' . I18N::translateContext('NOMINATIVE', 'March') . '", "' . I18N::translateContext('NOMINATIVE', 'April') . '", "' . I18N::translateContext('NOMINATIVE', 'May') . '", "' . I18N::translateContext('NOMINATIVE', 'June') . '", "' . I18N::translateContext('NOMINATIVE', 'July') . '", "' . I18N::translateContext('NOMINATIVE', 'August') . '", "' . I18N::translateContext('NOMINATIVE', 'September') . '", "' . I18N::translateContext('NOMINATIVE', 'October') . '", "' . I18N::translateContext('NOMINATIVE', 'November') . '", "' . I18N::translateContext('NOMINATIVE', 'December') . '" ) cal_setDayHeaders( "' . I18N::translate('Sun') . '", "' . I18N::translate('Mon') . '", "' . I18N::translate('Tue') . '", "' . I18N::translate('Wed') . '", "' . I18N::translate('Thu') . '", "' . I18N::translate('Fri') . '", "' . I18N::translate('Sat') . '" ) cal_setWeekStart(' . I18N::firstDay() . '); '); } /** * HTML link to find an individual. * * @param string $element_id * @param string $indiname * @param Tree $tree * * @return string */ public static function printFindIndividualLink($element_id, $indiname = '', $tree = null) { global $WT_TREE; if ($tree === null) { $tree = $WT_TREE; } return ''; } /** * HTML link to find a place. * * @param string $element_id * * @return string */ public static function printFindPlaceLink($element_id) { return ''; } /** * HTML link to find a family. * * @param string $element_id * * @return string */ public static function printFindFamilyLink($element_id) { return ''; } /** * HTML link to open the special character window. * * @param string $element_id * * @return string */ public static function printSpecialCharacterLink($element_id) { return ''; } /** * HTML element to insert a value from a list. * * @param string $element_id * @param string[] $choices */ public static function printAutoPasteLink($element_id, $choices) { echo ''; foreach ($choices as $choice) { echo '", $choice, ' '; } echo ''; } /** * HTML link to find a source. * * @param string $element_id * @param string $sourcename * * @return string */ public static function printFindSourceLink($element_id, $sourcename = '') { return ''; } /** * HTML link to find a note. * * @param string $element_id * @param string $notename * * @return string */ public static function printFindNoteLink($element_id, $notename = '') { return ''; } /** * HTML link to find a repository. * * @param string $element_id * * @return string */ public static function printFindRepositoryLink($element_id) { return ''; } /** * HTML link to find a media object. * * @param string $element_id * @param string $choose * * @return string */ public static function printFindMediaLink($element_id, $choose = '') { return ''; } /** * HTML link to find a fact. * * @param string $element_id * * @return string */ public static function printFindFactLink($element_id) { return ''; } /** * Summary of LDS ordinances. * * @param Individual $individual * * @return string */ public static function getLdsSummary(Individual $individual) { $BAPL = $individual->getFacts('BAPL') ? 'B' : '_'; $ENDL = $individual->getFacts('ENDL') ? 'E' : '_'; $SLGC = $individual->getFacts('SLGC') ? 'C' : '_'; $SLGS = '_'; foreach ($individual->getSpouseFamilies() as $family) { if ($family->getFacts('SLGS')) { $SLGS = ''; } } return $BAPL . $ENDL . $SLGS . $SLGC; } }