. */ namespace Fisharebest\Webtrees\Controller; use Fisharebest\Webtrees\Auth; use Fisharebest\Webtrees\Fact; use Fisharebest\Webtrees\Family; use Fisharebest\Webtrees\Filter; use Fisharebest\Webtrees\FontAwesome; use Fisharebest\Webtrees\Functions\FunctionsPrint; use Fisharebest\Webtrees\Functions\FunctionsPrintFacts; use Fisharebest\Webtrees\GedcomCode\GedcomCodeName; use Fisharebest\Webtrees\GedcomTag; use Fisharebest\Webtrees\Html; use Fisharebest\Webtrees\I18N; use Fisharebest\Webtrees\Individual; use Fisharebest\Webtrees\Menu; use Fisharebest\Webtrees\Module; use Fisharebest\Webtrees\Module\ModuleTabInterface; /** * Controller for the individual page */ class IndividualController extends GedcomRecordController { /** @var int Count of names */ public $name_count = 0; /** @var int Count of names. */ public $total_names = 0; /** * Startup activity * * @param Individual|null $record */ public function __construct($record) { parent::__construct($record); // If we can display the details, add them to the page header if ($this->record && $this->record->canShow()) { $this->setPageTitle($this->record->getFullName() . ' ' . $this->record->getLifeSpan()); } } /** * Get significant information from this page, to allow other pages such as * charts and reports to initialise with the same records * * @return Individual */ public function getSignificantIndividual() { if ($this->record) { return $this->record; } return parent::getSignificantIndividual(); } /** * Get significant information from this page, to allow other pages such as * charts and reports to initialise with the same records * * @return Family */ public function getSignificantFamily() { if ($this->record) { foreach ($this->record->getChildFamilies() as $family) { return $family; } foreach ($this->record->getSpouseFamilies() as $family) { return $family; } } return parent::getSignificantFamily(); } /** * Which tabs should we show on this individual's page. * We don't show empty tabs. * * @return ModuleTabInterface[] */ public function getTabs() { $active_tabs = Module::getActiveTabs($this->record->getTree()); return array_filter($active_tabs, function(ModuleTabInterface $tab) { return $tab->hasTabContent(); }); } /** * Handle AJAX requests - to generate the tab content */ public function ajaxRequest() { header('Content-Type: text/html; charset=UTF-8'); $tab = Filter::get('module'); $tabs = $this->getTabs(); if (!array_key_exists($tab, $tabs)) { http_response_code(404); } else { echo $tabs[$tab]->getTabContent(); } } /** * Format a name record * * @param int $n * @param Fact $fact * * @return string */ public function formatNameRecord($n, Fact $fact) { $individual = $fact->getParent(); // Create a dummy record, so we can extract the formatted NAME value from it. $dummy = new Individual( 'xref', "0 @xref@ INDI\n1 DEAT Y\n" . $fact->getGedcom(), null, $individual->getTree() ); $dummy->setPrimaryName(0); // Make sure we use the name from "1 NAME" $container_class = 'card'; $content_class = 'collapse'; $aria = 'false'; if ($n === 0) { $content_class = 'collapse show'; $aria = 'true'; } if ($fact->isPendingDeletion()) { $container_class .= ' old'; } elseif ($fact->isPendingAddition()) { $container_class .= ' new'; } ob_start(); echo '
', I18N::translate('Name'), '
'; echo '
', $dummy->getFullName(), '
'; $ct = preg_match_all('/\n2 (\w+) (.*)/', $fact->getGedcom(), $nmatch, PREG_SET_ORDER); for ($i = 0; $i < $ct; $i++) { $tag = $nmatch[$i][1]; if ($tag != 'SOUR' && $tag != 'NOTE' && $tag != 'SPFX') { echo '
', GedcomTag::getLabel($tag, $this->record), '
'; echo '
'; // Before using dir="auto" on this field, note that Gecko treats this as an inline element but WebKit treats it as a block element if (isset($nmatch[$i][2])) { $name = Html::escape($nmatch[$i][2]); $name = str_replace('/', '', $name); $name = preg_replace('/(\S*)\*/', '\\1', $name); switch ($tag) { case 'TYPE': echo GedcomCodeName::getValue($name, $this->record); break; case 'SURN': // The SURN field is not necessarily the surname. // Where it is not a substring of the real surname, show it after the real surname. $surname = Html::escape($dummy->getAllNames()[0]['surname']); $surns = preg_replace('/, */', ' ', $nmatch[$i][2]); if (strpos($dummy->getAllNames()[0]['surname'], $surns) !== false) { echo '' . $surname . ''; } else { echo I18N::translate('%1$s (%2$s)', '' . $surname . '', '' . $name . ''); } break; default: echo '' . $name . ''; break; } } echo '
'; echo '
'; } } if (preg_match("/\n2 SOUR/", $fact->getGedcom())) { echo '
', FunctionsPrintFacts::printFactSources($fact->getGedcom(), 2), '
'; } if (preg_match("/\n2 NOTE/", $fact->getGedcom())) { echo '
', FunctionsPrint::printFactNotes($fact->getGedcom(), 2), '
'; } $content = ob_get_clean(); if ($this->record->canEdit() && !$fact->isPendingDeletion()) { $edit_links = FontAwesome::linkIcon('delete', I18N::translate('Delete this name'), ['class' => 'btn btn-link', 'href' => '#', 'onclick' => 'return delete_fact("' . I18N::translate('Are you sure you want to delete this fact?') . '", "' . $this->record->getXref() . '", "' . $fact->getFactId() . '");']) . FontAwesome::linkIcon('edit', I18N::translate('Edit the name'), ['class' => 'btn btn-link', 'href' => 'edit_interface.php?action=editname&xref=' . $this->record->getXref() . '&fact_id=' . $fact->getFactId() . '&ged=' . $this->record->getTree()->getNameHtml()]); } else { $edit_links = ''; } return '
' . $content . '
'; } /** * print information for a sex record * * @param Fact $fact * * @return string */ public function formatSexRecord(Fact $fact) { $individual = $fact->getParent(); switch ($fact->getValue()) { case 'M': $sex = I18N::translate('Male'); break; case 'F': $sex = I18N::translate('Female'); break; default: $sex = I18N::translateContext('unknown gender', 'Unknown'); break; } $container_class = 'card'; if ($fact->isPendingDeletion()) { $container_class .= ' old'; } elseif ($fact->isPendingAddition()) { $container_class .= ' new'; } if ($individual->canEdit() && !$fact->isPendingDeletion()) { $edit_links = FontAwesome::linkIcon('edit', I18N::translate('Edit the gender'), ['class' => 'btn btn-link', 'href' => 'edit_interface.php?action=edit&xref=' . $individual->getXref() . '&fact_id=' . $fact->getFactId() . '&ged=' . $individual->getTree()->getNameHtml() ]); } else { $edit_links = ''; } return '
'; } /** * get edit menu */ public function getEditMenu() { if (!$this->record || $this->record->isPendingDeletion()) { return null; } // edit menu $menu = new Menu(I18N::translate('Edit'), '#', 'menu-indi'); if (Auth::isEditor($this->record->getTree())) { // delete $menu->addSubmenu(new Menu(I18N::translate('Delete'), '#', 'menu-indi-del', [ 'onclick' => 'return delete_record("' . I18N::translate('Are you sure you want to delete ā€œ%sā€?', strip_tags($this->record->getFullName())) . '", "' . $this->record->getXref() . '");', ])); } // edit raw if (Auth::isAdmin() || Auth::isEditor($this->record->getTree()) && $this->record->getTree()->getPreference('SHOW_GEDCOM_RECORD')) { $menu->addSubmenu(new Menu(I18N::translate('Edit the raw GEDCOM'), 'edit_interface.php?action=editraw&ged=' . $this->record->getTree()->getNameHtml() . '&xref=' . $this->record->getXref(), 'menu-indi-editraw')); } return $menu; } /** * get the person box stylesheet class for the given person * * @param Individual $person * * @return string returns 'person_box', 'person_boxF', or 'person_boxNN' */ public function getPersonStyle($person) { switch ($person->getSex()) { case 'M': $class = 'person_box'; break; case 'F': $class = 'person_boxF'; break; default: $class = 'person_boxNN'; break; } if ($person->isPendingDeletion()) { $class .= ' old'; } elseif ($person->isPendingAddtion()) { $class .= ' new'; } return $class; } /** * Get significant information from this page, to allow other pages such as * charts and reports to initialise with the same records * * @return string */ public function getSignificantSurname() { if ($this->record) { list($surn) = explode(',', $this->record->getSortName()); return $surn; } else { return ''; } } /** * Get the contents of sidebar. * * @return string */ public function getSideBarContent() { $html = ''; foreach (Module::getActiveSidebars($this->record->getTree()) as $module) { if ($module->hasSidebarContent()) { $class = $module->getName() === 'family_nav' ? 'collapse show' : 'collapse'; $aria = $module->getName() === 'family_nav' ? 'true' : 'false'; $html .= '
'; } } if ($html) { return ''; } else { return ''; } } /** * Get the description for the family. * * For example, "XXX's family with new wife". * * @param Family $family * @param Individual $individual * * @return string */ public function getSpouseFamilyLabel(Family $family, Individual $individual) { $spouse = $family->getSpouse($individual); if ($spouse) { return /* I18N: %s is the spouse name */ I18N::translate('Family with %s', $spouse->getFullName()); } else { return $family->getFullName(); } } }