. */ declare(strict_types=1); namespace Fisharebest\Webtrees\Module; use Fisharebest\Webtrees\Auth; use Fisharebest\Webtrees\Contracts\UserInterface; use Fisharebest\Webtrees\Functions\FunctionsPrint; use Fisharebest\Webtrees\I18N; use Fisharebest\Webtrees\Individual; use Fisharebest\Webtrees\Menu; use Fisharebest\Webtrees\Tree; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class HourglassChartModule */ class HourglassChartModule extends AbstractModule implements ModuleChartInterface { use ModuleChartTrait; // Defaults private const DEFAULT_GENERATIONS = '3'; private const DEFAULT_MAXIMUM_GENERATIONS = '9'; // Limits private const MAXIMUM_GENERATIONS = 10; private const MINIMUM_GENERATIONS = 2; /** * How should this module be identified in the control panel, 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 * @param UserInterface $user * * @return Response */ public function getChartAction(Request $request, Tree $tree, UserInterface $user): Response { $ajax = (bool) $request->get('ajax'); $xref = $request->get('xref', ''); $individual = Individual::getInstance($xref, $tree); Auth::checkIndividualAccess($individual); Auth::checkComponentAccess($this, 'chart', $tree, $user); $generations = (int) $request->get('generations', self::DEFAULT_GENERATIONS); $generations = min($generations, self::MAXIMUM_GENERATIONS); $generations = max($generations, self::MINIMUM_GENERATIONS); $show_spouse = (bool) $request->get('show_spouse'); if ($ajax) { return $this->chart($individual, $generations, $show_spouse); } $ajax_url = $this->chartUrl($individual, [ 'ajax' => true, ]); return $this->viewResponse('modules/hourglass-chart/page', [ 'ajax_url' => $ajax_url, 'generations' => $generations, 'individual' => $individual, 'maximum_generations' => self::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 Individual $individual * @param int $generations * @param bool $show_spouse * * @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) (app(ModuleThemeInterface::class)->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 { $show_spouse = (bool) $request->get('show_spouse'); $xref = $request->get('xref', ''); $individual = Individual::getInstance($xref, $tree); if ($individual === null) { throw new NotFoundHttpException(); } 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): void { 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->spouseFamilies(); $children = []; if ($generation < $generations) { // Put all of the children in a common array foreach ($families as $family) { foreach ($family->children() 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 = app(ModuleThemeInterface::class)->parameter('chart-box-x') + 16; for ($j = $generation; $j < $generations; $j++) { echo "

"; } $kcount = 0; foreach ($families as $family) { $kcount += $family->numberOfChildren(); } if ($kcount == 0) { echo ""; } else { echo '' . view('icons/arrow-left') . ''; //-- 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->spouse($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 '' . view('icons/arrow-down') . ''; echo '
'; echo '
'; foreach ($individual->spouseFamilies() as $family) { echo "" . I18N::translate('Family') . ''; $spouse = $family->spouse($individual); if ($spouse !== null) { echo '' . $spouse->fullName() . ''; } foreach ($family->children() as $child) { echo '' . $child->fullName() . ''; } } //-- print the siblings foreach ($individual->childFamilies() as $family) { if ($family->husband() || $family->wife()) { echo "" . I18N::translate('Parents') . ''; $husb = $family->husband(); if ($husb) { echo '' . $husb->fullName() . ''; } $wife = $family->wife(); if ($wife) { echo '' . $wife->fullName() . ''; } } // filter out root person from children array so only siblings remain $siblings = $family->children()->filter(function (Individual $x) use ($individual): bool { return $x !== $individual; }); if ($siblings->count() > 0) { echo ''; echo $siblings->count() > 1 ? I18N::translate('Siblings') : I18N::translate('Sibling'); echo ''; foreach ($siblings as $child) { echo '' . $child->fullName() . ''; } } } 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): void { if ($generation >= $generations) { return; } // handle pedigree n generations lines $genoffset = $generations; $family = $individual->primaryChildFamily(); 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 '
' . app(ModuleThemeInterface::class)->individualBoxEmpty() . ' '; // Recursively get the father’s family $this->printPersonPedigree($individual, $generation + 1, $generations, $show_spouse); echo '
' . app(ModuleThemeInterface::class)->individualBoxEmpty() . ' '; // Recursively get the mother’s family $this->printPersonPedigree($individual, $generation + 1, $generations, $show_spouse); echo '
'; } else { echo ''; echo ''; echo ''; echo ''; echo ''; if ($family->husband()) { $ARID = $family->husband()->xref(); echo ''; } else { echo '', '', '', ''; if ($family->wife()) { $ARID = $family->wife()->xref(); echo ''; } echo '
'; //-- print the father box echo FunctionsPrint::printPedigreePerson($family->husband()); echo ''; if ($generation == $generations - 1 && $family->husband()->childFamilies()) { echo '' . view('icons/arrow-right') . ''; } $this->printPersonPedigree($family->husband(), $generation + 1, $generations, $show_spouse); echo ' '; if ($generation < $genoffset - 1) { echo ''; for ($i = $generation; $i < ((2 ** (($genoffset - 1) - $generation)) / 2) + 2; $i++) { echo app(ModuleThemeInterface::class)->individualBoxEmpty(); echo ''; echo app(ModuleThemeInterface::class)->individualBoxEmpty(); echo ''; } echo '
'; } } echo '
'; echo FunctionsPrint::printPedigreePerson($family->wife()); echo ''; if ($generation == $generations - 1 && $family->wife()->childFamilies()) { echo '' . view('icons/arrow-right') . ''; } $this->printPersonPedigree($family->wife(), $generation + 1, $generations, $show_spouse); echo '
'; } } }