. */ declare(strict_types=1); namespace Fisharebest\Webtrees\Module; use Fisharebest\Webtrees\Auth; use Fisharebest\Webtrees\FontAwesome; use Fisharebest\Webtrees\Functions\FunctionsPrint; use Fisharebest\Webtrees\I18N; use Fisharebest\Webtrees\Individual; use Fisharebest\Webtrees\Menu; use Fisharebest\Webtrees\Theme; use Fisharebest\Webtrees\Tree; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; /** * Class HourglassChartModule */ class HourglassChartModule extends AbstractModule implements ModuleInterface, ModuleChartInterface { use ModuleChartTrait; // Defaults private const DEFAULT_GENERATIONS = '3'; private const DEFAULT_MAXIMUM_GENERATIONS = '9'; // Limits private const MINIMUM_GENERATIONS = 2; /** * How should this module be labelled on tabs, menus, etc.? * * @return string */ public function title(): string { /* I18N: Name of a module/chart */ return I18N::translate('Hourglass chart'); } /** * A sentence describing what this module does. * * @return string */ public function description(): string { /* I18N: Description of the “HourglassChart” module */ return I18N::translate('An hourglass chart of an individual’s ancestors and descendants.'); } /** * CSS class for the URL. * * @return string */ public function chartMenuClass(): string { return 'menu-chart-hourglass'; } /** * Return a menu item for this chart - for use in individual boxes. * * @param Individual $individual * * @return Menu|null */ public function chartBoxMenu(Individual $individual): ?Menu { return $this->chartMenu($individual); } /** * A form to request the chart parameters. * * @param Request $request * @param Tree $tree * * @return Response */ public function getChartAction(Request $request, Tree $tree): Response { $ajax = $request->get('ajax', ''); $xref = $request->get('xref', ''); $individual = Individual::getInstance($xref, $tree); Auth::checkIndividualAccess($individual); $maximum_generations = (int) $tree->getPreference('MAX_DESCENDANCY_GENERATIONS', self::DEFAULT_MAXIMUM_GENERATIONS); $default_generations = (int) $tree->getPreference('DEFAULT_PEDIGREE_GENERATIONS', self::DEFAULT_GENERATIONS); $generations = (int) $request->get('generations', $default_generations); $generations = min($generations, $maximum_generations); $generations = max($generations, self::MINIMUM_GENERATIONS); $show_spouse = (bool) $request->get('show_spouse'); if ($ajax === '1') { return $this->chart($individual, $generations, $show_spouse); } $ajax_url = $this->chartUrl($individual, [ 'ajax' => '1', ]); return $this->viewResponse('modules/hourglass-chart/chart-page', [ 'ajax_url' => $ajax_url, 'generations' => $generations, 'individual' => $individual, 'maximum_generations' => $maximum_generations, 'minimum_generations' => self::MINIMUM_GENERATIONS, 'module_name' => $this->name(), 'show_spouse' => $show_spouse, 'title' => $this->chartTitle($individual), ]); } /** * Generate the initial generations of the chart * * @param Request $request * @param Tree $tree * * @return Response */ protected function chart(Individual $individual, int $generations, bool $show_spouse): Response { ob_start(); $this->printDescendency($individual, 1, $generations, $show_spouse, true); $descendants = ob_get_clean(); ob_start(); $this->printPersonPedigree($individual, 1, $generations, $show_spouse); $ancestors = ob_get_clean(); return new Response(view('modules/hourglass-chart/chart', [ 'descendants' => $descendants, 'ancestors' => $ancestors, 'bhalfheight' => (int) (Theme::theme()->parameter('chart-box-y') / 2), 'module_name' => $this->name(), ])); } /** * @param Request $request * @param Tree $tree * * @return Response */ public function postAncestorsAction(Request $request, Tree $tree): Response { $xref = $request->get('xref', ''); $individual = Individual::getInstance($xref, $tree); Auth::checkIndividualAccess($individual); $show_spouse = (bool) $request->get('show_spouse'); ob_start(); $this->printPersonPedigree($individual, 0, 1, $show_spouse); $html = ob_get_clean(); return new Response($html); } /** * @param Request $request * @param Tree $tree * * @return Response */ public function postDescendantsAction(Request $request, Tree $tree): Response { $xref = $request->get('xref', ''); $individual = Individual::getInstance($xref, $tree); $show_spouse = (bool) $request->get('show_spouse'); ob_start(); $this->printDescendency($individual, 1, 2, $show_spouse, false); $html = ob_get_clean(); return new Response($html); } /** * Prints descendency of passed in person * * @param Individual $individual Show descendants of this individual * @param int $generation The current generation number * @param int $generations Show this number of generations * @param bool $show_spouse * @param bool $show_menu * * @return void */ private function printDescendency(Individual $individual, int $generation, int $generations, bool $show_spouse, bool $show_menu) { static $lastGenSecondFam = false; if ($generation > $generations) { return; } $pid = $individual->xref(); $tablealign = 'right'; $otablealign = 'left'; if (I18N::direction() === 'rtl') { $tablealign = 'left'; $otablealign = 'right'; } //-- put a space between families on the last generation if ($generation == $generations - 1) { if ($lastGenSecondFam) { echo '
'; } $lastGenSecondFam = true; } echo ''; echo ''; echo ''; echo '
'; $families = $individual->getSpouseFamilies(); $children = []; if ($generation < $generations) { // Put all of the children in a common array foreach ($families as $family) { foreach ($family->getChildren() as $child) { $children[] = $child; } } $ct = count($children); if ($ct > 0) { echo ''; for ($i = 0; $i < $ct; $i++) { $individual2 = $children[$i]; $chil = $individual2->xref(); echo ''; echo ''; // Print the lines if ($ct > 1) { if ($i == 0) { // First child echo ''; } elseif ($i == $ct - 1) { // Last child echo ''; } else { // Middle child echo ''; } } echo ''; } echo '
'; $this->printDescendency($individual2, $generation + 1, $generations, $show_spouse, false); echo '
'; } echo '
'; } // Print the descendency expansion arrow if ($generation == $generations) { $tbwidth = Theme::theme()->parameter('chart-box-x') + 16; for ($j = $generation; $j < $generations; $j++) { echo "

"; } $kcount = 0; foreach ($families as $family) { $kcount += $family->getNumberOfChildren(); } if ($kcount == 0) { echo ""; } else { echo FontAwesome::linkIcon('arrow-start', I18N::translate('Children'), [ 'href' => '#', 'data-route' => 'Descendants', 'data-xref' => $pid, 'data-spouses' => $show_spouse, 'data-tree' => $individual->tree()->name(), ]); //-- move the arrow up to line up with the correct box if ($show_spouse) { echo str_repeat('


', count($families)); } echo "
"; } } echo ''; } //-- add offset divs to make things line up better if ($generation == $generations) { echo "
'; echo FunctionsPrint::printPedigreePerson($individual); echo ' '; //----- Print the spouse if ($show_spouse) { foreach ($families as $family) { echo "
"; echo FunctionsPrint::printPedigreePerson($family->getSpouse($individual)); echo '
parameter('chart-box-x'), "px;'>
"; } } echo '
'; // For the root individual, print a down arrow that allows changing the root of tree if ($show_menu && $generation == 1) { echo '
'; echo FontAwesome::linkIcon('arrow-down', I18N::translate('Family'), [ 'href' => '#', 'id' => 'spouse-child-links', ]); echo '
'; echo '
'; foreach ($individual->getSpouseFamilies() as $family) { echo "" . I18N::translate('Family') . ''; $spouse = $family->getSpouse($individual); if ($spouse !== null) { echo '' . $spouse->getFullName() . ''; } foreach ($family->getChildren() as $child) { echo '' . $child->getFullName() . ''; } } //-- print the siblings foreach ($individual->getChildFamilies() as $family) { if ($family->getHusband() || $family->getWife()) { echo "" . I18N::translate('Parents') . ''; $husb = $family->getHusband(); if ($husb) { echo '' . $husb->getFullName() . ''; } $wife = $family->getWife(); if ($wife) { echo '' . $wife->getFullName() . ''; } } // filter out root person from children array so only siblings remain $siblings = array_filter($family->getChildren(), function (Individual $x) use ($individual): bool { return $x !== $individual; }); $count_siblings = count($siblings); if ($count_siblings > 0) { echo ''; echo $count_siblings > 1 ? I18N::translate('Siblings') : I18N::translate('Sibling'); echo ''; foreach ($siblings as $child) { echo '' . $child->getFullName() . ''; } } } echo '
'; echo '
'; echo '
'; } echo '
'; } /** * Prints pedigree of the person passed in. Which is the descendancy * * @param Individual $individual Show the pedigree of this individual * @param int $generation Current generation number * @param int $generations Show this number of generations * @param bool $show_spouse * * @return void */ private function printPersonPedigree(Individual $individual, int $generation, int $generations, bool $show_spouse) { if ($generation >= $generations) { return; } // handle pedigree n generations lines $genoffset = $generations; $family = $individual->getPrimaryChildFamily(); if ($family === null) { // Prints empty table columns for children w/o parents up to the max generation // This allows vertical line spacing to be consistent echo ''; echo ''; echo ''; echo '
' . Theme::theme()->individualBoxEmpty() . ' '; // Recursively get the father’s family $this->printPersonPedigree($individual, $generation + 1, $generations, $show_spouse); echo '
' . Theme::theme()->individualBoxEmpty() . ' '; // Recursively get the mother’s family $this->printPersonPedigree($individual, $generation + 1, $generations, $show_spouse); echo '
'; } else { echo ''; echo ''; echo ''; echo ''; echo ''; if ($family->getHusband()) { $ARID = $family->getHusband()->xref(); echo ''; } else { echo '', '', '', ''; if ($family->getWife()) { $ARID = $family->getWife()->xref(); echo ''; } echo '
'; //-- print the father box echo FunctionsPrint::printPedigreePerson($family->getHusband()); echo ''; if ($generation == $generations - 1 && $family->getHusband()->getChildFamilies()) { echo FontAwesome::linkIcon('arrow-end', I18N::translate('Parents'), [ 'href' => '#', 'data-route' => 'Ancestors', 'data-xref' => $ARID, 'data-spouses' => (int) $show_spouse, 'data-tree' => $family->getHusband()->tree()->name(), ]); } $this->printPersonPedigree($family->getHusband(), $generation + 1, $generations, $show_spouse); echo ' '; if ($generation < $genoffset - 1) { echo ''; for ($i = $generation; $i < ((2 ** (($genoffset - 1) - $generation)) / 2) + 2; $i++) { echo Theme::theme()->individualBoxEmpty(); echo ''; echo Theme::theme()->individualBoxEmpty(); echo ''; } echo '
'; } } echo '
'; echo FunctionsPrint::printPedigreePerson($family->getWife()); echo ''; if ($generation == $generations - 1 && $family->getWife()->getChildFamilies()) { echo FontAwesome::linkIcon('arrow-end', I18N::translate('Parents'), [ 'href' => '#', 'data-route' => 'Ancestors', 'data-xref' => $ARID, 'data-spouses' => (int) $show_spouse, 'data-tree' => $family->getWife()->tree()->name(), ]); } $this->printPersonPedigree($family->getWife(), $generation + 1, $generations, $show_spouse); echo '
'; } } }