.
*/
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 stdClass;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* Class FamilyBookChartModule
*/
class FamilyBookChartModule extends AbstractModule implements ModuleChartInterface
{
use ModuleChartTrait;
// Defaults
private const DEFAULT_GENERATIONS = '2';
private const DEFAULT_DESCENDANT_GENERATIONS = '5';
private const DEFAULT_MAXIMUM_GENERATIONS = '9';
// Limits
public const MINIMUM_GENERATIONS = 2;
public const MAXIMUM_GENERATIONS = 10;
/** @var stdClass */
private $box;
/** @var bool */
private $show_spouse;
/** @var int */
private $descent;
/** @var int */
private $bhalfheight;
/** @var int */
private $generations;
/** @var int */
private $dgenerations;
/**
* 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('Family book');
}
/**
* A sentence describing what this module does.
*
* @return string
*/
public function description(): string
{
/* I18N: Description of the “FamilyBookChart” module */
return I18N::translate('A chart of an individual’s ancestors and descendants, as a family book.');
}
/**
* CSS class for the URL.
*
* @return string
*/
public function chartMenuClass(): string
{
return 'menu-chart-familybook';
}
/**
* 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);
}
/**
* The title for a specific instance of this chart.
*
* @param Individual $individual
*
* @return string
*/
public function chartTitle(Individual $individual): string
{
/* I18N: %s is an individual’s name */
return I18N::translate('Family book of %s', $individual->fullName());
}
/**
* 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);
$show_spouse = (bool) $request->get('show_spouse');
$generations = (int) $request->get('generations', self::DEFAULT_GENERATIONS);
$generations = min($generations, self::MAXIMUM_GENERATIONS);
$generations = max($generations, self::MINIMUM_GENERATIONS);
// Generations of ancestors/descendants in each mini-tree.
$book_size = (int) $request->get('book_size', 2);
$book_size = min($book_size, 5);
$book_size = max($book_size, 2);
if ($ajax) {
return $this->chart($individual, $generations, $book_size, $show_spouse);
}
$ajax_url = $this->chartUrl($individual, [
'ajax' => true,
'book_size' => $book_size,
'generations' => $generations,
'show_spouse' => $show_spouse,
]);
return $this->viewResponse('modules/family-book-chart/page', [
'ajax_url' => $ajax_url,
'book_size' => $book_size,
'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),
]);
}
/**
* @param Individual $individual
* @param int $generations
* @param int $book_size
* @param bool $show_spouse
*
* @return Response
*/
public function chart(Individual $individual, int $generations, int $book_size, bool $show_spouse): Response
{
$this->box = (object) [
'width' => app(ModuleThemeInterface::class)->parameter('chart-box-x'),
'height' => app(ModuleThemeInterface::class)->parameter('chart-box-y'),
];
$this->show_spouse = $show_spouse;
$this->descent = $generations;
$this->generations = $book_size;
$this->bhalfheight = $this->box->height / 2;
$this->dgenerations = $this->maxDescendencyGenerations($individual, 0);
if ($this->dgenerations < 1) {
$this->dgenerations = 1;
}
// @TODO - this is just a wrapper around the old code.
ob_start();
$this->printFamilyBook($individual, $generations);
$html = ob_get_clean();
return new Response($html);
}
/**
* Prints descendency of passed in person
*
* @param int $generation
* @param Individual|null $person
*
* @return float
*/
private function printDescendency($generation, Individual $person = null): float
{
if ($generation > $this->dgenerations) {
return 0;
}
echo '
';
return $numkids;
}
/**
* Prints pedigree of the person passed in
*
* @param Individual $person
* @param int $count
*
* @return void
*/
private function printPersonPedigree($person, $count): void
{
if ($count >= $this->generations) {
return;
}
$genoffset = $this->generations; // handle pedigree n generations lines
//-- calculate how tall the lines should be
$lh = $this->bhalfheight * (2 ** ($genoffset - $count - 1));
//
//Prints empty table columns for children w/o parents up to the max generation
//This allows vertical line spacing to be consistent
if ($person->childFamilies()->isEmpty()) {
echo '';
$this->printEmptyBox();
//-- recursively get the father’s family
$this->printPersonPedigree($person, $count + 1);
echo '';
$this->printEmptyBox();
//-- recursively get the mother’s family
$this->printPersonPedigree($person, $count + 1);
echo ' | |
';
}
// Empty box section done, now for regular pedigree
foreach ($person->childFamilies() as $family) {
echo '';
// Determine line height for two or more spouces
// And then adjust the vertical line for the root person only
$famcount = 0;
if ($this->show_spouse) {
// count number of spouses
$famcount += $person->spouseFamilies()->count();
}
$savlh = $lh; // Save current line height
if ($count == 1 && $genoffset <= $famcount) {
$linefactor = 0;
// genoffset of 2 needs no adjustment
if ($genoffset > 2) {
$tblheight = $this->box->height + 8;
if ($genoffset == 3) {
if ($famcount == 3) {
$linefactor = $tblheight / 2;
} elseif ($famcount > 3) {
$linefactor = $tblheight;
}
}
if ($genoffset == 4) {
if ($famcount == 4) {
$linefactor = $tblheight;
} elseif ($famcount > 4) {
$linefactor = ($famcount - $genoffset) * ($tblheight * 1.5);
}
}
if ($genoffset == 5) {
if ($famcount == 5) {
$linefactor = 0;
} elseif ($famcount > 5) {
$linefactor = $tblheight * ($famcount - $genoffset);
}
}
}
$lh = (($famcount - 1) * $this->box->height - $linefactor);
if ($genoffset > 5) {
$lh = $savlh;
}
}
echo ' | ',
'',
' | ',
'';
$lh = $savlh; // restore original line height
//-- print the father box
echo FunctionsPrint::printPedigreePerson($family->husband());
echo ' | ';
if ($family->husband()) {
echo '';
//-- recursively get the father’s family
$this->printPersonPedigree($family->husband(), $count + 1);
echo ' | ';
} else {
echo '';
if ($genoffset > $count) {
echo '';
for ($i = 1; $i < ((2 ** ($genoffset - $count)) / 2); $i++) {
$this->printEmptyBox();
echo '';
}
echo ' ';
}
}
echo ' |
',
' | ',
' | ',
'';
//-- print the mother box
echo FunctionsPrint::printPedigreePerson($family->wife());
echo ' | ';
if ($family->wife()) {
echo '';
//-- recursively print the mother’s family
$this->printPersonPedigree($family->wife(), $count + 1);
echo ' | ';
} else {
echo '';
if ($count < $genoffset - 1) {
echo '';
for ($i = 1; $i < ((2 ** (($genoffset - 1) - $count)) / 2) + 1; $i++) {
$this->printEmptyBox();
echo '';
$this->printEmptyBox();
echo '';
}
echo ' ';
}
}
echo ' |
',
'
';
break;
}
}
/**
* Calculates number of generations a person has
*
* @param Individual $individual
* @param int $depth
*
* @return int
*/
private function maxDescendencyGenerations(Individual $individual, $depth): int
{
if ($depth > $this->generations) {
return $depth;
}
$maxdc = $depth;
foreach ($individual->spouseFamilies() as $family) {
foreach ($family->children() as $child) {
$dc = $this->maxDescendencyGenerations($child, $depth + 1);
if ($dc >= $this->generations) {
return $dc;
}
if ($dc > $maxdc) {
$maxdc = $dc;
}
}
}
$maxdc++;
if ($maxdc == 1) {
$maxdc++;
}
return $maxdc;
}
/**
* Print empty box
*
* @return void
*/
private function printEmptyBox(): void
{
echo app(ModuleThemeInterface::class)->individualBoxEmpty();
}
/**
* Print a “Family Book” for an individual
*
* @param Individual $person
* @param int $descent_steps
*
* @return void
*/
private function printFamilyBook(Individual $person, $descent_steps): void
{
if ($descent_steps == 0) {
return;
}
echo
'',
/* I18N: %s is an individual’s name */
I18N::translate('Family of %s', $person->fullName()),
'
',
'';
$this->dgenerations = $this->generations;
$this->printDescendency(1, $person);
echo ' | ';
$this->printPersonPedigree($person, 1);
echo ' |
';
foreach ($person->spouseFamilies() as $family) {
foreach ($family->children() as $child) {
$this->printFamilyBook($child, $descent_steps - 1);
}
}
}
}