summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/Http/Controllers/RelationshipsChartController.php473
-rw-r--r--app/Module/AncestorsChartModule.php8
-rw-r--r--app/Module/ChartsBlockModule.php6
-rw-r--r--app/Module/CompactTreeChartModule.php8
-rw-r--r--app/Module/DescendancyChartModule.php8
-rw-r--r--app/Module/FamilyBookChartModule.php8
-rw-r--r--app/Module/FanChartModule.php8
-rw-r--r--app/Module/HourglassChartModule.php8
-rw-r--r--app/Module/InteractiveTree/TreeView.php2
-rw-r--r--app/Module/LifespansChartModule.php8
-rw-r--r--app/Module/PedigreeChartModule.php8
-rw-r--r--app/Module/RelationshipsChartModule.php455
-rw-r--r--app/Module/StatisticsChartModule.php2
-rw-r--r--app/Module/TimelineChartModule.php8
-rw-r--r--resources/views/modules/ancestors-chart/page.phtml (renamed from resources/views/modules/ancestors-chart/chart-page.phtml)0
-rw-r--r--resources/views/modules/compact-chart/page.phtml (renamed from resources/views/modules/compact-chart/chart-page.phtml)0
-rw-r--r--resources/views/modules/descendancy_chart/page.phtml (renamed from resources/views/modules/descendancy_chart/chart-page.phtml)0
-rw-r--r--resources/views/modules/family-book-chart/page.phtml (renamed from resources/views/modules/family-book-chart/chart-page.phtml)0
-rw-r--r--resources/views/modules/fanchart/page.phtml (renamed from resources/views/modules/fanchart/chart-page.phtml)0
-rw-r--r--resources/views/modules/hourglass-chart/page.phtml (renamed from resources/views/modules/hourglass-chart/chart-page.phtml)0
-rw-r--r--resources/views/modules/lifespans-chart/page.phtml (renamed from resources/views/modules/lifespans-chart/chart-page.phtml)0
-rw-r--r--resources/views/modules/pedigree-chart/page.phtml (renamed from resources/views/modules/pedigree-chart/chart-page.phtml)0
-rw-r--r--resources/views/modules/relationships-chart/config.phtml (renamed from resources/views/modules/relationships_chart/config.phtml)0
-rw-r--r--resources/views/modules/relationships-chart/page.phtml (renamed from resources/views/relationships-page.phtml)12
-rw-r--r--resources/views/modules/statistics-chart/page.phtml (renamed from resources/views/modules/statistics-chart/chart-page.phtml)0
-rw-r--r--resources/views/modules/timeline-chart/page.phtml (renamed from resources/views/modules/timeline-chart/chart-page.phtml)0
-rw-r--r--routes/web.php2
27 files changed, 493 insertions, 531 deletions
diff --git a/app/Http/Controllers/RelationshipsChartController.php b/app/Http/Controllers/RelationshipsChartController.php
deleted file mode 100644
index 0c3d45db06..0000000000
--- a/app/Http/Controllers/RelationshipsChartController.php
+++ /dev/null
@@ -1,473 +0,0 @@
-<?php
-/**
- * webtrees: online genealogy
- * Copyright (C) 2019 webtrees development team
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-declare(strict_types=1);
-
-namespace Fisharebest\Webtrees\Http\Controllers;
-
-use Fisharebest\Algorithm\Dijkstra;
-use Fisharebest\Webtrees\Auth;
-use Fisharebest\Webtrees\Family;
-use Fisharebest\Webtrees\FontAwesome;
-use Fisharebest\Webtrees\Functions\Functions;
-use Fisharebest\Webtrees\Functions\FunctionsPrint;
-use Fisharebest\Webtrees\I18N;
-use Fisharebest\Webtrees\Individual;
-use Fisharebest\Webtrees\Module\RelationshipsChartModule;
-use Fisharebest\Webtrees\Theme;
-use Fisharebest\Webtrees\Tree;
-use Illuminate\Database\Capsule\Manager as DB;
-use Illuminate\Database\Query\JoinClause;
-use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpFoundation\Response;
-
-/**
- * A chart showing the relationship(s) between two individuals.
- */
-class RelationshipsChartController extends AbstractBaseController
-{
- /**
- * A form to request the chart parameters.
- *
- * @param Request $request
- * @param Tree $tree
- *
- * @return Response
- */
- public function page(Request $request, Tree $tree): Response
- {
- $this->checkModuleIsActive($tree, RelationshipsChartModule::class);
-
- $xref1 = $request->get('xref1', '');
- $xref2 = $request->get('xref2', '');
-
- $individual1 = Individual::getInstance($xref1, $tree);
- $individual2 = Individual::getInstance($xref2, $tree);
-
- $recursion = (int) $request->get('recursion', '0');
- $ancestors = (int) $request->get('ancestors', '0');
-
- if ($individual1 instanceof Individual && $individual2 instanceof Individual) {
- Auth::checkIndividualAccess($individual1);
- Auth::checkIndividualAccess($individual2);
- }
-
- $ancestors_only = (int) $tree->getPreference('RELATIONSHIP_ANCESTORS', RelationshipsChartModule::DEFAULT_ANCESTORS);
- $max_recursion = (int) $tree->getPreference('RELATIONSHIP_RECURSION', RelationshipsChartModule::DEFAULT_RECURSION);
-
- $recursion = min($recursion, $max_recursion);
-
- if ($individual1 instanceof Individual && $individual2 instanceof Individual) {
- /* I18N: %s are individual’s names */
- $title = I18N::translate('Relationships between %1$s and %2$s', $individual1->getFullName(), $individual2->getFullName());
- } else {
- $title = I18N::translate('Relationships');
- }
-
- return $this->viewResponse('relationships-page', [
- 'ancestors' => $ancestors,
- 'ancestors_only' => $ancestors_only,
- 'ancestors_options' => $this->ancestorsOptions(),
- 'individual1' => $individual1,
- 'individual2' => $individual2,
- 'max_recursion' => $max_recursion,
- 'recursion' => $recursion,
- 'recursion_options' => $this->recursionOptions($max_recursion),
- 'title' => $title,
- ]);
- }
-
- /**
- * @param Request $request
- * @param Tree $tree
- *
- * @return Response
- */
- public function chart(Request $request, Tree $tree): Response
- {
- $this->checkModuleIsActive($tree, RelationshipsChartModule::class);
-
- $xref1 = $request->get('xref1', '');
- $individual1 = Individual::getInstance($xref1, $tree);
- $xref2 = $request->get('xref2', '');
- $individual2 = Individual::getInstance($xref2, $tree);
-
- Auth::checkIndividualAccess($individual1);
- Auth::checkIndividualAccess($individual2);
-
- $recursion = (int) $request->get('recursion', '0');
- $ancestors = (bool) $request->get('ancestors', '0');
-
- $max_recursion = (int) $tree->getPreference('RELATIONSHIP_RECURSION', RelationshipsChartModule::DEFAULT_RECURSION);
-
- $recursion = min($recursion, $max_recursion);
-
- $paths = $this->calculateRelationships($individual1, $individual2, $recursion, (bool) $ancestors);
-
- // @TODO - convert to views
- ob_start();
- if (I18N::direction() === 'ltr') {
- $diagonal1 = Theme::theme()->parameter('image-dline');
- $diagonal2 = Theme::theme()->parameter('image-dline2');
- } else {
- $diagonal1 = Theme::theme()->parameter('image-dline2');
- $diagonal2 = Theme::theme()->parameter('image-dline');
- }
-
- $num_paths = 0;
- foreach ($paths as $path) {
- // Extract the relationship names between pairs of individuals
- $relationships = $this->oldStyleRelationshipPath($tree, $path);
- if (empty($relationships)) {
- // Cannot see one of the families/individuals, due to privacy;
- continue;
- }
- echo '<h3>', I18N::translate('Relationship: %s', Functions::getRelationshipNameFromPath(implode('', $relationships), $individual1, $individual2)), '</h3>';
- $num_paths++;
-
- // Use a table/grid for layout.
- $table = [];
- // Current position in the grid.
- $x = 0;
- $y = 0;
- // Extent of the grid.
- $min_y = 0;
- $max_y = 0;
- $max_x = 0;
- // For each node in the path.
- foreach ($path as $n => $xref) {
- if ($n % 2 === 1) {
- switch ($relationships[$n]) {
- case 'hus':
- case 'wif':
- case 'spo':
- case 'bro':
- case 'sis':
- case 'sib':
- $table[$x + 1][$y] = '<div style="background:url(' . Theme::theme()->parameter('image-hline') . ') repeat-x center; width: 94px; text-align: center"><div class="hline-text" style="height: 32px;">' . Functions::getRelationshipNameFromPath($relationships[$n], Individual::getInstance($path[$n - 1], $tree), Individual::getInstance($path[$n + 1], $tree)) . '</div><div style="height: 32px;">' . FontAwesome::decorativeIcon('arrow-end') . '</div></div>';
- $x += 2;
- break;
- case 'son':
- case 'dau':
- case 'chi':
- if ($n > 2 && preg_match('/fat|mot|par/', $relationships[$n - 2])) {
- $table[$x + 1][$y - 1] = '<div style="background:url(' . $diagonal2 . '); width: 64px; height: 64px; text-align: center;"><div style="height: 32px; text-align: end;">' . Functions::getRelationshipNameFromPath($relationships[$n], Individual::getInstance($path[$n - 1], $tree), Individual::getInstance($path[$n + 1], $tree)) . '</div><div style="height: 32px; text-align: start;">' . FontAwesome::decorativeIcon('arrow-down') . '</div></div>';
- $x += 2;
- } else {
- $table[$x][$y - 1] = '<div style="background:url(' . Theme::theme()
- ->parameter('image-vline') . ') repeat-y center; height: 64px; text-align: center;"><div class="vline-text" style="display: inline-block; width:50%; line-height: 64px;">' . Functions::getRelationshipNameFromPath($relationships[$n], Individual::getInstance($path[$n - 1], $tree), Individual::getInstance($path[$n + 1], $tree)) . '</div><div style="display: inline-block; width:50%; line-height: 64px;">' . FontAwesome::decorativeIcon('arrow-down') . '</div></div>';
- }
- $y -= 2;
- break;
- case 'fat':
- case 'mot':
- case 'par':
- if ($n > 2 && preg_match('/son|dau|chi/', $relationships[$n - 2])) {
- $table[$x + 1][$y + 1] = '<div style="background:url(' . $diagonal1 . '); background-position: top right; width: 64px; height: 64px; text-align: center;"><div style="height: 32px; text-align: start;">' . Functions::getRelationshipNameFromPath($relationships[$n], Individual::getInstance($path[$n - 1], $tree), Individual::getInstance($path[$n + 1], $tree)) . '</div><div style="height: 32px; text-align: end;">' . FontAwesome::decorativeIcon('arrow-down') . '</div></div>';
- $x += 2;
- } else {
- $table[$x][$y + 1] = '<div style="background:url(' . Theme::theme()
- ->parameter('image-vline') . ') repeat-y center; height: 64px; text-align:center; "><div class="vline-text" style="display: inline-block; width: 50%; line-height: 32px;">' . Functions::getRelationshipNameFromPath($relationships[$n], Individual::getInstance($path[$n - 1], $tree), Individual::getInstance($path[$n + 1], $tree)) . '</div><div style="display: inline-block; width: 50%; line-height: 32px">' . FontAwesome::decorativeIcon('arrow-up') . '</div></div>';
- }
- $y += 2;
- break;
- }
- $max_x = max($max_x, $x);
- $min_y = min($min_y, $y);
- $max_y = max($max_y, $y);
- } else {
- $individual = Individual::getInstance($xref, $tree);
- $table[$x][$y] = FunctionsPrint::printPedigreePerson($individual);
- }
- }
- echo '<div class="wt-chart wt-relationship-chart">';
- echo '<table style="border-collapse: collapse; margin: 20px 50px;">';
- for ($y = $max_y; $y >= $min_y; --$y) {
- echo '<tr>';
- for ($x = 0; $x <= $max_x; ++$x) {
- echo '<td style="padding: 0;">';
- if (isset($table[$x][$y])) {
- echo $table[$x][$y];
- }
- echo '</td>';
- }
- echo '</tr>';
- }
- echo '</table>';
- echo '</div>';
- }
-
- if (!$num_paths) {
- echo '<p>', I18N::translate('No link between the two individuals could be found.'), '</p>';
- }
-
- $html = ob_get_clean();
-
- return new Response($html);
- }
-
- /**
- * Calculate the shortest paths - or all paths - between two individuals.
- *
- * @param Individual $individual1
- * @param Individual $individual2
- * @param int $recursion How many levels of recursion to use
- * @param bool $ancestor Restrict to relationships via a common ancestor
- *
- * @return string[][]
- */
- private function calculateRelationships(Individual $individual1, Individual $individual2, $recursion, $ancestor = false): array
- {
- $rows = DB::table('link')
- ->where('l_file', '=', $individual1->tree()->id())
- ->whereIn('l_type', ['FAMS', 'FAMC'])
- ->select(['l_from', 'l_to'])
- ->get();
-
- // Optionally restrict the graph to the ancestors of the individuals.
- if ($ancestor) {
- $ancestors = $this->allAncestors($individual1->xref(), $individual2->xref(), $individual1->tree()->id());
- $exclude = $this->excludeFamilies($individual1->xref(), $individual2->xref(), $individual1->tree()->id());
- } else {
- $ancestors = [];
- $exclude = [];
- }
-
- $graph = [];
-
- foreach ($rows as $row) {
- if (empty($ancestors) || in_array($row->l_from, $ancestors) && !in_array($row->l_to, $exclude)) {
- $graph[$row->l_from][$row->l_to] = 1;
- $graph[$row->l_to][$row->l_from] = 1;
- }
- }
-
- $xref1 = $individual1->xref();
- $xref2 = $individual2->xref();
- $dijkstra = new Dijkstra($graph);
- $paths = $dijkstra->shortestPaths($xref1, $xref2);
-
- // Only process each exclusion list once;
- $excluded = [];
-
- $queue = [];
- foreach ($paths as $path) {
- // Insert the paths into the queue, with an exclusion list.
- $queue[] = [
- 'path' => $path,
- 'exclude' => [],
- ];
- // While there are un-extended paths
- for ($next = current($queue); $next !== false; $next = next($queue)) {
- // For each family on the path
- for ($n = count($next['path']) - 2; $n >= 1; $n -= 2) {
- $exclude = $next['exclude'];
- if (count($exclude) >= $recursion) {
- continue;
- }
- $exclude[] = $next['path'][$n];
- sort($exclude);
- $tmp = implode('-', $exclude);
- if (in_array($tmp, $excluded)) {
- continue;
- }
-
- $excluded[] = $tmp;
- // Add any new path to the queue
- foreach ($dijkstra->shortestPaths($xref1, $xref2, $exclude) as $new_path) {
- $queue[] = [
- 'path' => $new_path,
- 'exclude' => $exclude,
- ];
- }
- }
- }
- }
- // Extract the paths from the queue, removing duplicates.
- $paths = [];
- foreach ($queue as $next) {
- $paths[implode('-', $next['path'])] = $next['path'];
- }
-
- return $paths;
- }
-
- /**
- * Convert a path (list of XREFs) to an "old-style" string of relationships.
- *
- * Return an empty array, if privacy rules prevent us viewing any node.
- *
- * @param Tree $tree
- * @param string[] $path Alternately Individual / Family
- *
- * @return string[]
- */
- private function oldStyleRelationshipPath(Tree $tree, array $path): array
- {
- $spouse_codes = [
- 'M' => 'hus',
- 'F' => 'wif',
- 'U' => 'spo',
- ];
- $parent_codes = [
- 'M' => 'fat',
- 'F' => 'mot',
- 'U' => 'par',
- ];
- $child_codes = [
- 'M' => 'son',
- 'F' => 'dau',
- 'U' => 'chi',
- ];
- $sibling_codes = [
- 'M' => 'bro',
- 'F' => 'sis',
- 'U' => 'sib',
- ];
- $relationships = [];
-
- for ($i = 1, $count = count($path); $i < $count; $i += 2) {
- $family = Family::getInstance($path[$i], $tree);
- $prev = Individual::getInstance($path[$i - 1], $tree);
- $next = Individual::getInstance($path[$i + 1], $tree);
- if (preg_match('/\n\d (HUSB|WIFE|CHIL) @' . $prev->xref() . '@/', $family->gedcom(), $match)) {
- $rel1 = $match[1];
- } else {
- return [];
- }
- if (preg_match('/\n\d (HUSB|WIFE|CHIL) @' . $next->xref() . '@/', $family->gedcom(), $match)) {
- $rel2 = $match[1];
- } else {
- return [];
- }
- if (($rel1 === 'HUSB' || $rel1 === 'WIFE') && ($rel2 === 'HUSB' || $rel2 === 'WIFE')) {
- $relationships[$i] = $spouse_codes[$next->getSex()];
- } elseif (($rel1 === 'HUSB' || $rel1 === 'WIFE') && $rel2 === 'CHIL') {
- $relationships[$i] = $child_codes[$next->getSex()];
- } elseif ($rel1 === 'CHIL' && ($rel2 === 'HUSB' || $rel2 === 'WIFE')) {
- $relationships[$i] = $parent_codes[$next->getSex()];
- } elseif ($rel1 === 'CHIL' && $rel2 === 'CHIL') {
- $relationships[$i] = $sibling_codes[$next->getSex()];
- }
- }
-
- return $relationships;
- }
-
- /**
- * Find all ancestors of a list of individuals
- *
- * @param string $xref1
- * @param string $xref2
- * @param int $tree_id
- *
- * @return string[]
- */
- private function allAncestors($xref1, $xref2, $tree_id): array
- {
- $ancestors = [
- $xref1,
- $xref2,
- ];
-
- $queue = [
- $xref1,
- $xref2,
- ];
- while (!empty($queue)) {
- $parents = DB::table('link AS l1')
- ->join('link AS l2', function (JoinClause $join): void {
- $join
- ->on('l1.l_to', '=', 'l2.l_to')
- ->on('l1.l_file', '=', 'l2.l_file');
- })
- ->where('l1.l_file', '=', $tree_id)
- ->where('l1.l_type', '=', 'FAMC')
- ->where('l2.l_type', '=', 'FAMS')
- ->whereIn('l1.l_from', $queue)
- ->pluck('l2.l_from');
-
- $queue = [];
- foreach ($parents as $parent) {
- if (!in_array($parent, $ancestors)) {
- $ancestors[] = $parent;
- $queue[] = $parent;
- }
- }
- }
-
- return $ancestors;
- }
-
- /**
- * Find all families of two individuals
- *
- * @param string $xref1
- * @param string $xref2
- * @param int $tree_id
- *
- * @return string[]
- */
- private function excludeFamilies($xref1, $xref2, $tree_id): array
- {
- return DB::table('link AS l1')
- ->join('link AS l2', function (JoinClause $join): void {
- $join
- ->on('l1.l_to', '=', 'l2.l_to')
- ->on('l1.l_type', '=', 'l2.l_type')
- ->on('l1.l_file', '=', 'l2.l_file');
- })
- ->where('l1.l_file', '=', $tree_id)
- ->where('l1.l_type', '=', 'FAMS')
- ->where('l1.l_from', '=', $xref1)
- ->where('l2.l_from', '=', $xref2)
- ->pluck('l1.l_to')
- ->all();
- }
-
- /**
- * Possible options for the ancestors option
- *
- * @return string[]
- */
- private function ancestorsOptions(): array
- {
- return [
- 0 => I18N::translate('Find any relationship'),
- 1 => I18N::translate('Find relationships via ancestors'),
- ];
- }
-
- /**
- * Possible options for the recursion option
- *
- * @param int $max_recursion
- *
- * @return array
- */
- private function recursionOptions(int $max_recursion): array
- {
- if ($max_recursion === RelationshipsChartModule::UNLIMITED_RECURSION) {
- $text = I18N::translate('Find all possible relationships');
- } else {
- $text = I18N::translate('Find other relationships');
- }
-
- return [
- '0' => I18N::translate('Find the closest relationships'),
- $max_recursion => $text,
- ];
- }
-}
diff --git a/app/Module/AncestorsChartModule.php b/app/Module/AncestorsChartModule.php
index 81d5f6249f..451027226e 100644
--- a/app/Module/AncestorsChartModule.php
+++ b/app/Module/AncestorsChartModule.php
@@ -119,7 +119,7 @@ class AncestorsChartModule extends AbstractModule implements ModuleInterface, Mo
*/
public function getChartAction(Request $request, Tree $tree, ChartService $chart_service): Response
{
- $ajax = $request->get('ajax');
+ $ajax = (bool) $request->get('ajax');
$xref = $request->get('xref', '');
$individual = Individual::getInstance($xref, $tree);
@@ -136,7 +136,7 @@ class AncestorsChartModule extends AbstractModule implements ModuleInterface, Mo
$generations = min($generations, $maximum_generations);
$generations = max($generations, $minimum_generations);
- if ($ajax === '1') {
+ if ($ajax) {
$ancestors = $chart_service->sosaStradonitzAncestors($individual, $generations);
switch ($chart_style) {
@@ -159,10 +159,10 @@ class AncestorsChartModule extends AbstractModule implements ModuleInterface, Mo
'generations' => $generations,
'chart_style' => $chart_style,
'show_cousins' => $show_cousins,
- 'ajax' => '1',
+ 'ajax' => true,
]);
- return $this->viewResponse('modules/ancestors-chart/chart-page', [
+ return $this->viewResponse('modules/ancestors-chart/page', [
'ajax_url' => $ajax_url,
'chart_style' => $chart_style,
'chart_styles' => $this->chartStyles(),
diff --git a/app/Module/ChartsBlockModule.php b/app/Module/ChartsBlockModule.php
index 11591b1b0f..d1a0d145d5 100644
--- a/app/Module/ChartsBlockModule.php
+++ b/app/Module/ChartsBlockModule.php
@@ -91,7 +91,7 @@ class ChartsBlockModule extends AbstractModule implements ModuleInterface, Modul
$module = Module::getModuleByClassName(PedigreeChartModule::class);
$title = $module->chartTitle($person);
$chart_url = $module->chartUrl($person, [
- 'ajax' => '1',
+ 'ajax' => true,
'generations' => 3,
'layout' => PedigreeChartModule::PORTRAIT,
]);
@@ -106,7 +106,7 @@ class ChartsBlockModule extends AbstractModule implements ModuleInterface, Modul
$module = Module::getModuleByClassName(DescendancyChartModule::class);
$title = $module->chartTitle($person);
$chart_url = $module->chartUrl($person, [
- 'ajax' => '1',
+ 'ajax' => true,
'generations' => 2,
'chart_style' => DescendancyChartModule::CHART_STYLE_LIST,
]);
@@ -121,7 +121,7 @@ class ChartsBlockModule extends AbstractModule implements ModuleInterface, Modul
$module = Module::getModuleByClassName(HourglassChartModule::class);
$title = $module->chartTitle($person);
$chart_url = $module->chartUrl($person, [
- 'ajax' => '1',
+ 'ajax' => true,
'generations' => 2,
]);
$content = view('modules/charts/chart', [
diff --git a/app/Module/CompactTreeChartModule.php b/app/Module/CompactTreeChartModule.php
index 9c0e416e8b..8bd7138d61 100644
--- a/app/Module/CompactTreeChartModule.php
+++ b/app/Module/CompactTreeChartModule.php
@@ -101,21 +101,21 @@ class CompactTreeChartModule extends AbstractModule implements ModuleInterface,
*/
public function getChartAction(Request $request, Tree $tree, ChartService $chart_service): Response
{
- $ajax = $request->get('ajax', '');
+ $ajax = (bool) $request->get('ajax');
$xref = $request->get('xref', '');
$individual = Individual::getInstance($xref, $tree);
Auth::checkIndividualAccess($individual);
- if ($ajax === '1') {
+ if ($ajax) {
return $this->chartCompact($individual, $chart_service);
}
$ajax_url = $this->chartUrl($individual, [
- 'ajax' => '1',
+ 'ajax' => true,
]);
- return $this->viewResponse('modules/compact-chart/chart-page', [
+ return $this->viewResponse('modules/compact-chart/page', [
'ajax_url' => $ajax_url,
'individual' => $individual,
'module_name' => $this->name(),
diff --git a/app/Module/DescendancyChartModule.php b/app/Module/DescendancyChartModule.php
index 9fdaf31986..e0dd6ff876 100644
--- a/app/Module/DescendancyChartModule.php
+++ b/app/Module/DescendancyChartModule.php
@@ -127,7 +127,7 @@ class DescendancyChartModule extends AbstractModule implements ModuleInterface,
*/
public function getChartAction(Request $request, Tree $tree, ChartService $chart_service): Response
{
- $ajax = $request->get('ajax', '');
+ $ajax = (bool) $request->get('ajax');
$xref = $request->get('xref', '');
$individual = Individual::getInstance($xref, $tree);
@@ -143,17 +143,17 @@ class DescendancyChartModule extends AbstractModule implements ModuleInterface,
$generations = min($generations, $maximum_generations);
$generations = max($generations, $minimum_generations);
- if ($ajax === '1') {
+ if ($ajax) {
return $this->chart($request, $tree, $chart_service);
}
$ajax_url = $this->chartUrl($individual, [
'chart_style' => $chart_style,
'generations' => $generations,
- 'ajax' => '1',
+ 'ajax' => true,
]);
- return $this->viewResponse('modules/descendancy_chart/chart-page', [
+ return $this->viewResponse('modules/descendancy_chart/page', [
'ajax_url' => $ajax_url,
'chart_style' => $chart_style,
'chart_styles' => $this->chartStyles(),
diff --git a/app/Module/FamilyBookChartModule.php b/app/Module/FamilyBookChartModule.php
index 3e2214f8c4..073adf323e 100644
--- a/app/Module/FamilyBookChartModule.php
+++ b/app/Module/FamilyBookChartModule.php
@@ -125,7 +125,7 @@ class FamilyBookChartModule extends AbstractModule implements ModuleInterface, M
*/
public function getChartAction(Request $request, Tree $tree): Response
{
- $ajax = $request->get('ajax', '');
+ $ajax = (bool) $request->get('ajax');
$xref = $request->get('xref', '');
$individual = Individual::getInstance($xref, $tree);
@@ -145,18 +145,18 @@ class FamilyBookChartModule extends AbstractModule implements ModuleInterface, M
$book_size = min($book_size, 5);
$book_size = max($book_size, 2);
- if ($ajax === '1') {
+ if ($ajax) {
return $this->chart($individual, $generations, $book_size, $show_spouse);
}
$ajax_url = $this->chartUrl($individual, [
- 'ajax' => '1',
+ 'ajax' => true,
'book_size' => $book_size,
'generations' => $generations,
'show_spouse' => $show_spouse
]);
- return $this->viewResponse('modules/family-book-chart/chart-page', [
+ return $this->viewResponse('modules/family-book-chart/page', [
'ajax_url' => $ajax_url,
'book_size' => $book_size,
'generations' => $generations,
diff --git a/app/Module/FanChartModule.php b/app/Module/FanChartModule.php
index 9cadefe8f7..a5fc062cee 100644
--- a/app/Module/FanChartModule.php
+++ b/app/Module/FanChartModule.php
@@ -118,7 +118,7 @@ class FanChartModule extends AbstractModule implements ModuleInterface, ModuleCh
*/
public function getChartAction(Request $request, Tree $tree, ChartService $chart_service): Response
{
- $ajax = $request->get('ajax', '');
+ $ajax = (bool) $request->get('ajax');
$xref = $request->get('xref', '');
$individual = Individual::getInstance($xref, $tree);
@@ -134,18 +134,18 @@ class FanChartModule extends AbstractModule implements ModuleInterface, ModuleCh
$generations = min($generations, self::MAXIMUM_GENERATIONS);
$generations = max($generations, self::MINIMUM_GENERATIONS);
- if ($ajax === '1') {
+ if ($ajax) {
return $this->chart($individual, $chart_style, $fan_width, $generations, $chart_service);
}
$ajax_url = $this->chartUrl($individual, [
- 'ajax' => '1',
+ 'ajax' => true,
'chart_style' => $chart_style,
'fan_width' => $fan_width,
'generations' => $generations,
]);
- return $this->viewResponse('modules/fanchart/chart-page', [
+ return $this->viewResponse('modules/fanchart/page', [
'ajax_url' => $ajax_url,
'chart_style' => $chart_style,
'chart_styles' => $this->chartStyles(),
diff --git a/app/Module/HourglassChartModule.php b/app/Module/HourglassChartModule.php
index e2cd92bc0b..7f2ccb4f76 100644
--- a/app/Module/HourglassChartModule.php
+++ b/app/Module/HourglassChartModule.php
@@ -96,7 +96,7 @@ class HourglassChartModule extends AbstractModule implements ModuleInterface, Mo
*/
public function getChartAction(Request $request, Tree $tree): Response
{
- $ajax = $request->get('ajax', '');
+ $ajax = (bool) $request->get('ajax');
$xref = $request->get('xref', '');
$individual = Individual::getInstance($xref, $tree);
@@ -112,15 +112,15 @@ class HourglassChartModule extends AbstractModule implements ModuleInterface, Mo
$show_spouse = (bool) $request->get('show_spouse');
- if ($ajax === '1') {
+ if ($ajax) {
return $this->chart($individual, $generations, $show_spouse);
}
$ajax_url = $this->chartUrl($individual, [
- 'ajax' => '1',
+ 'ajax' => true,
]);
- return $this->viewResponse('modules/hourglass-chart/chart-page', [
+ return $this->viewResponse('modules/hourglass-chart/page', [
'ajax_url' => $ajax_url,
'generations' => $generations,
'individual' => $individual,
diff --git a/app/Module/InteractiveTree/TreeView.php b/app/Module/InteractiveTree/TreeView.php
index b7edd5f09b..822fe8bd26 100644
--- a/app/Module/InteractiveTree/TreeView.php
+++ b/app/Module/InteractiveTree/TreeView.php
@@ -173,7 +173,7 @@ class TreeView
*
* @param Family[] $familyList array of families to draw the children for
* @param int $gen number of generations to draw
- * @param bool $ajax setted to true for an ajax call
+ * @param bool $ajax true for an ajax call
*
* @return string
*/
diff --git a/app/Module/LifespansChartModule.php b/app/Module/LifespansChartModule.php
index b9900067b9..114777e3c1 100644
--- a/app/Module/LifespansChartModule.php
+++ b/app/Module/LifespansChartModule.php
@@ -103,7 +103,7 @@ class LifespansChartModule extends AbstractModule implements ModuleInterface, Mo
*/
public function getChartAction(Request $request, Tree $tree): Response
{
- $ajax = $request->get('ajax');
+ $ajax = (bool) $request->get('ajax');
$xrefs = (array) $request->get('xrefs', []);
$addxref = $request->get('addxref', '');
$addfam = (bool) $request->get('addfam', false);
@@ -145,14 +145,14 @@ class LifespansChartModule extends AbstractModule implements ModuleInterface, Mo
return $individual !== null && $individual->canShow();
});
- if ($ajax === '1') {
+ if ($ajax) {
$subtitle = $this->subtitle(count($xrefs), $start_date, $end_date, $placename);
return $this->chart($tree, $xrefs, $subtitle);
}
$ajax_url = route('module', [
- 'ajax' => '1',
+ 'ajax' => true,
'module' => $this->name(),
'action' => 'Chart',
'ged' => $tree->name(),
@@ -165,7 +165,7 @@ class LifespansChartModule extends AbstractModule implements ModuleInterface, Mo
'ged' => $tree->name(),
]);
- return $this->viewResponse('modules/lifespans-chart/chart-page', [
+ return $this->viewResponse('modules/lifespans-chart/page', [
'ajax_url' => $ajax_url,
'module_name' => $this->name(),
'reset_url' => $reset_url,
diff --git a/app/Module/PedigreeChartModule.php b/app/Module/PedigreeChartModule.php
index 804ed0aaae..6a60b77516 100644
--- a/app/Module/PedigreeChartModule.php
+++ b/app/Module/PedigreeChartModule.php
@@ -141,7 +141,7 @@ class PedigreeChartModule extends AbstractModule implements ModuleInterface, Mod
*/
public function getChartAction(Request $request, Tree $tree, ChartService $chart_service): Response
{
- $ajax = $request->get('ajax', '');
+ $ajax = (bool) $request->get('ajax');
$xref = $request->get('xref', '');
$individual = Individual::getInstance($xref, $tree);
@@ -155,17 +155,17 @@ class PedigreeChartModule extends AbstractModule implements ModuleInterface, Mod
$generation_options = $this->generationOptions();
- if ($ajax === '1') {
+ if ($ajax) {
return $this->chart($individual, $generations, $orientation, $chart_service);
}
$ajax_url = $this->chartUrl($individual, [
- 'ajax' => '1',
+ 'ajax' => true,
'generations' => $generations,
'orientation' => $orientation,
]);
- return $this->viewResponse('modules/pedigree-chart/chart-page', [
+ return $this->viewResponse('modules/pedigree-chart/page', [
'ajax_url' => $ajax_url,
'generations' => $generations,
'generation_options' => $generation_options,
diff --git a/app/Module/RelationshipsChartModule.php b/app/Module/RelationshipsChartModule.php
index 656b6796d0..2aeb631b03 100644
--- a/app/Module/RelationshipsChartModule.php
+++ b/app/Module/RelationshipsChartModule.php
@@ -17,12 +17,20 @@ declare(strict_types=1);
namespace Fisharebest\Webtrees\Module;
+use Fisharebest\Algorithm\Dijkstra;
use Fisharebest\Webtrees\Auth;
+use Fisharebest\Webtrees\Family;
use Fisharebest\Webtrees\FlashMessages;
+use Fisharebest\Webtrees\FontAwesome;
+use Fisharebest\Webtrees\Functions\Functions;
+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 Illuminate\Database\Capsule\Manager as DB;
+use Illuminate\Database\Query\JoinClause;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
@@ -126,10 +134,12 @@ class RelationshipsChartModule extends AbstractModule implements ModuleInterface
*/
public function chartUrl(Individual $individual, array $parameters = []): string
{
- return route('relationships', [
- 'xref1' => $individual->xref(),
- 'ged' => $individual->tree()->name(),
- ] + $parameters);
+ return route('module', [
+ 'module' => $this->name(),
+ 'action' => 'Chart',
+ 'xref1' => $individual->xref(),
+ 'ged' => $individual->tree()->name(),
+ ] + $parameters);
}
/**
@@ -139,12 +149,12 @@ class RelationshipsChartModule extends AbstractModule implements ModuleInterface
{
$this->layout = 'layouts/administration';
- return $this->viewResponse('modules/relationships_chart/config', [
+ return $this->viewResponse('modules/relationships-chart/config', [
'all_trees' => Tree::getAll(),
'ancestors_options' => $this->ancestorsOptions(),
'default_ancestors' => self::DEFAULT_ANCESTORS,
'default_recursion' => self::DEFAULT_RECURSION,
- 'recursion_options' => $this->recursionOptions(),
+ 'recursion_options' => $this->recursionConfigOptions(),
'title' => I18N::translate('Chart preferences') . ' — ' . $this->title(),
]);
}
@@ -187,7 +197,7 @@ class RelationshipsChartModule extends AbstractModule implements ModuleInterface
*
* @return string[]
*/
- private function recursionOptions(): array
+ private function recursionConfigOptions(): array
{
return [
0 => I18N::translate('none'),
@@ -197,4 +207,435 @@ class RelationshipsChartModule extends AbstractModule implements ModuleInterface
self::UNLIMITED_RECURSION => I18N::translate('unlimited'),
];
}
+
+ /**
+ * A form to request the chart parameters.
+ *
+ * @param Request $request
+ * @param Tree $tree
+ *
+ * @return Response
+ */
+ public function getChartAction(Request $request, Tree $tree): Response
+ {
+ $ajax = (bool) $request->get('ajax');
+
+ $xref1 = $request->get('xref1', '');
+ $xref2 = $request->get('xref2', '');
+
+ $individual1 = Individual::getInstance($xref1, $tree);
+ $individual2 = Individual::getInstance($xref2, $tree);
+
+ $recursion = (int) $request->get('recursion', '0');
+ $ancestors = (int) $request->get('ancestors', '0');
+
+ if ($individual1 instanceof Individual && $individual2 instanceof Individual) {
+ Auth::checkIndividualAccess($individual1);
+ Auth::checkIndividualAccess($individual2);
+ }
+
+ $ancestors_only = (int) $tree->getPreference('RELATIONSHIP_ANCESTORS', RelationshipsChartModule::DEFAULT_ANCESTORS);
+ $max_recursion = (int) $tree->getPreference('RELATIONSHIP_RECURSION', RelationshipsChartModule::DEFAULT_RECURSION);
+
+ $recursion = min($recursion, $max_recursion);
+
+ if ($individual1 instanceof Individual && $individual2 instanceof Individual) {
+ /* I18N: %s are individual’s names */
+ $title = I18N::translate('Relationships between %1$s and %2$s', $individual1->getFullName(), $individual2->getFullName());
+ } else {
+ $title = I18N::translate('Relationships');
+ }
+
+ if ($ajax) {
+ return $this->chart($request, $tree);
+ }
+
+ $ajax_url = $this->chartUrl($individual1, [
+ 'ajax' => true,
+ 'xref2' => $individual2->xref(),
+ 'ged' => $individual2->tree()->name(),
+ 'recursion' => $recursion,
+ 'ancestors' => $ancestors,
+ ]);
+
+ return $this->viewResponse('modules/relationships-chart/page', [
+ 'ajax_url' => $ajax_url,
+ 'ancestors' => $ancestors,
+ 'ancestors_only' => $ancestors_only,
+ 'ancestors_options' => $this->ancestorsOptions(),
+ 'individual1' => $individual1,
+ 'individual2' => $individual2,
+ 'max_recursion' => $max_recursion,
+ 'module_name' => $this->name(),
+ 'recursion' => $recursion,
+ 'recursion_options' => $this->recursionOptions($max_recursion),
+ 'title' => $title,
+ ]);
+ }
+
+ /**
+ * @param Request $request
+ * @param Tree $tree
+ *
+ * @return Response
+ */
+ public function chart(Request $request, Tree $tree): Response
+ {
+ $xref1 = $request->get('xref1', '');
+ $individual1 = Individual::getInstance($xref1, $tree);
+ $xref2 = $request->get('xref2', '');
+ $individual2 = Individual::getInstance($xref2, $tree);
+
+ Auth::checkIndividualAccess($individual1);
+ Auth::checkIndividualAccess($individual2);
+
+ $recursion = (int) $request->get('recursion', '0');
+ $ancestors = (bool) $request->get('ancestors', '0');
+
+ $max_recursion = (int) $tree->getPreference('RELATIONSHIP_RECURSION', RelationshipsChartModule::DEFAULT_RECURSION);
+
+ $recursion = min($recursion, $max_recursion);
+
+ $paths = $this->calculateRelationships($individual1, $individual2, $recursion, (bool) $ancestors);
+
+ // @TODO - convert to views
+ ob_start();
+ if (I18N::direction() === 'ltr') {
+ $diagonal1 = Theme::theme()->parameter('image-dline');
+ $diagonal2 = Theme::theme()->parameter('image-dline2');
+ } else {
+ $diagonal1 = Theme::theme()->parameter('image-dline2');
+ $diagonal2 = Theme::theme()->parameter('image-dline');
+ }
+
+ $num_paths = 0;
+ foreach ($paths as $path) {
+ // Extract the relationship names between pairs of individuals
+ $relationships = $this->oldStyleRelationshipPath($tree, $path);
+ if (empty($relationships)) {
+ // Cannot see one of the families/individuals, due to privacy;
+ continue;
+ }
+ echo '<h3>', I18N::translate('Relationship: %s', Functions::getRelationshipNameFromPath(implode('', $relationships), $individual1, $individual2)), '</h3>';
+ $num_paths++;
+
+ // Use a table/grid for layout.
+ $table = [];
+ // Current position in the grid.
+ $x = 0;
+ $y = 0;
+ // Extent of the grid.
+ $min_y = 0;
+ $max_y = 0;
+ $max_x = 0;
+ // For each node in the path.
+ foreach ($path as $n => $xref) {
+ if ($n % 2 === 1) {
+ switch ($relationships[$n]) {
+ case 'hus':
+ case 'wif':
+ case 'spo':
+ case 'bro':
+ case 'sis':
+ case 'sib':
+ $table[$x + 1][$y] = '<div style="background:url(' . Theme::theme()->parameter('image-hline') . ') repeat-x center; width: 94px; text-align: center"><div class="hline-text" style="height: 32px;">' . Functions::getRelationshipNameFromPath($relationships[$n], Individual::getInstance($path[$n - 1], $tree), Individual::getInstance($path[$n + 1], $tree)) . '</div><div style="height: 32px;">' . FontAwesome::decorativeIcon('arrow-end') . '</div></div>';
+ $x += 2;
+ break;
+ case 'son':
+ case 'dau':
+ case 'chi':
+ if ($n > 2 && preg_match('/fat|mot|par/', $relationships[$n - 2])) {
+ $table[$x + 1][$y - 1] = '<div style="background:url(' . $diagonal2 . '); width: 64px; height: 64px; text-align: center;"><div style="height: 32px; text-align: end;">' . Functions::getRelationshipNameFromPath($relationships[$n], Individual::getInstance($path[$n - 1], $tree), Individual::getInstance($path[$n + 1], $tree)) . '</div><div style="height: 32px; text-align: start;">' . FontAwesome::decorativeIcon('arrow-down') . '</div></div>';
+ $x += 2;
+ } else {
+ $table[$x][$y - 1] = '<div style="background:url(' . Theme::theme()
+ ->parameter('image-vline') . ') repeat-y center; height: 64px; text-align: center;"><div class="vline-text" style="display: inline-block; width:50%; line-height: 64px;">' . Functions::getRelationshipNameFromPath($relationships[$n], Individual::getInstance($path[$n - 1], $tree), Individual::getInstance($path[$n + 1], $tree)) . '</div><div style="display: inline-block; width:50%; line-height: 64px;">' . FontAwesome::decorativeIcon('arrow-down') . '</div></div>';
+ }
+ $y -= 2;
+ break;
+ case 'fat':
+ case 'mot':
+ case 'par':
+ if ($n > 2 && preg_match('/son|dau|chi/', $relationships[$n - 2])) {
+ $table[$x + 1][$y + 1] = '<div style="background:url(' . $diagonal1 . '); background-position: top right; width: 64px; height: 64px; text-align: center;"><div style="height: 32px; text-align: start;">' . Functions::getRelationshipNameFromPath($relationships[$n], Individual::getInstance($path[$n - 1], $tree), Individual::getInstance($path[$n + 1], $tree)) . '</div><div style="height: 32px; text-align: end;">' . FontAwesome::decorativeIcon('arrow-down') . '</div></div>';
+ $x += 2;
+ } else {
+ $table[$x][$y + 1] = '<div style="background:url(' . Theme::theme()
+ ->parameter('image-vline') . ') repeat-y center; height: 64px; text-align:center; "><div class="vline-text" style="display: inline-block; width: 50%; line-height: 32px;">' . Functions::getRelationshipNameFromPath($relationships[$n], Individual::getInstance($path[$n - 1], $tree), Individual::getInstance($path[$n + 1], $tree)) . '</div><div style="display: inline-block; width: 50%; line-height: 32px">' . FontAwesome::decorativeIcon('arrow-up') . '</div></div>';
+ }
+ $y += 2;
+ break;
+ }
+ $max_x = max($max_x, $x);
+ $min_y = min($min_y, $y);
+ $max_y = max($max_y, $y);
+ } else {
+ $individual = Individual::getInstance($xref, $tree);
+ $table[$x][$y] = FunctionsPrint::printPedigreePerson($individual);
+ }
+ }
+ echo '<div class="wt-chart wt-relationship-chart">';
+ echo '<table style="border-collapse: collapse; margin: 20px 50px;">';
+ for ($y = $max_y; $y >= $min_y; --$y) {
+ echo '<tr>';
+ for ($x = 0; $x <= $max_x; ++$x) {
+ echo '<td style="padding: 0;">';
+ if (isset($table[$x][$y])) {
+ echo $table[$x][$y];
+ }
+ echo '</td>';
+ }
+ echo '</tr>';
+ }
+ echo '</table>';
+ echo '</div>';
+ }
+
+ if (!$num_paths) {
+ echo '<p>', I18N::translate('No link between the two individuals could be found.'), '</p>';
+ }
+
+ $html = ob_get_clean();
+
+ return new Response($html);
+ }
+
+ /**
+ * Calculate the shortest paths - or all paths - between two individuals.
+ *
+ * @param Individual $individual1
+ * @param Individual $individual2
+ * @param int $recursion How many levels of recursion to use
+ * @param bool $ancestor Restrict to relationships via a common ancestor
+ *
+ * @return string[][]
+ */
+ private function calculateRelationships(Individual $individual1, Individual $individual2, $recursion, $ancestor = false): array
+ {
+ $rows = DB::table('link')
+ ->where('l_file', '=', $individual1->tree()->id())
+ ->whereIn('l_type', ['FAMS', 'FAMC'])
+ ->select(['l_from', 'l_to'])
+ ->get();
+
+ // Optionally restrict the graph to the ancestors of the individuals.
+ if ($ancestor) {
+ $ancestors = $this->allAncestors($individual1->xref(), $individual2->xref(), $individual1->tree()->id());
+ $exclude = $this->excludeFamilies($individual1->xref(), $individual2->xref(), $individual1->tree()->id());
+ } else {
+ $ancestors = [];
+ $exclude = [];
+ }
+
+ $graph = [];
+
+ foreach ($rows as $row) {
+ if (empty($ancestors) || in_array($row->l_from, $ancestors) && !in_array($row->l_to, $exclude)) {
+ $graph[$row->l_from][$row->l_to] = 1;
+ $graph[$row->l_to][$row->l_from] = 1;
+ }
+ }
+
+ $xref1 = $individual1->xref();
+ $xref2 = $individual2->xref();
+ $dijkstra = new Dijkstra($graph);
+ $paths = $dijkstra->shortestPaths($xref1, $xref2);
+
+ // Only process each exclusion list once;
+ $excluded = [];
+
+ $queue = [];
+ foreach ($paths as $path) {
+ // Insert the paths into the queue, with an exclusion list.
+ $queue[] = [
+ 'path' => $path,
+ 'exclude' => [],
+ ];
+ // While there are un-extended paths
+ for ($next = current($queue); $next !== false; $next = next($queue)) {
+ // For each family on the path
+ for ($n = count($next['path']) - 2; $n >= 1; $n -= 2) {
+ $exclude = $next['exclude'];
+ if (count($exclude) >= $recursion) {
+ continue;
+ }
+ $exclude[] = $next['path'][$n];
+ sort($exclude);
+ $tmp = implode('-', $exclude);
+ if (in_array($tmp, $excluded)) {
+ continue;
+ }
+
+ $excluded[] = $tmp;
+ // Add any new path to the queue
+ foreach ($dijkstra->shortestPaths($xref1, $xref2, $exclude) as $new_path) {
+ $queue[] = [
+ 'path' => $new_path,
+ 'exclude' => $exclude,
+ ];
+ }
+ }
+ }
+ }
+ // Extract the paths from the queue, removing duplicates.
+ $paths = [];
+ foreach ($queue as $next) {
+ $paths[implode('-', $next['path'])] = $next['path'];
+ }
+
+ return $paths;
+ }
+
+ /**
+ * Convert a path (list of XREFs) to an "old-style" string of relationships.
+ * Return an empty array, if privacy rules prevent us viewing any node.
+ *
+ * @param Tree $tree
+ * @param string[] $path Alternately Individual / Family
+ *
+ * @return string[]
+ */
+ private function oldStyleRelationshipPath(Tree $tree, array $path): array
+ {
+ $spouse_codes = [
+ 'M' => 'hus',
+ 'F' => 'wif',
+ 'U' => 'spo',
+ ];
+ $parent_codes = [
+ 'M' => 'fat',
+ 'F' => 'mot',
+ 'U' => 'par',
+ ];
+ $child_codes = [
+ 'M' => 'son',
+ 'F' => 'dau',
+ 'U' => 'chi',
+ ];
+ $sibling_codes = [
+ 'M' => 'bro',
+ 'F' => 'sis',
+ 'U' => 'sib',
+ ];
+ $relationships = [];
+
+ for ($i = 1, $count = count($path); $i < $count; $i += 2) {
+ $family = Family::getInstance($path[$i], $tree);
+ $prev = Individual::getInstance($path[$i - 1], $tree);
+ $next = Individual::getInstance($path[$i + 1], $tree);
+ if (preg_match('/\n\d (HUSB|WIFE|CHIL) @' . $prev->xref() . '@/', $family->gedcom(), $match)) {
+ $rel1 = $match[1];
+ } else {
+ return [];
+ }
+ if (preg_match('/\n\d (HUSB|WIFE|CHIL) @' . $next->xref() . '@/', $family->gedcom(), $match)) {
+ $rel2 = $match[1];
+ } else {
+ return [];
+ }
+ if (($rel1 === 'HUSB' || $rel1 === 'WIFE') && ($rel2 === 'HUSB' || $rel2 === 'WIFE')) {
+ $relationships[$i] = $spouse_codes[$next->getSex()];
+ } elseif (($rel1 === 'HUSB' || $rel1 === 'WIFE') && $rel2 === 'CHIL') {
+ $relationships[$i] = $child_codes[$next->getSex()];
+ } elseif ($rel1 === 'CHIL' && ($rel2 === 'HUSB' || $rel2 === 'WIFE')) {
+ $relationships[$i] = $parent_codes[$next->getSex()];
+ } elseif ($rel1 === 'CHIL' && $rel2 === 'CHIL') {
+ $relationships[$i] = $sibling_codes[$next->getSex()];
+ }
+ }
+
+ return $relationships;
+ }
+
+ /**
+ * Find all ancestors of a list of individuals
+ *
+ * @param string $xref1
+ * @param string $xref2
+ * @param int $tree_id
+ *
+ * @return string[]
+ */
+ private function allAncestors($xref1, $xref2, $tree_id): array
+ {
+ $ancestors = [
+ $xref1,
+ $xref2,
+ ];
+
+ $queue = [
+ $xref1,
+ $xref2,
+ ];
+ while (!empty($queue)) {
+ $parents = DB::table('link AS l1')
+ ->join('link AS l2', function (JoinClause $join): void {
+ $join
+ ->on('l1.l_to', '=', 'l2.l_to')
+ ->on('l1.l_file', '=', 'l2.l_file');
+ })
+ ->where('l1.l_file', '=', $tree_id)
+ ->where('l1.l_type', '=', 'FAMC')
+ ->where('l2.l_type', '=', 'FAMS')
+ ->whereIn('l1.l_from', $queue)
+ ->pluck('l2.l_from');
+
+ $queue = [];
+ foreach ($parents as $parent) {
+ if (!in_array($parent, $ancestors)) {
+ $ancestors[] = $parent;
+ $queue[] = $parent;
+ }
+ }
+ }
+
+ return $ancestors;
+ }
+
+ /**
+ * Find all families of two individuals
+ *
+ * @param string $xref1
+ * @param string $xref2
+ * @param int $tree_id
+ *
+ * @return string[]
+ */
+ private function excludeFamilies($xref1, $xref2, $tree_id): array
+ {
+ return DB::table('link AS l1')
+ ->join('link AS l2', function (JoinClause $join): void {
+ $join
+ ->on('l1.l_to', '=', 'l2.l_to')
+ ->on('l1.l_type', '=', 'l2.l_type')
+ ->on('l1.l_file', '=', 'l2.l_file');
+ })
+ ->where('l1.l_file', '=', $tree_id)
+ ->where('l1.l_type', '=', 'FAMS')
+ ->where('l1.l_from', '=', $xref1)
+ ->where('l2.l_from', '=', $xref2)
+ ->pluck('l1.l_to')
+ ->all();
+ }
+
+ /**
+ * Possible options for the recursion option
+ *
+ * @param int $max_recursion
+ *
+ * @return array
+ */
+ private function recursionOptions(int $max_recursion): array
+ {
+ if ($max_recursion === RelationshipsChartModule::UNLIMITED_RECURSION) {
+ $text = I18N::translate('Find all possible relationships');
+ } else {
+ $text = I18N::translate('Find other relationships');
+ }
+
+ return [
+ '0' => I18N::translate('Find the closest relationships'),
+ $max_recursion => $text,
+ ];
+ }
}
diff --git a/app/Module/StatisticsChartModule.php b/app/Module/StatisticsChartModule.php
index 813761f17d..c3a1dbfca0 100644
--- a/app/Module/StatisticsChartModule.php
+++ b/app/Module/StatisticsChartModule.php
@@ -148,7 +148,7 @@ class StatisticsChartModule extends AbstractModule implements ModuleInterface, M
]),
];
- return $this->viewResponse('modules/statistics-chart/chart-page', [
+ return $this->viewResponse('modules/statistics-chart/page', [
'tabs' => $tabs,
'title' => $this->title(),
]);
diff --git a/app/Module/TimelineChartModule.php b/app/Module/TimelineChartModule.php
index 4f8174507b..84c9477156 100644
--- a/app/Module/TimelineChartModule.php
+++ b/app/Module/TimelineChartModule.php
@@ -112,7 +112,7 @@ class TimelineChartModule extends AbstractModule implements ModuleInterface, Mod
*/
public function getChartAction(Request $request, Tree $tree): Response
{
- $ajax = $request->get('ajax');
+ $ajax = (bool) $request->get('ajax');
$scale = (int) $request->get('scale', self::SCALE_DEFAULT);
$scale = min($scale, self::SCALE_MAX);
$scale = max($scale, self::SCALE_MIN);
@@ -153,12 +153,12 @@ class TimelineChartModule extends AbstractModule implements ModuleInterface, Mod
return Individual::getInstance($xref, $tree);
}, $xrefs);
- if ($ajax === '1') {
+ if ($ajax) {
return $this->chart($tree, $xrefs, $scale);
}
$ajax_url = route('module', [
- 'ajax' => '1',
+ 'ajax' => true,
'module' => $this->name(),
'action' => 'Chart',
'ged' => $tree->name(),
@@ -188,7 +188,7 @@ class TimelineChartModule extends AbstractModule implements ModuleInterface, Mod
'xrefs' => $xrefs,
]);
- return $this->viewResponse('modules/timeline-chart/chart-page', [
+ return $this->viewResponse('modules/timeline-chart/page', [
'ajax_url' => $ajax_url,
'individuals' => $individuals,
'module_name' => $this->name(),
diff --git a/resources/views/modules/ancestors-chart/chart-page.phtml b/resources/views/modules/ancestors-chart/page.phtml
index 5c008bae25..5c008bae25 100644
--- a/resources/views/modules/ancestors-chart/chart-page.phtml
+++ b/resources/views/modules/ancestors-chart/page.phtml
diff --git a/resources/views/modules/compact-chart/chart-page.phtml b/resources/views/modules/compact-chart/page.phtml
index aae3243eb8..aae3243eb8 100644
--- a/resources/views/modules/compact-chart/chart-page.phtml
+++ b/resources/views/modules/compact-chart/page.phtml
diff --git a/resources/views/modules/descendancy_chart/chart-page.phtml b/resources/views/modules/descendancy_chart/page.phtml
index 6866de25f1..6866de25f1 100644
--- a/resources/views/modules/descendancy_chart/chart-page.phtml
+++ b/resources/views/modules/descendancy_chart/page.phtml
diff --git a/resources/views/modules/family-book-chart/chart-page.phtml b/resources/views/modules/family-book-chart/page.phtml
index 59fbfe84d4..59fbfe84d4 100644
--- a/resources/views/modules/family-book-chart/chart-page.phtml
+++ b/resources/views/modules/family-book-chart/page.phtml
diff --git a/resources/views/modules/fanchart/chart-page.phtml b/resources/views/modules/fanchart/page.phtml
index 16688e1319..16688e1319 100644
--- a/resources/views/modules/fanchart/chart-page.phtml
+++ b/resources/views/modules/fanchart/page.phtml
diff --git a/resources/views/modules/hourglass-chart/chart-page.phtml b/resources/views/modules/hourglass-chart/page.phtml
index 041d43acbc..041d43acbc 100644
--- a/resources/views/modules/hourglass-chart/chart-page.phtml
+++ b/resources/views/modules/hourglass-chart/page.phtml
diff --git a/resources/views/modules/lifespans-chart/chart-page.phtml b/resources/views/modules/lifespans-chart/page.phtml
index 7b65947fcb..7b65947fcb 100644
--- a/resources/views/modules/lifespans-chart/chart-page.phtml
+++ b/resources/views/modules/lifespans-chart/page.phtml
diff --git a/resources/views/modules/pedigree-chart/chart-page.phtml b/resources/views/modules/pedigree-chart/page.phtml
index f71b3f1c68..f71b3f1c68 100644
--- a/resources/views/modules/pedigree-chart/chart-page.phtml
+++ b/resources/views/modules/pedigree-chart/page.phtml
diff --git a/resources/views/modules/relationships_chart/config.phtml b/resources/views/modules/relationships-chart/config.phtml
index d4212c3d3a..d4212c3d3a 100644
--- a/resources/views/modules/relationships_chart/config.phtml
+++ b/resources/views/modules/relationships-chart/config.phtml
diff --git a/resources/views/relationships-page.phtml b/resources/views/modules/relationships-chart/page.phtml
index f15fe01698..211d995321 100644
--- a/resources/views/relationships-page.phtml
+++ b/resources/views/modules/relationships-chart/page.phtml
@@ -8,7 +8,9 @@
</h2>
<form class="wt-page-options wt-page-options-relationships-chart d-print-none">
- <input type="hidden" name="route" value="relationships">
+ <input type="hidden" name="route" value="module">
+ <input type="hidden" name="module" value="<?= e($module_name) ?>">
+ <input type="hidden" name="action" value="Chart">
<input type="hidden" name="ged" value="<?= e($tree->name()) ?>">
<div class="row form-group">
@@ -79,13 +81,7 @@
</form>
<?php if ($individual1 !== null && $individual2 !== null) : ?>
- <div class="wt-ajax-load wt-page-content wt-chart wt-relationships-chart" data-ajax-url="<?= e(route('relationships-chart', [
- 'xref1' => $individual1->xref(),
- 'xref2' => $individual2->xref(),
- 'ged' => $individual2->tree()->name(),
- 'recursion' => $recursion,
- 'ancestors' => $ancestors,
- ])) ?>"></div>
+ <div class="wt-ajax-load wt-page-content wt-chart wt-relationships-chart" data-ajax-url="<?= e($ajax_url) ?>"></div>
<?php endif ?>
<?php View::push('javascript') ?>
diff --git a/resources/views/modules/statistics-chart/chart-page.phtml b/resources/views/modules/statistics-chart/page.phtml
index a78f389ab2..a78f389ab2 100644
--- a/resources/views/modules/statistics-chart/chart-page.phtml
+++ b/resources/views/modules/statistics-chart/page.phtml
diff --git a/resources/views/modules/timeline-chart/chart-page.phtml b/resources/views/modules/timeline-chart/page.phtml
index a8d5441ba3..a8d5441ba3 100644
--- a/resources/views/modules/timeline-chart/chart-page.phtml
+++ b/resources/views/modules/timeline-chart/page.phtml
diff --git a/routes/web.php b/routes/web.php
index fdc9213fe9..bc8b9c0a17 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -276,8 +276,6 @@ if ($tree instanceof Tree && $tree->getPreference('imported') === '1') {
'GET:source-list' => 'ListController@sourceList',
'GET:interactive' => 'InteractiveChartController@page',
'GET:interactive-chart' => 'InteractiveChartController@chart',
- 'GET:relationships' => 'RelationshipsChartController@page',
- 'GET:relationships-chart' => 'RelationshipsChartController@chart',
'POST:accept-changes' => 'PendingChangesController@acceptChanges',
'POST:reject-changes' => 'PendingChangesController@rejectChanges',
'POST:accept-all-changes' => 'PendingChangesController@acceptAllChanges',