xref: /webtrees/app/Http/RequestHandlers/UnconnectedPage.php (revision 449b311ecf65f677a2595e1e29f712d11ef22f34)
196716c47SGreg Roach<?php
296716c47SGreg Roach
396716c47SGreg Roach/**
496716c47SGreg Roach * webtrees: online genealogy
5d11be702SGreg Roach * Copyright (C) 2023 webtrees development team
696716c47SGreg Roach * This program is free software: you can redistribute it and/or modify
796716c47SGreg Roach * it under the terms of the GNU General Public License as published by
896716c47SGreg Roach * the Free Software Foundation, either version 3 of the License, or
996716c47SGreg Roach * (at your option) any later version.
1096716c47SGreg Roach * This program is distributed in the hope that it will be useful,
1196716c47SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of
1296716c47SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1396716c47SGreg Roach * GNU General Public License for more details.
1496716c47SGreg Roach * You should have received a copy of the GNU General Public License
1589f7189bSGreg Roach * along with this program. If not, see <https://www.gnu.org/licenses/>.
1696716c47SGreg Roach */
1796716c47SGreg Roach
1896716c47SGreg Roachdeclare(strict_types=1);
1996716c47SGreg Roach
2096716c47SGreg Roachnamespace Fisharebest\Webtrees\Http\RequestHandlers;
2196716c47SGreg Roach
2296716c47SGreg Roachuse Fisharebest\Algorithm\ConnectedComponent;
236f4ec3caSGreg Roachuse Fisharebest\Webtrees\DB;
2496716c47SGreg Roachuse Fisharebest\Webtrees\Http\ViewResponseTrait;
2596716c47SGreg Roachuse Fisharebest\Webtrees\I18N;
2696716c47SGreg Roachuse Fisharebest\Webtrees\Individual;
276b9cb339SGreg Roachuse Fisharebest\Webtrees\Registry;
28b55cbc6bSGreg Roachuse Fisharebest\Webtrees\Validator;
294fd89268SGreg Roachuse Illuminate\Support\Collection;
3096716c47SGreg Roachuse Psr\Http\Message\ResponseInterface;
3196716c47SGreg Roachuse Psr\Http\Message\ServerRequestInterface;
3296716c47SGreg Roachuse Psr\Http\Server\RequestHandlerInterface;
3396716c47SGreg Roach
3410e06497SGreg Roachuse function count;
3510e06497SGreg Roachuse function in_array;
3668c99bc7SGreg Roachuse function strtolower;
3796716c47SGreg Roach
3896716c47SGreg Roach/**
3996716c47SGreg Roach * Find groups of unrelated individuals.
4096716c47SGreg Roach */
4196716c47SGreg Roachclass UnconnectedPage implements RequestHandlerInterface
4296716c47SGreg Roach{
4396716c47SGreg Roach    use ViewResponseTrait;
4496716c47SGreg Roach
4596716c47SGreg Roach    /**
4696716c47SGreg Roach     * @param ServerRequestInterface $request
4796716c47SGreg Roach     *
4896716c47SGreg Roach     * @return ResponseInterface
4996716c47SGreg Roach     */
5096716c47SGreg Roach    public function handle(ServerRequestInterface $request): ResponseInterface
5196716c47SGreg Roach    {
52b55cbc6bSGreg Roach        $tree       = Validator::attributes($request)->tree();
53b55cbc6bSGreg Roach        $user       = Validator::attributes($request)->user();
54748dbe15SGreg Roach        $aliases    = Validator::queryParams($request)->boolean('aliases', false);
55748dbe15SGreg Roach        $associates = Validator::queryParams($request)->boolean('associates', false);
5696716c47SGreg Roach
57f925fcc4SGreg Roach        // Connect individuals using these links.
5896716c47SGreg Roach        $links = ['FAMS', 'FAMC'];
59f925fcc4SGreg Roach
60f925fcc4SGreg Roach        if ($aliases) {
61f925fcc4SGreg Roach            $links[] = 'ALIA';
62f925fcc4SGreg Roach        }
63f925fcc4SGreg Roach
64f925fcc4SGreg Roach        if ($associates) {
65f925fcc4SGreg Roach            $links[] = 'ASSO';
66f925fcc4SGreg Roach            $links[] = '_ASSO';
6796716c47SGreg Roach        }
6896716c47SGreg Roach
6996716c47SGreg Roach        $rows = DB::table('link')
7096716c47SGreg Roach            ->where('l_file', '=', $tree->id())
7196716c47SGreg Roach            ->whereIn('l_type', $links)
7296716c47SGreg Roach            ->select(['l_from', 'l_to'])
7396716c47SGreg Roach            ->get();
7496716c47SGreg Roach
7512e03a90SGreg Roach        $graph = DB::table('individuals')
7612e03a90SGreg Roach            ->where('i_file', '=', $tree->id())
7712e03a90SGreg Roach            ->pluck('i_id')
78*f25fc0f9SGreg Roach            ->mapWithKeys(static fn (string $xref): array => [$xref => []])
7912e03a90SGreg Roach            ->all();
8096716c47SGreg Roach
8196716c47SGreg Roach        foreach ($rows as $row) {
8296716c47SGreg Roach            $graph[$row->l_from][$row->l_to] = 1;
8396716c47SGreg Roach            $graph[$row->l_to][$row->l_from] = 1;
8496716c47SGreg Roach        }
8596716c47SGreg Roach
8696716c47SGreg Roach        $algorithm  = new ConnectedComponent($graph);
8796716c47SGreg Roach        $components = $algorithm->findConnectedComponents();
8896716c47SGreg Roach        $root       = $tree->significantIndividual($user);
8996716c47SGreg Roach        $xref       = $root->xref();
9096716c47SGreg Roach
9196716c47SGreg Roach        /** @var Individual[][] */
9296716c47SGreg Roach        $individual_groups = [];
9396716c47SGreg Roach
9496716c47SGreg Roach        foreach ($components as $component) {
9568c99bc7SGreg Roach            // Allow for upper/lower-case mismatches, and all-numeric XREFs
9668c99bc7SGreg Roach            $component = array_map(static fn ($x): string => strtolower((string) $x), $component);
9768c99bc7SGreg Roach
9868c99bc7SGreg Roach            if (!in_array(strtolower($xref), $component, true)) {
9970c53a06SGreg Roach                $individual_groups[] = DB::table('individuals')
10070c53a06SGreg Roach                    ->where('i_file', '=', $tree->id())
10170c53a06SGreg Roach                    ->whereIn('i_id', $component)
10270c53a06SGreg Roach                    ->get()
1036b9cb339SGreg Roach                    ->map(Registry::individualFactory()->mapper($tree))
10470c53a06SGreg Roach                    ->filter();
10596716c47SGreg Roach            }
10696716c47SGreg Roach        }
10796716c47SGreg Roach
1084fd89268SGreg Roach        usort($individual_groups, static fn (Collection $x, Collection $y): int => count($x) <=> count($y));
1094fd89268SGreg Roach
11096716c47SGreg Roach        $title = I18N::translate('Find unrelated individuals') . ' — ' . e($tree->title());
11196716c47SGreg Roach
11296716c47SGreg Roach        $this->layout = 'layouts/administration';
11396716c47SGreg Roach
11496716c47SGreg Roach        return $this->viewResponse('admin/trees-unconnected', [
115f925fcc4SGreg Roach            'aliases'           => $aliases,
11696716c47SGreg Roach            'associates'        => $associates,
11796716c47SGreg Roach            'root'              => $root,
11896716c47SGreg Roach            'individual_groups' => $individual_groups,
11996716c47SGreg Roach            'title'             => $title,
12096716c47SGreg Roach            'tree'              => $tree,
12196716c47SGreg Roach        ]);
12296716c47SGreg Roach    }
12396716c47SGreg Roach}
124