xref: /webtrees/app/Statistics/Repository/IndividualRepository.php (revision baa78b21e5cd0820060dda79437a1c8fcb6fb6e0)
18add1155SRico Sonntag<?php
23976b470SGreg Roach
38add1155SRico Sonntag/**
48add1155SRico Sonntag * webtrees: online genealogy
5d11be702SGreg Roach * Copyright (C) 2023 webtrees development team
68add1155SRico Sonntag * This program is free software: you can redistribute it and/or modify
78add1155SRico Sonntag * it under the terms of the GNU General Public License as published by
88add1155SRico Sonntag * the Free Software Foundation, either version 3 of the License, or
98add1155SRico Sonntag * (at your option) any later version.
108add1155SRico Sonntag * This program is distributed in the hope that it will be useful,
118add1155SRico Sonntag * but WITHOUT ANY WARRANTY; without even the implied warranty of
128add1155SRico Sonntag * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
138add1155SRico Sonntag * GNU General Public License for more details.
148add1155SRico Sonntag * 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/>.
168add1155SRico Sonntag */
17fcfa147eSGreg Roach
188add1155SRico Sonntagdeclare(strict_types=1);
198add1155SRico Sonntag
208add1155SRico Sonntagnamespace Fisharebest\Webtrees\Statistics\Repository;
218add1155SRico Sonntag
228add1155SRico Sonntaguse Fisharebest\Webtrees\Auth;
238add1155SRico Sonntaguse Fisharebest\Webtrees\Gedcom;
24d1a467e4SGreg Roachuse Fisharebest\Webtrees\GedcomRecord;
258add1155SRico Sonntaguse Fisharebest\Webtrees\I18N;
268add1155SRico Sonntaguse Fisharebest\Webtrees\Individual;
2767992b6aSRichard Cisseeuse Fisharebest\Webtrees\Module\IndividualListModule;
2867992b6aSRichard Cisseeuse Fisharebest\Webtrees\Module\ModuleInterface;
2987cca37cSGreg Roachuse Fisharebest\Webtrees\Module\ModuleListInterface;
30f78da678SGreg Roachuse Fisharebest\Webtrees\Registry;
3167992b6aSRichard Cisseeuse Fisharebest\Webtrees\Services\ModuleService;
328add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartAge;
338add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartBirth;
348add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartCommonGiven;
358add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartCommonSurname;
368add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartDeath;
378add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartFamilyWithSources;
3888de55fdSRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartIndividualWithSources;
398add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartMortality;
408add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartSex;
418add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Repository\Interfaces\IndividualRepositoryInterface;
42f78da678SGreg Roachuse Fisharebest\Webtrees\Statistics\Service\CenturyService;
43f78da678SGreg Roachuse Fisharebest\Webtrees\Statistics\Service\ColorService;
448add1155SRico Sonntaguse Fisharebest\Webtrees\Tree;
458add1155SRico Sonntaguse Illuminate\Database\Capsule\Manager as DB;
463dc8167dSGreg Roachuse Illuminate\Database\Query\Builder;
47a69f5655SGreg Roachuse Illuminate\Database\Query\Expression;
488add1155SRico Sonntaguse Illuminate\Database\Query\JoinClause;
4976d39c55SGreg Roachuse stdClass;
5076d39c55SGreg Roach
514c78e066SGreg Roachuse function app;
5271378461SGreg Roachuse function array_key_exists;
531c6e5e0cSGreg Roachuse function array_keys;
544c78e066SGreg Roachuse function array_reverse;
551c6e5e0cSGreg Roachuse function array_shift;
5671378461SGreg Roachuse function array_slice;
574c78e066SGreg Roachuse function array_walk;
584c78e066SGreg Roachuse function arsort;
5910e06497SGreg Roachuse function assert;
604c78e066SGreg Roachuse function e;
614c78e066SGreg Roachuse function explode;
621c6e5e0cSGreg Roachuse function implode;
634c78e066SGreg Roachuse function preg_match;
644c78e066SGreg Roachuse function uksort;
654c78e066SGreg Roachuse function view;
6671378461SGreg Roach
678add1155SRico Sonntag/**
684c78e066SGreg Roach * A repository providing methods for individual related statistics.
698add1155SRico Sonntag */
708add1155SRico Sonntagclass IndividualRepository implements IndividualRepositoryInterface
718add1155SRico Sonntag{
72f78da678SGreg Roach    private CenturyService $century_service;
73f78da678SGreg Roach
74f78da678SGreg Roach    private ColorService $color_service;
75f78da678SGreg Roach
764c78e066SGreg Roach    private Tree $tree;
778add1155SRico Sonntag
788add1155SRico Sonntag    /**
79f78da678SGreg Roach     * @param CenturyService $century_service
80f78da678SGreg Roach     * @param ColorService $color_service
818add1155SRico Sonntag     * @param Tree         $tree
828add1155SRico Sonntag     */
83f78da678SGreg Roach    public function __construct(CenturyService $century_service, ColorService $color_service, Tree $tree)
848add1155SRico Sonntag    {
85f78da678SGreg Roach        $this->century_service = $century_service;
86f78da678SGreg Roach        $this->color_service   = $color_service;
878add1155SRico Sonntag        $this->tree            = $tree;
888add1155SRico Sonntag    }
898add1155SRico Sonntag
908add1155SRico Sonntag    /**
918add1155SRico Sonntag     * Find common given names.
928add1155SRico Sonntag     *
938add1155SRico Sonntag     * @param string $sex
948add1155SRico Sonntag     * @param string $type
958add1155SRico Sonntag     * @param bool   $show_tot
968add1155SRico Sonntag     * @param int    $threshold
978add1155SRico Sonntag     * @param int    $maxtoshow
988add1155SRico Sonntag     *
9909482a55SGreg Roach     * @return string|array<int>
1008add1155SRico Sonntag     */
1018add1155SRico Sonntag    private function commonGivenQuery(string $sex, string $type, bool $show_tot, int $threshold, int $maxtoshow)
1028add1155SRico Sonntag    {
103d1a467e4SGreg Roach        $query = DB::table('name')
1040b5fd0a6SGreg Roach            ->join('individuals', static function (JoinClause $join): void {
105d1a467e4SGreg Roach                $join
106d1a467e4SGreg Roach                    ->on('i_file', '=', 'n_file')
107d1a467e4SGreg Roach                    ->on('i_id', '=', 'n_id');
108d1a467e4SGreg Roach            })
109d1a467e4SGreg Roach            ->where('n_file', '=', $this->tree->id())
110d1a467e4SGreg Roach            ->where('n_type', '<>', '_MARNM')
1118fb4e87cSGreg Roach            ->where('n_givn', '<>', Individual::PRAENOMEN_NESCIO)
112a69f5655SGreg Roach            ->where(new Expression('LENGTH(n_givn)'), '>', 1);
113d1a467e4SGreg Roach
1148add1155SRico Sonntag        switch ($sex) {
1158add1155SRico Sonntag            case 'M':
1168add1155SRico Sonntag            case 'F':
1178add1155SRico Sonntag            case 'U':
118d1a467e4SGreg Roach                $query->where('i_sex', '=', $sex);
1198add1155SRico Sonntag                break;
120d1a467e4SGreg Roach
1218add1155SRico Sonntag            case 'B':
1228add1155SRico Sonntag            default:
123d1a467e4SGreg Roach                $query->where('i_sex', '<>', 'U');
1248add1155SRico Sonntag                break;
1258add1155SRico Sonntag        }
1268add1155SRico Sonntag
127d1a467e4SGreg Roach        $rows = $query
1283413ec75SRico Sonntag            ->groupBy(['n_givn'])
129a69f5655SGreg Roach            ->select(['n_givn', new Expression('COUNT(distinct n_id) AS count')])
130d1a467e4SGreg Roach            ->pluck('count', 'n_givn');
1318add1155SRico Sonntag
1328add1155SRico Sonntag        $nameList = [];
1338add1155SRico Sonntag
134d1a467e4SGreg Roach        foreach ($rows as $n_givn => $count) {
1358add1155SRico Sonntag            // Split “John Thomas” into “John” and “Thomas” and count against both totals
13621f5b67aSGreg Roach            foreach (explode(' ', (string) $n_givn) as $given) {
1378add1155SRico Sonntag                // Exclude initials and particles.
138ef475b14SGreg Roach                if (preg_match('/^([A-Z]|[a-z]{1,3})$/', $given) !== 1) {
1396ccdf4f0SGreg Roach                    if (array_key_exists($given, $nameList)) {
140d1a467e4SGreg Roach                        $nameList[$given] += (int) $count;
1418add1155SRico Sonntag                    } else {
142d1a467e4SGreg Roach                        $nameList[$given] = (int) $count;
1438add1155SRico Sonntag                    }
1448add1155SRico Sonntag                }
1458add1155SRico Sonntag            }
1468add1155SRico Sonntag        }
1478add1155SRico Sonntag        arsort($nameList);
1486ccdf4f0SGreg Roach        $nameList = array_slice($nameList, 0, $maxtoshow);
1498add1155SRico Sonntag
1508add1155SRico Sonntag        foreach ($nameList as $given => $total) {
1518add1155SRico Sonntag            if ($total < $threshold) {
1528add1155SRico Sonntag                unset($nameList[$given]);
1538add1155SRico Sonntag            }
1548add1155SRico Sonntag        }
1558add1155SRico Sonntag
1568add1155SRico Sonntag        switch ($type) {
1578add1155SRico Sonntag            case 'chart':
1588add1155SRico Sonntag                return $nameList;
1598add1155SRico Sonntag
1608add1155SRico Sonntag            case 'table':
1618add1155SRico Sonntag                return view('lists/given-names-table', [
1628add1155SRico Sonntag                    'given_names' => $nameList,
163c9128110SGreg Roach                    'order'       => [[1, 'desc']],
1648add1155SRico Sonntag                ]);
1658add1155SRico Sonntag
1668add1155SRico Sonntag            case 'list':
1678add1155SRico Sonntag                return view('lists/given-names-list', [
1688add1155SRico Sonntag                    'given_names' => $nameList,
1698add1155SRico Sonntag                    'show_totals' => $show_tot,
1708add1155SRico Sonntag                ]);
1718add1155SRico Sonntag
1728add1155SRico Sonntag            case 'nolist':
1738add1155SRico Sonntag            default:
1740b5fd0a6SGreg Roach                array_walk($nameList, static function (string &$value, string $key) use ($show_tot): void {
1758add1155SRico Sonntag                    if ($show_tot) {
176315eb316SGreg Roach                        $value = '<bdi>' . e($key) . '</bdi> (' . I18N::number((int) $value) . ')';
1772da2e0a6SGreg Roach                    } else {
178315eb316SGreg Roach                        $value = '<bdi>' . e($key) . '</bdi>';
1798add1155SRico Sonntag                    }
1808add1155SRico Sonntag                });
1818add1155SRico Sonntag
1828add1155SRico Sonntag                return implode(I18N::$list_separator, $nameList);
1838add1155SRico Sonntag        }
1848add1155SRico Sonntag    }
1858add1155SRico Sonntag
1868add1155SRico Sonntag    /**
1878add1155SRico Sonntag     * Find common give names.
1888add1155SRico Sonntag     *
1898add1155SRico Sonntag     * @param int $threshold
1908add1155SRico Sonntag     * @param int $maxtoshow
1918add1155SRico Sonntag     *
1928add1155SRico Sonntag     * @return string
1938add1155SRico Sonntag     */
1948add1155SRico Sonntag    public function commonGiven(int $threshold = 1, int $maxtoshow = 10): string
1958add1155SRico Sonntag    {
1968add1155SRico Sonntag        return $this->commonGivenQuery('B', 'nolist', false, $threshold, $maxtoshow);
1978add1155SRico Sonntag    }
1988add1155SRico Sonntag
1998add1155SRico Sonntag    /**
2008add1155SRico Sonntag     * Find common give names.
2018add1155SRico Sonntag     *
2028add1155SRico Sonntag     * @param int $threshold
2038add1155SRico Sonntag     * @param int $maxtoshow
2048add1155SRico Sonntag     *
2058add1155SRico Sonntag     * @return string
2068add1155SRico Sonntag     */
2078add1155SRico Sonntag    public function commonGivenTotals(int $threshold = 1, int $maxtoshow = 10): string
2088add1155SRico Sonntag    {
2098add1155SRico Sonntag        return $this->commonGivenQuery('B', 'nolist', true, $threshold, $maxtoshow);
2108add1155SRico Sonntag    }
2118add1155SRico Sonntag
2128add1155SRico Sonntag    /**
2138add1155SRico Sonntag     * Find common give names.
2148add1155SRico Sonntag     *
2158add1155SRico Sonntag     * @param int $threshold
2168add1155SRico Sonntag     * @param int $maxtoshow
2178add1155SRico Sonntag     *
2188add1155SRico Sonntag     * @return string
2198add1155SRico Sonntag     */
2208add1155SRico Sonntag    public function commonGivenList(int $threshold = 1, int $maxtoshow = 10): string
2218add1155SRico Sonntag    {
2228add1155SRico Sonntag        return $this->commonGivenQuery('B', 'list', false, $threshold, $maxtoshow);
2238add1155SRico Sonntag    }
2248add1155SRico Sonntag
2258add1155SRico Sonntag    /**
2268add1155SRico Sonntag     * Find common give names.
2278add1155SRico Sonntag     *
2288add1155SRico Sonntag     * @param int $threshold
2298add1155SRico Sonntag     * @param int $maxtoshow
2308add1155SRico Sonntag     *
2318add1155SRico Sonntag     * @return string
2328add1155SRico Sonntag     */
2338add1155SRico Sonntag    public function commonGivenListTotals(int $threshold = 1, int $maxtoshow = 10): string
2348add1155SRico Sonntag    {
2358add1155SRico Sonntag        return $this->commonGivenQuery('B', 'list', true, $threshold, $maxtoshow);
2368add1155SRico Sonntag    }
2378add1155SRico Sonntag
2388add1155SRico Sonntag    /**
2398add1155SRico Sonntag     * Find common give names.
2408add1155SRico Sonntag     *
2418add1155SRico Sonntag     * @param int $threshold
2428add1155SRico Sonntag     * @param int $maxtoshow
2438add1155SRico Sonntag     *
2448add1155SRico Sonntag     * @return string
2458add1155SRico Sonntag     */
2468add1155SRico Sonntag    public function commonGivenTable(int $threshold = 1, int $maxtoshow = 10): string
2478add1155SRico Sonntag    {
2488add1155SRico Sonntag        return $this->commonGivenQuery('B', 'table', false, $threshold, $maxtoshow);
2498add1155SRico Sonntag    }
2508add1155SRico Sonntag
2518add1155SRico Sonntag    /**
2528add1155SRico Sonntag     * Find common give names of females.
2538add1155SRico Sonntag     *
2548add1155SRico Sonntag     * @param int $threshold
2558add1155SRico Sonntag     * @param int $maxtoshow
2568add1155SRico Sonntag     *
2578add1155SRico Sonntag     * @return string
2588add1155SRico Sonntag     */
2598add1155SRico Sonntag    public function commonGivenFemale(int $threshold = 1, int $maxtoshow = 10): string
2608add1155SRico Sonntag    {
2618add1155SRico Sonntag        return $this->commonGivenQuery('F', 'nolist', false, $threshold, $maxtoshow);
2628add1155SRico Sonntag    }
2638add1155SRico Sonntag
2648add1155SRico Sonntag    /**
2658add1155SRico Sonntag     * Find common give names of females.
2668add1155SRico Sonntag     *
2678add1155SRico Sonntag     * @param int $threshold
2688add1155SRico Sonntag     * @param int $maxtoshow
2698add1155SRico Sonntag     *
2708add1155SRico Sonntag     * @return string
2718add1155SRico Sonntag     */
2728add1155SRico Sonntag    public function commonGivenFemaleTotals(int $threshold = 1, int $maxtoshow = 10): string
2738add1155SRico Sonntag    {
2748add1155SRico Sonntag        return $this->commonGivenQuery('F', 'nolist', true, $threshold, $maxtoshow);
2758add1155SRico Sonntag    }
2768add1155SRico Sonntag
2778add1155SRico Sonntag    /**
2788add1155SRico Sonntag     * Find common give names of females.
2798add1155SRico Sonntag     *
2808add1155SRico Sonntag     * @param int $threshold
2818add1155SRico Sonntag     * @param int $maxtoshow
2828add1155SRico Sonntag     *
2838add1155SRico Sonntag     * @return string
2848add1155SRico Sonntag     */
2858add1155SRico Sonntag    public function commonGivenFemaleList(int $threshold = 1, int $maxtoshow = 10): string
2868add1155SRico Sonntag    {
2878add1155SRico Sonntag        return $this->commonGivenQuery('F', 'list', false, $threshold, $maxtoshow);
2888add1155SRico Sonntag    }
2898add1155SRico Sonntag
2908add1155SRico Sonntag    /**
2918add1155SRico Sonntag     * Find common give names of females.
2928add1155SRico Sonntag     *
2938add1155SRico Sonntag     * @param int $threshold
2948add1155SRico Sonntag     * @param int $maxtoshow
2958add1155SRico Sonntag     *
2968add1155SRico Sonntag     * @return string
2978add1155SRico Sonntag     */
2988add1155SRico Sonntag    public function commonGivenFemaleListTotals(int $threshold = 1, int $maxtoshow = 10): string
2998add1155SRico Sonntag    {
3008add1155SRico Sonntag        return $this->commonGivenQuery('F', 'list', true, $threshold, $maxtoshow);
3018add1155SRico Sonntag    }
3028add1155SRico Sonntag
3038add1155SRico Sonntag    /**
3048add1155SRico Sonntag     * Find common give names of females.
3058add1155SRico Sonntag     *
3068add1155SRico Sonntag     * @param int $threshold
3078add1155SRico Sonntag     * @param int $maxtoshow
3088add1155SRico Sonntag     *
3098add1155SRico Sonntag     * @return string
3108add1155SRico Sonntag     */
3118add1155SRico Sonntag    public function commonGivenFemaleTable(int $threshold = 1, int $maxtoshow = 10): string
3128add1155SRico Sonntag    {
3138add1155SRico Sonntag        return $this->commonGivenQuery('F', 'table', false, $threshold, $maxtoshow);
3148add1155SRico Sonntag    }
3158add1155SRico Sonntag
3168add1155SRico Sonntag    /**
3178add1155SRico Sonntag     * Find common give names of males.
3188add1155SRico Sonntag     *
3198add1155SRico Sonntag     * @param int $threshold
3208add1155SRico Sonntag     * @param int $maxtoshow
3218add1155SRico Sonntag     *
3228add1155SRico Sonntag     * @return string
3238add1155SRico Sonntag     */
3248add1155SRico Sonntag    public function commonGivenMale(int $threshold = 1, int $maxtoshow = 10): string
3258add1155SRico Sonntag    {
3268add1155SRico Sonntag        return $this->commonGivenQuery('M', 'nolist', false, $threshold, $maxtoshow);
3278add1155SRico Sonntag    }
3288add1155SRico Sonntag
3298add1155SRico Sonntag    /**
3308add1155SRico Sonntag     * Find common give names of males.
3318add1155SRico Sonntag     *
3328add1155SRico Sonntag     * @param int $threshold
3338add1155SRico Sonntag     * @param int $maxtoshow
3348add1155SRico Sonntag     *
3358add1155SRico Sonntag     * @return string
3368add1155SRico Sonntag     */
3378add1155SRico Sonntag    public function commonGivenMaleTotals(int $threshold = 1, int $maxtoshow = 10): string
3388add1155SRico Sonntag    {
3398add1155SRico Sonntag        return $this->commonGivenQuery('M', 'nolist', true, $threshold, $maxtoshow);
3408add1155SRico Sonntag    }
3418add1155SRico Sonntag
3428add1155SRico Sonntag    /**
3438add1155SRico Sonntag     * Find common give names of males.
3448add1155SRico Sonntag     *
3458add1155SRico Sonntag     * @param int $threshold
3468add1155SRico Sonntag     * @param int $maxtoshow
3478add1155SRico Sonntag     *
3488add1155SRico Sonntag     * @return string
3498add1155SRico Sonntag     */
3508add1155SRico Sonntag    public function commonGivenMaleList(int $threshold = 1, int $maxtoshow = 10): string
3518add1155SRico Sonntag    {
3528add1155SRico Sonntag        return $this->commonGivenQuery('M', 'list', false, $threshold, $maxtoshow);
3538add1155SRico Sonntag    }
3548add1155SRico Sonntag
3558add1155SRico Sonntag    /**
3568add1155SRico Sonntag     * Find common give names of males.
3578add1155SRico Sonntag     *
3588add1155SRico Sonntag     * @param int $threshold
3598add1155SRico Sonntag     * @param int $maxtoshow
3608add1155SRico Sonntag     *
3618add1155SRico Sonntag     * @return string
3628add1155SRico Sonntag     */
3638add1155SRico Sonntag    public function commonGivenMaleListTotals(int $threshold = 1, int $maxtoshow = 10): string
3648add1155SRico Sonntag    {
3658add1155SRico Sonntag        return $this->commonGivenQuery('M', 'list', true, $threshold, $maxtoshow);
3668add1155SRico Sonntag    }
3678add1155SRico Sonntag
3688add1155SRico Sonntag    /**
3698add1155SRico Sonntag     * Find common give names of males.
3708add1155SRico Sonntag     *
3718add1155SRico Sonntag     * @param int $threshold
3728add1155SRico Sonntag     * @param int $maxtoshow
3738add1155SRico Sonntag     *
3748add1155SRico Sonntag     * @return string
3758add1155SRico Sonntag     */
3768add1155SRico Sonntag    public function commonGivenMaleTable(int $threshold = 1, int $maxtoshow = 10): string
3778add1155SRico Sonntag    {
3788add1155SRico Sonntag        return $this->commonGivenQuery('M', 'table', false, $threshold, $maxtoshow);
3798add1155SRico Sonntag    }
3808add1155SRico Sonntag
3818add1155SRico Sonntag    /**
3828add1155SRico Sonntag     * Find common give names of unknown sexes.
3838add1155SRico Sonntag     *
3848add1155SRico Sonntag     * @param int $threshold
3858add1155SRico Sonntag     * @param int $maxtoshow
3868add1155SRico Sonntag     *
3878add1155SRico Sonntag     * @return string
3888add1155SRico Sonntag     */
3898add1155SRico Sonntag    public function commonGivenUnknown(int $threshold = 1, int $maxtoshow = 10): string
3908add1155SRico Sonntag    {
3918add1155SRico Sonntag        return $this->commonGivenQuery('U', 'nolist', false, $threshold, $maxtoshow);
3928add1155SRico Sonntag    }
3938add1155SRico Sonntag
3948add1155SRico Sonntag    /**
3958add1155SRico Sonntag     * Find common give names of unknown sexes.
3968add1155SRico Sonntag     *
3978add1155SRico Sonntag     * @param int $threshold
3988add1155SRico Sonntag     * @param int $maxtoshow
3998add1155SRico Sonntag     *
4008add1155SRico Sonntag     * @return string
4018add1155SRico Sonntag     */
4028add1155SRico Sonntag    public function commonGivenUnknownTotals(int $threshold = 1, int $maxtoshow = 10): string
4038add1155SRico Sonntag    {
4048add1155SRico Sonntag        return $this->commonGivenQuery('U', 'nolist', true, $threshold, $maxtoshow);
4058add1155SRico Sonntag    }
4068add1155SRico Sonntag
4078add1155SRico Sonntag    /**
4088add1155SRico Sonntag     * Find common give names of unknown sexes.
4098add1155SRico Sonntag     *
4108add1155SRico Sonntag     * @param int $threshold
4118add1155SRico Sonntag     * @param int $maxtoshow
4128add1155SRico Sonntag     *
4138add1155SRico Sonntag     * @return string
4148add1155SRico Sonntag     */
4158add1155SRico Sonntag    public function commonGivenUnknownList(int $threshold = 1, int $maxtoshow = 10): string
4168add1155SRico Sonntag    {
4178add1155SRico Sonntag        return $this->commonGivenQuery('U', 'list', false, $threshold, $maxtoshow);
4188add1155SRico Sonntag    }
4198add1155SRico Sonntag
4208add1155SRico Sonntag    /**
4218add1155SRico Sonntag     * Find common give names of unknown sexes.
4228add1155SRico Sonntag     *
4238add1155SRico Sonntag     * @param int $threshold
4248add1155SRico Sonntag     * @param int $maxtoshow
4258add1155SRico Sonntag     *
4268add1155SRico Sonntag     * @return string
4278add1155SRico Sonntag     */
4288add1155SRico Sonntag    public function commonGivenUnknownListTotals(int $threshold = 1, int $maxtoshow = 10): string
4298add1155SRico Sonntag    {
4308add1155SRico Sonntag        return $this->commonGivenQuery('U', 'list', true, $threshold, $maxtoshow);
4318add1155SRico Sonntag    }
4328add1155SRico Sonntag
4338add1155SRico Sonntag    /**
4348add1155SRico Sonntag     * Find common give names of unknown sexes.
4358add1155SRico Sonntag     *
4368add1155SRico Sonntag     * @param int $threshold
4378add1155SRico Sonntag     * @param int $maxtoshow
4388add1155SRico Sonntag     *
4398add1155SRico Sonntag     * @return string
4408add1155SRico Sonntag     */
4418add1155SRico Sonntag    public function commonGivenUnknownTable(int $threshold = 1, int $maxtoshow = 10): string
4428add1155SRico Sonntag    {
4438add1155SRico Sonntag        return $this->commonGivenQuery('U', 'table', false, $threshold, $maxtoshow);
4448add1155SRico Sonntag    }
4458add1155SRico Sonntag
4468add1155SRico Sonntag    /**
4473dc8167dSGreg Roach     * Count the number of distinct given names (or the number of occurences of specific given names).
4488add1155SRico Sonntag     *
44909482a55SGreg Roach     * @param array<string> ...$params
4508add1155SRico Sonntag     *
4518add1155SRico Sonntag     * @return string
4528add1155SRico Sonntag     */
4538add1155SRico Sonntag    public function totalGivennames(...$params): string
4548add1155SRico Sonntag    {
4553dc8167dSGreg Roach        $query = DB::table('name')
4563dc8167dSGreg Roach            ->where('n_file', '=', $this->tree->id());
4573dc8167dSGreg Roach
458320f6a24SGreg Roach        if ($params === []) {
459c09e99bfSRico Sonntag            // Count number of distinct given names.
4603dc8167dSGreg Roach            $query
461c09e99bfSRico Sonntag                ->distinct()
4628fb4e87cSGreg Roach                ->where('n_givn', '<>', Individual::PRAENOMEN_NESCIO)
463c09e99bfSRico Sonntag                ->whereNotNull('n_givn');
4648add1155SRico Sonntag        } else {
465c09e99bfSRico Sonntag            // Count number of occurences of specific given names.
46644cdc21eSGreg Roach            $query->whereIn('n_givn', $params);
4678add1155SRico Sonntag        }
4688add1155SRico Sonntag
469c09e99bfSRico Sonntag        $count = $query->count('n_givn');
4703dc8167dSGreg Roach
4713dc8167dSGreg Roach        return I18N::number($count);
4728add1155SRico Sonntag    }
4738add1155SRico Sonntag
4748add1155SRico Sonntag    /**
475320f6a24SGreg Roach     * Count the number of distinct surnames (or the number of occurrences of specific surnames).
4768add1155SRico Sonntag     *
47709482a55SGreg Roach     * @param array<string> ...$params
4788add1155SRico Sonntag     *
4798add1155SRico Sonntag     * @return string
4808add1155SRico Sonntag     */
4818add1155SRico Sonntag    public function totalSurnames(...$params): string
4828add1155SRico Sonntag    {
4833dc8167dSGreg Roach        $query = DB::table('name')
4843dc8167dSGreg Roach            ->where('n_file', '=', $this->tree->id());
4853dc8167dSGreg Roach
486320f6a24SGreg Roach        if ($params === []) {
4873dc8167dSGreg Roach            // Count number of distinct surnames
488c09e99bfSRico Sonntag            $query->distinct()
489c09e99bfSRico Sonntag                ->whereNotNull('n_surn');
4908add1155SRico Sonntag        } else {
4913dc8167dSGreg Roach            // Count number of occurences of specific surnames.
4923dc8167dSGreg Roach            $query->whereIn('n_surn', $params);
4938add1155SRico Sonntag        }
4948add1155SRico Sonntag
495c09e99bfSRico Sonntag        $count = $query->count('n_surn');
4968add1155SRico Sonntag
4973dc8167dSGreg Roach        return I18N::number($count);
4988add1155SRico Sonntag    }
4998add1155SRico Sonntag
5008add1155SRico Sonntag    /**
5018add1155SRico Sonntag     * @param int $number_of_surnames
5028add1155SRico Sonntag     * @param int $threshold
5038add1155SRico Sonntag     *
50409482a55SGreg Roach     * @return array<array<int>>
5058add1155SRico Sonntag     */
5068add1155SRico Sonntag    private function topSurnames(int $number_of_surnames, int $threshold): array
5078add1155SRico Sonntag    {
5088add1155SRico Sonntag        // Use the count of base surnames.
509d1a467e4SGreg Roach        $top_surnames = DB::table('name')
510d1a467e4SGreg Roach            ->where('n_file', '=', $this->tree->id())
511d1a467e4SGreg Roach            ->where('n_type', '<>', '_MARNM')
5128fb4e87cSGreg Roach            ->whereNotIn('n_surn', ['', Individual::NOMEN_NESCIO])
51347256fc5SGreg Roach            ->select(['n_surn'])
5147f5c2944SGreg Roach            ->groupBy(['n_surn'])
5152da2e0a6SGreg Roach            ->orderByRaw('COUNT(n_surn) DESC')
5162da2e0a6SGreg Roach            ->orderBy(new Expression('COUNT(n_surn)'), 'DESC')
5172da2e0a6SGreg Roach            ->having(new Expression('COUNT(n_surn)'), '>=', $threshold)
518d1a467e4SGreg Roach            ->take($number_of_surnames)
519d1a467e4SGreg Roach            ->get()
520d1a467e4SGreg Roach            ->pluck('n_surn')
521d1a467e4SGreg Roach            ->all();
5228add1155SRico Sonntag
5238add1155SRico Sonntag        $surnames = [];
5242da2e0a6SGreg Roach
5258add1155SRico Sonntag        foreach ($top_surnames as $top_surname) {
5262da2e0a6SGreg Roach            $surnames[$top_surname] = DB::table('name')
527d1a467e4SGreg Roach                ->where('n_file', '=', $this->tree->id())
5282da2e0a6SGreg Roach                ->where('n_type', '<>', '_MARNM')
5292da2e0a6SGreg Roach                ->where('n_surn', '=', $top_surname)
5302da2e0a6SGreg Roach                ->select(['n_surn', new Expression('COUNT(n_surn) AS count')])
5317f5c2944SGreg Roach                ->groupBy(['n_surn'])
5322da2e0a6SGreg Roach                ->orderBy('n_surn')
533d1a467e4SGreg Roach                ->get()
534d1a467e4SGreg Roach                ->pluck('count', 'n_surn')
535b7c04225SGreg Roach                ->map(static fn (string $count): int => (int) $count)
536d1a467e4SGreg Roach                ->all();
5378add1155SRico Sonntag        }
5388add1155SRico Sonntag
5398add1155SRico Sonntag        return $surnames;
5408add1155SRico Sonntag    }
5418add1155SRico Sonntag
5428add1155SRico Sonntag    /**
5438add1155SRico Sonntag     * Find common surnames.
5448add1155SRico Sonntag     *
5458add1155SRico Sonntag     * @return string
5468add1155SRico Sonntag     */
5478add1155SRico Sonntag    public function getCommonSurname(): string
5488add1155SRico Sonntag    {
5498add1155SRico Sonntag        $top_surname = $this->topSurnames(1, 0);
550f24db0ceSRico Sonntag
5511c6e5e0cSGreg Roach        return implode(', ', array_keys(array_shift($top_surname) ?? []));
5528add1155SRico Sonntag    }
5538add1155SRico Sonntag
5548add1155SRico Sonntag    /**
5558add1155SRico Sonntag     * Find common surnames.
5568add1155SRico Sonntag     *
5578add1155SRico Sonntag     * @param string $type
5588add1155SRico Sonntag     * @param bool   $show_tot
5598add1155SRico Sonntag     * @param int    $threshold
5608add1155SRico Sonntag     * @param int    $number_of_surnames
5618add1155SRico Sonntag     * @param string $sorting
5628add1155SRico Sonntag     *
5638add1155SRico Sonntag     * @return string
5648add1155SRico Sonntag     */
5658add1155SRico Sonntag    private function commonSurnamesQuery(
5668add1155SRico Sonntag        string $type,
5678add1155SRico Sonntag        bool $show_tot,
5688add1155SRico Sonntag        int $threshold,
5698add1155SRico Sonntag        int $number_of_surnames,
5708add1155SRico Sonntag        string $sorting
5718add1155SRico Sonntag    ): string {
5728add1155SRico Sonntag        $surnames = $this->topSurnames($number_of_surnames, $threshold);
5738add1155SRico Sonntag
5748add1155SRico Sonntag        switch ($sorting) {
5758add1155SRico Sonntag            default:
5768add1155SRico Sonntag            case 'alpha':
57737646143SGreg Roach                uksort($surnames, I18N::comparator());
5788add1155SRico Sonntag                break;
5798add1155SRico Sonntag            case 'count':
5808add1155SRico Sonntag                break;
5818add1155SRico Sonntag            case 'rcount':
5828add1155SRico Sonntag                $surnames = array_reverse($surnames, true);
5838add1155SRico Sonntag                break;
5848add1155SRico Sonntag        }
5858add1155SRico Sonntag
58667992b6aSRichard Cissee        // find a module providing individual lists
587b55cbc6bSGreg Roach        $module_service = app(ModuleService::class);
588b55cbc6bSGreg Roach        assert($module_service instanceof ModuleService);
589b55cbc6bSGreg Roach
590b55cbc6bSGreg Roach        $module = $module_service
591b55cbc6bSGreg Roach            ->findByComponent(ModuleListInterface::class, $this->tree, Auth::user())
592b55cbc6bSGreg Roach            ->first(static fn (ModuleInterface $module): bool => $module instanceof IndividualListModule);
59367992b6aSRichard Cissee
594cd1ec0d0SGreg Roach        if ($type === 'list') {
595cd1ec0d0SGreg Roach            return view('lists/surnames-bullet-list', [
596cd1ec0d0SGreg Roach                'surnames' => $surnames,
5976efab796SGreg Roach                'module'   => $module,
598cd1ec0d0SGreg Roach                'totals'   => $show_tot,
599cd1ec0d0SGreg Roach                'tree'     => $this->tree,
600cd1ec0d0SGreg Roach            ]);
601cd1ec0d0SGreg Roach        }
602cd1ec0d0SGreg Roach
603cd1ec0d0SGreg Roach        return view('lists/surnames-compact-list', [
604cd1ec0d0SGreg Roach            'surnames' => $surnames,
6056efab796SGreg Roach            'module'   => $module,
606cd1ec0d0SGreg Roach            'totals'   => $show_tot,
607cd1ec0d0SGreg Roach            'tree'     => $this->tree,
608cd1ec0d0SGreg Roach        ]);
6098add1155SRico Sonntag    }
6108add1155SRico Sonntag
6118add1155SRico Sonntag    /**
6128add1155SRico Sonntag     * Find common surnames.
6138add1155SRico Sonntag     *
6148add1155SRico Sonntag     * @param int    $threshold
6158add1155SRico Sonntag     * @param int    $number_of_surnames
6168add1155SRico Sonntag     * @param string $sorting
6178add1155SRico Sonntag     *
6188add1155SRico Sonntag     * @return string
6198add1155SRico Sonntag     */
6208add1155SRico Sonntag    public function commonSurnames(
6218add1155SRico Sonntag        int $threshold = 1,
6228add1155SRico Sonntag        int $number_of_surnames = 10,
6238add1155SRico Sonntag        string $sorting = 'alpha'
6248add1155SRico Sonntag    ): string {
6258add1155SRico Sonntag        return $this->commonSurnamesQuery('nolist', false, $threshold, $number_of_surnames, $sorting);
6268add1155SRico Sonntag    }
6278add1155SRico Sonntag
6288add1155SRico Sonntag    /**
6298add1155SRico Sonntag     * Find common surnames.
6308add1155SRico Sonntag     *
6318add1155SRico Sonntag     * @param int    $threshold
6328add1155SRico Sonntag     * @param int    $number_of_surnames
6338add1155SRico Sonntag     * @param string $sorting
6348add1155SRico Sonntag     *
6358add1155SRico Sonntag     * @return string
6368add1155SRico Sonntag     */
6378add1155SRico Sonntag    public function commonSurnamesTotals(
6388add1155SRico Sonntag        int $threshold = 1,
6398add1155SRico Sonntag        int $number_of_surnames = 10,
6402da2e0a6SGreg Roach        string $sorting = 'count'
6418add1155SRico Sonntag    ): string {
6428add1155SRico Sonntag        return $this->commonSurnamesQuery('nolist', true, $threshold, $number_of_surnames, $sorting);
6438add1155SRico Sonntag    }
6448add1155SRico Sonntag
6458add1155SRico Sonntag    /**
6468add1155SRico Sonntag     * Find common surnames.
6478add1155SRico Sonntag     *
6488add1155SRico Sonntag     * @param int    $threshold
6498add1155SRico Sonntag     * @param int    $number_of_surnames
6508add1155SRico Sonntag     * @param string $sorting
6518add1155SRico Sonntag     *
6528add1155SRico Sonntag     * @return string
6538add1155SRico Sonntag     */
6548add1155SRico Sonntag    public function commonSurnamesList(
6558add1155SRico Sonntag        int $threshold = 1,
6568add1155SRico Sonntag        int $number_of_surnames = 10,
6578add1155SRico Sonntag        string $sorting = 'alpha'
6588add1155SRico Sonntag    ): string {
6598add1155SRico Sonntag        return $this->commonSurnamesQuery('list', false, $threshold, $number_of_surnames, $sorting);
6608add1155SRico Sonntag    }
6618add1155SRico Sonntag
6628add1155SRico Sonntag    /**
6638add1155SRico Sonntag     * Find common surnames.
6648add1155SRico Sonntag     *
6658add1155SRico Sonntag     * @param int    $threshold
6668add1155SRico Sonntag     * @param int    $number_of_surnames
6678add1155SRico Sonntag     * @param string $sorting
6688add1155SRico Sonntag     *
6698add1155SRico Sonntag     * @return string
6708add1155SRico Sonntag     */
6718add1155SRico Sonntag    public function commonSurnamesListTotals(
6728add1155SRico Sonntag        int $threshold = 1,
6738add1155SRico Sonntag        int $number_of_surnames = 10,
6742da2e0a6SGreg Roach        string $sorting = 'count'
6758add1155SRico Sonntag    ): string {
6768add1155SRico Sonntag        return $this->commonSurnamesQuery('list', true, $threshold, $number_of_surnames, $sorting);
6778add1155SRico Sonntag    }
6788add1155SRico Sonntag
6798add1155SRico Sonntag    /**
680cde1d378SGreg Roach     * Get a count of births by month.
6818add1155SRico Sonntag     *
6828add1155SRico Sonntag     * @param int  $year1
6838add1155SRico Sonntag     * @param int  $year2
6848add1155SRico Sonntag     *
685cde1d378SGreg Roach     * @return Builder
6868add1155SRico Sonntag     */
687cde1d378SGreg Roach    public function statsBirthQuery(int $year1 = -1, int $year2 = -1): Builder
6888add1155SRico Sonntag    {
689d1a467e4SGreg Roach        $query = DB::table('dates')
690a69f5655SGreg Roach            ->select(['d_month', new Expression('COUNT(*) AS total')])
691d1a467e4SGreg Roach            ->where('d_file', '=', $this->tree->id())
692d1a467e4SGreg Roach            ->where('d_fact', '=', 'BIRT')
693d1a467e4SGreg Roach            ->whereIn('d_type', ['@#DGREGORIAN@', '@#DJULIAN@'])
6947f5c2944SGreg Roach            ->groupBy(['d_month']);
6958add1155SRico Sonntag
6968add1155SRico Sonntag        if ($year1 >= 0 && $year2 >= 0) {
697d1a467e4SGreg Roach            $query->whereBetween('d_year', [$year1, $year2]);
6988add1155SRico Sonntag        }
6998add1155SRico Sonntag
700cde1d378SGreg Roach        return $query;
701cde1d378SGreg Roach    }
702cde1d378SGreg Roach
703cde1d378SGreg Roach    /**
704cde1d378SGreg Roach     * Get a count of births by month.
705cde1d378SGreg Roach     *
706cde1d378SGreg Roach     * @param int  $year1
707cde1d378SGreg Roach     * @param int  $year2
708cde1d378SGreg Roach     *
709cde1d378SGreg Roach     * @return Builder
710cde1d378SGreg Roach     */
711cde1d378SGreg Roach    public function statsBirthBySexQuery(int $year1 = -1, int $year2 = -1): Builder
712cde1d378SGreg Roach    {
713cde1d378SGreg Roach        return $this->statsBirthQuery($year1, $year2)
714a69f5655SGreg Roach            ->select(['d_month', 'i_sex', new Expression('COUNT(*) AS total')])
7150b5fd0a6SGreg Roach            ->join('individuals', static function (JoinClause $join): void {
716d1a467e4SGreg Roach                $join
717d1a467e4SGreg Roach                    ->on('i_id', '=', 'd_gid')
718d1a467e4SGreg Roach                    ->on('i_file', '=', 'd_file');
719d1a467e4SGreg Roach            })
7207f5c2944SGreg Roach            ->groupBy(['i_sex']);
7218add1155SRico Sonntag    }
7228add1155SRico Sonntag
7238add1155SRico Sonntag    /**
7248add1155SRico Sonntag     * General query on births.
7258add1155SRico Sonntag     *
7268add1155SRico Sonntag     * @param string|null $color_from
7278add1155SRico Sonntag     * @param string|null $color_to
7288add1155SRico Sonntag     *
7298add1155SRico Sonntag     * @return string
7308add1155SRico Sonntag     */
73188de55fdSRico Sonntag    public function statsBirth(string $color_from = null, string $color_to = null): string
7328add1155SRico Sonntag    {
733f78da678SGreg Roach        return (new ChartBirth($this->century_service, $this->color_service, $this->tree))
73488de55fdSRico Sonntag            ->chartBirth($color_from, $color_to);
7358add1155SRico Sonntag    }
7368add1155SRico Sonntag
7378add1155SRico Sonntag    /**
7388add1155SRico Sonntag     * Get a list of death dates.
7398add1155SRico Sonntag     *
7408add1155SRico Sonntag     * @param int  $year1
7418add1155SRico Sonntag     * @param int  $year2
7428add1155SRico Sonntag     *
743cde1d378SGreg Roach     * @return Builder
7448add1155SRico Sonntag     */
745cde1d378SGreg Roach    public function statsDeathQuery(int $year1 = -1, int $year2 = -1): Builder
7468add1155SRico Sonntag    {
747d1a467e4SGreg Roach        $query = DB::table('dates')
748a69f5655SGreg Roach            ->select(['d_month', new Expression('COUNT(*) AS total')])
749d1a467e4SGreg Roach            ->where('d_file', '=', $this->tree->id())
750d1a467e4SGreg Roach            ->where('d_fact', '=', 'DEAT')
751d1a467e4SGreg Roach            ->whereIn('d_type', ['@#DGREGORIAN@', '@#DJULIAN@'])
7527f5c2944SGreg Roach            ->groupBy(['d_month']);
7538add1155SRico Sonntag
7548add1155SRico Sonntag        if ($year1 >= 0 && $year2 >= 0) {
755d1a467e4SGreg Roach            $query->whereBetween('d_year', [$year1, $year2]);
7568add1155SRico Sonntag        }
7578add1155SRico Sonntag
758cde1d378SGreg Roach        return $query;
759cde1d378SGreg Roach    }
760cde1d378SGreg Roach
761cde1d378SGreg Roach    /**
762cde1d378SGreg Roach     * Get a list of death dates.
763cde1d378SGreg Roach     *
764cde1d378SGreg Roach     * @param int  $year1
765cde1d378SGreg Roach     * @param int  $year2
766cde1d378SGreg Roach     *
767cde1d378SGreg Roach     * @return Builder
768cde1d378SGreg Roach     */
769cde1d378SGreg Roach    public function statsDeathBySexQuery(int $year1 = -1, int $year2 = -1): Builder
770cde1d378SGreg Roach    {
771cde1d378SGreg Roach        return $this->statsDeathQuery($year1, $year2)
772a69f5655SGreg Roach            ->select(['d_month', 'i_sex', new Expression('COUNT(*) AS total')])
7730b5fd0a6SGreg Roach            ->join('individuals', static function (JoinClause $join): void {
774d1a467e4SGreg Roach                $join
775d1a467e4SGreg Roach                    ->on('i_id', '=', 'd_gid')
776d1a467e4SGreg Roach                    ->on('i_file', '=', 'd_file');
777d1a467e4SGreg Roach            })
7787f5c2944SGreg Roach            ->groupBy(['i_sex']);
7798add1155SRico Sonntag    }
7808add1155SRico Sonntag
7818add1155SRico Sonntag    /**
7828add1155SRico Sonntag     * General query on deaths.
7838add1155SRico Sonntag     *
7848add1155SRico Sonntag     * @param string|null $color_from
7858add1155SRico Sonntag     * @param string|null $color_to
7868add1155SRico Sonntag     *
7878add1155SRico Sonntag     * @return string
7888add1155SRico Sonntag     */
78988de55fdSRico Sonntag    public function statsDeath(string $color_from = null, string $color_to = null): string
7908add1155SRico Sonntag    {
791f78da678SGreg Roach        return (new ChartDeath($this->century_service, $this->color_service, $this->tree))
79288de55fdSRico Sonntag            ->chartDeath($color_from, $color_to);
7938add1155SRico Sonntag    }
7948add1155SRico Sonntag
7958add1155SRico Sonntag    /**
7968add1155SRico Sonntag     * General query on ages.
7978add1155SRico Sonntag     *
7988add1155SRico Sonntag     * @param string $related
7998add1155SRico Sonntag     * @param string $sex
8008add1155SRico Sonntag     * @param int    $year1
8018add1155SRico Sonntag     * @param int    $year2
8028add1155SRico Sonntag     *
80376d39c55SGreg Roach     * @return array<stdClass>
8048add1155SRico Sonntag     */
805d823340dSGreg Roach    public function statsAgeQuery(string $related = 'BIRT', string $sex = 'BOTH', int $year1 = -1, int $year2 = -1): array
8068add1155SRico Sonntag    {
80744cdc21eSGreg Roach        $prefix = DB::connection()->getTablePrefix();
8088add1155SRico Sonntag
80944cdc21eSGreg Roach        $query = $this->birthAndDeathQuery($sex);
8108add1155SRico Sonntag
8118add1155SRico Sonntag        if ($year1 >= 0 && $year2 >= 0) {
81244cdc21eSGreg Roach            $query
81344cdc21eSGreg Roach                ->whereIn('birth.d_type', ['@#DGREGORIAN@', '@#DJULIAN@'])
81444cdc21eSGreg Roach                ->whereIn('death.d_type', ['@#DGREGORIAN@', '@#DJULIAN@']);
81544cdc21eSGreg Roach
8168add1155SRico Sonntag            if ($related === 'BIRT') {
81744cdc21eSGreg Roach                $query->whereBetween('birth.d_year', [$year1, $year2]);
8188add1155SRico Sonntag            } elseif ($related === 'DEAT') {
81944cdc21eSGreg Roach                $query->whereBetween('death.d_year', [$year1, $year2]);
8208add1155SRico Sonntag            }
8218add1155SRico Sonntag        }
8228add1155SRico Sonntag
82344cdc21eSGreg Roach        return $query
824a69f5655SGreg Roach            ->select(new Expression($prefix . 'death.d_julianday2 - ' . $prefix . 'birth.d_julianday1 AS days'))
825c8c87812SRico Sonntag            ->orderBy('days', 'desc')
82644cdc21eSGreg Roach            ->get()
82744cdc21eSGreg Roach            ->all();
8288add1155SRico Sonntag    }
8298add1155SRico Sonntag
8308add1155SRico Sonntag    /**
8318add1155SRico Sonntag     * General query on ages.
8328add1155SRico Sonntag     *
8338add1155SRico Sonntag     * @return string
8348add1155SRico Sonntag     */
83588de55fdSRico Sonntag    public function statsAge(): string
8368add1155SRico Sonntag    {
837f78da678SGreg Roach        return (new ChartAge($this->century_service, $this->tree))->chartAge();
8388add1155SRico Sonntag    }
8398add1155SRico Sonntag
8408add1155SRico Sonntag    /**
8418add1155SRico Sonntag     * Lifespan
8428add1155SRico Sonntag     *
8438add1155SRico Sonntag     * @param string $type
8448add1155SRico Sonntag     * @param string $sex
8458add1155SRico Sonntag     *
8468add1155SRico Sonntag     * @return string
8478add1155SRico Sonntag     */
8488add1155SRico Sonntag    private function longlifeQuery(string $type, string $sex): string
8498add1155SRico Sonntag    {
85044cdc21eSGreg Roach        $prefix = DB::connection()->getTablePrefix();
8518add1155SRico Sonntag
85244cdc21eSGreg Roach        $row = $this->birthAndDeathQuery($sex)
85344cdc21eSGreg Roach            ->orderBy('days', 'desc')
854a69f5655SGreg Roach            ->select(['individuals.*', new Expression($prefix . 'death.d_julianday2 - ' . $prefix . 'birth.d_julianday1 AS days')])
85544cdc21eSGreg Roach            ->first();
85644cdc21eSGreg Roach
85744cdc21eSGreg Roach        if ($row === null) {
8588add1155SRico Sonntag            return '';
8598add1155SRico Sonntag        }
86044cdc21eSGreg Roach
86144cdc21eSGreg Roach        /** @var Individual $individual */
8626b9cb339SGreg Roach        $individual = Registry::individualFactory()->mapper($this->tree)($row);
86344cdc21eSGreg Roach
864747d54c2SGreg Roach        if ($type !== 'age' && !$individual->canShow()) {
86544cdc21eSGreg Roach            return I18N::translate('This information is private and cannot be shown.');
86644cdc21eSGreg Roach        }
86744cdc21eSGreg Roach
8688add1155SRico Sonntag        switch ($type) {
8698add1155SRico Sonntag            default:
8708add1155SRico Sonntag            case 'full':
87144cdc21eSGreg Roach                return $individual->formatList();
8728add1155SRico Sonntag
87344cdc21eSGreg Roach            case 'age':
87444cdc21eSGreg Roach                return I18N::number((int) ($row->days / 365.25));
87544cdc21eSGreg Roach
87644cdc21eSGreg Roach            case 'name':
87739ca88baSGreg Roach                return '<a href="' . e($individual->url()) . '">' . $individual->fullName() . '</a>';
87844cdc21eSGreg Roach        }
8798add1155SRico Sonntag    }
8808add1155SRico Sonntag
8818add1155SRico Sonntag    /**
8828add1155SRico Sonntag     * Find the longest lived individual.
8838add1155SRico Sonntag     *
8848add1155SRico Sonntag     * @return string
8858add1155SRico Sonntag     */
8868add1155SRico Sonntag    public function longestLife(): string
8878add1155SRico Sonntag    {
8888add1155SRico Sonntag        return $this->longlifeQuery('full', 'BOTH');
8898add1155SRico Sonntag    }
8908add1155SRico Sonntag
8918add1155SRico Sonntag    /**
8928add1155SRico Sonntag     * Find the age of the longest lived individual.
8938add1155SRico Sonntag     *
8948add1155SRico Sonntag     * @return string
8958add1155SRico Sonntag     */
8968add1155SRico Sonntag    public function longestLifeAge(): string
8978add1155SRico Sonntag    {
8988add1155SRico Sonntag        return $this->longlifeQuery('age', 'BOTH');
8998add1155SRico Sonntag    }
9008add1155SRico Sonntag
9018add1155SRico Sonntag    /**
9028add1155SRico Sonntag     * Find the name of the longest lived individual.
9038add1155SRico Sonntag     *
9048add1155SRico Sonntag     * @return string
9058add1155SRico Sonntag     */
9068add1155SRico Sonntag    public function longestLifeName(): string
9078add1155SRico Sonntag    {
9088add1155SRico Sonntag        return $this->longlifeQuery('name', 'BOTH');
9098add1155SRico Sonntag    }
9108add1155SRico Sonntag
9118add1155SRico Sonntag    /**
9128add1155SRico Sonntag     * Find the longest lived female.
9138add1155SRico Sonntag     *
9148add1155SRico Sonntag     * @return string
9158add1155SRico Sonntag     */
9168add1155SRico Sonntag    public function longestLifeFemale(): string
9178add1155SRico Sonntag    {
9188add1155SRico Sonntag        return $this->longlifeQuery('full', 'F');
9198add1155SRico Sonntag    }
9208add1155SRico Sonntag
9218add1155SRico Sonntag    /**
9228add1155SRico Sonntag     * Find the age of the longest lived female.
9238add1155SRico Sonntag     *
9248add1155SRico Sonntag     * @return string
9258add1155SRico Sonntag     */
9268add1155SRico Sonntag    public function longestLifeFemaleAge(): string
9278add1155SRico Sonntag    {
9288add1155SRico Sonntag        return $this->longlifeQuery('age', 'F');
9298add1155SRico Sonntag    }
9308add1155SRico Sonntag
9318add1155SRico Sonntag    /**
9328add1155SRico Sonntag     * Find the name of the longest lived female.
9338add1155SRico Sonntag     *
9348add1155SRico Sonntag     * @return string
9358add1155SRico Sonntag     */
9368add1155SRico Sonntag    public function longestLifeFemaleName(): string
9378add1155SRico Sonntag    {
9388add1155SRico Sonntag        return $this->longlifeQuery('name', 'F');
9398add1155SRico Sonntag    }
9408add1155SRico Sonntag
9418add1155SRico Sonntag    /**
9428add1155SRico Sonntag     * Find the longest lived male.
9438add1155SRico Sonntag     *
9448add1155SRico Sonntag     * @return string
9458add1155SRico Sonntag     */
9468add1155SRico Sonntag    public function longestLifeMale(): string
9478add1155SRico Sonntag    {
9488add1155SRico Sonntag        return $this->longlifeQuery('full', 'M');
9498add1155SRico Sonntag    }
9508add1155SRico Sonntag
9518add1155SRico Sonntag    /**
9528add1155SRico Sonntag     * Find the age of the longest lived male.
9538add1155SRico Sonntag     *
9548add1155SRico Sonntag     * @return string
9558add1155SRico Sonntag     */
9568add1155SRico Sonntag    public function longestLifeMaleAge(): string
9578add1155SRico Sonntag    {
9588add1155SRico Sonntag        return $this->longlifeQuery('age', 'M');
9598add1155SRico Sonntag    }
9608add1155SRico Sonntag
9618add1155SRico Sonntag    /**
9628add1155SRico Sonntag     * Find the name of the longest lived male.
9638add1155SRico Sonntag     *
9648add1155SRico Sonntag     * @return string
9658add1155SRico Sonntag     */
9668add1155SRico Sonntag    public function longestLifeMaleName(): string
9678add1155SRico Sonntag    {
9688add1155SRico Sonntag        return $this->longlifeQuery('name', 'M');
9698add1155SRico Sonntag    }
9708add1155SRico Sonntag
9718add1155SRico Sonntag    /**
9728add1155SRico Sonntag     * Returns the calculated age the time of event.
9738add1155SRico Sonntag     *
974054771e9SGreg Roach     * @param int $days The age from the database record
9758add1155SRico Sonntag     *
9768add1155SRico Sonntag     * @return string
9778add1155SRico Sonntag     */
978054771e9SGreg Roach    private function calculateAge(int $days): string
9798add1155SRico Sonntag    {
980054771e9SGreg Roach        if ($days < 31) {
981054771e9SGreg Roach            return I18N::plural('%s day', '%s days', $days, I18N::number($days));
9828add1155SRico Sonntag        }
9838add1155SRico Sonntag
984054771e9SGreg Roach        if ($days < 365) {
985054771e9SGreg Roach            $months = (int) ($days / 30.5);
9861061e22bSGreg Roach            return I18N::plural('%s month', '%s months', $months, I18N::number($months));
987054771e9SGreg Roach        }
988054771e9SGreg Roach
989054771e9SGreg Roach        $years = (int) ($days / 365.25);
990054771e9SGreg Roach
991054771e9SGreg Roach        return I18N::plural('%s year', '%s years', $years, I18N::number($years));
9928add1155SRico Sonntag    }
9938add1155SRico Sonntag
9948add1155SRico Sonntag    /**
9958add1155SRico Sonntag     * Find the oldest individuals.
9968add1155SRico Sonntag     *
9978add1155SRico Sonntag     * @param string $sex
9988add1155SRico Sonntag     * @param int    $total
9998add1155SRico Sonntag     *
100091c84b80SGreg Roach     * @return array<array<string,mixed>>
10018add1155SRico Sonntag     */
10028add1155SRico Sonntag    private function topTenOldestQuery(string $sex, int $total): array
10038add1155SRico Sonntag    {
100444cdc21eSGreg Roach        $prefix = DB::connection()->getTablePrefix();
10058add1155SRico Sonntag
100644cdc21eSGreg Roach        $rows = $this->birthAndDeathQuery($sex)
100744cdc21eSGreg Roach            ->groupBy(['i_id', 'i_file'])
100844cdc21eSGreg Roach            ->orderBy('days', 'desc')
1009a69f5655SGreg Roach            ->select(['individuals.*', new Expression('MAX(' . $prefix . 'death.d_julianday2 - ' . $prefix . 'birth.d_julianday1) AS days')])
101044cdc21eSGreg Roach            ->take($total)
101144cdc21eSGreg Roach            ->get();
10128add1155SRico Sonntag
10138add1155SRico Sonntag        $top10 = [];
10148add1155SRico Sonntag        foreach ($rows as $row) {
101544cdc21eSGreg Roach            /** @var Individual $individual */
10166b9cb339SGreg Roach            $individual = Registry::individualFactory()->mapper($this->tree)($row);
10178add1155SRico Sonntag
101844cdc21eSGreg Roach            if ($individual->canShow()) {
10198add1155SRico Sonntag                $top10[] = [
102044cdc21eSGreg Roach                    'person' => $individual,
102144cdc21eSGreg Roach                    'age'    => $this->calculateAge((int) $row->days),
10228add1155SRico Sonntag                ];
10238add1155SRico Sonntag            }
10248add1155SRico Sonntag        }
10258add1155SRico Sonntag
10268add1155SRico Sonntag        return $top10;
10278add1155SRico Sonntag    }
10288add1155SRico Sonntag
10298add1155SRico Sonntag    /**
10308add1155SRico Sonntag     * Find the oldest individuals.
10318add1155SRico Sonntag     *
10328add1155SRico Sonntag     * @param int $total
10338add1155SRico Sonntag     *
10348add1155SRico Sonntag     * @return string
10358add1155SRico Sonntag     */
10368add1155SRico Sonntag    public function topTenOldest(int $total = 10): string
10378add1155SRico Sonntag    {
10388add1155SRico Sonntag        $records = $this->topTenOldestQuery('BOTH', $total);
10398add1155SRico Sonntag
1040c0112ce8SGreg Roach        return view('statistics/individuals/top10-nolist', [
10418add1155SRico Sonntag            'records' => $records,
1042c0112ce8SGreg Roach        ]);
10438add1155SRico Sonntag    }
10448add1155SRico Sonntag
10458add1155SRico Sonntag    /**
10468add1155SRico Sonntag     * Find the oldest living individuals.
10478add1155SRico Sonntag     *
10488add1155SRico Sonntag     * @param int $total
10498add1155SRico Sonntag     *
10508add1155SRico Sonntag     * @return string
10518add1155SRico Sonntag     */
10528add1155SRico Sonntag    public function topTenOldestList(int $total = 10): string
10538add1155SRico Sonntag    {
10548add1155SRico Sonntag        $records = $this->topTenOldestQuery('BOTH', $total);
10558add1155SRico Sonntag
1056c0112ce8SGreg Roach        return view('statistics/individuals/top10-list', [
10578add1155SRico Sonntag            'records' => $records,
1058c0112ce8SGreg Roach        ]);
10598add1155SRico Sonntag    }
10608add1155SRico Sonntag
10618add1155SRico Sonntag    /**
10628add1155SRico Sonntag     * Find the oldest females.
10638add1155SRico Sonntag     *
10648add1155SRico Sonntag     * @param int $total
10658add1155SRico Sonntag     *
10668add1155SRico Sonntag     * @return string
10678add1155SRico Sonntag     */
10688add1155SRico Sonntag    public function topTenOldestFemale(int $total = 10): string
10698add1155SRico Sonntag    {
10708add1155SRico Sonntag        $records = $this->topTenOldestQuery('F', $total);
10718add1155SRico Sonntag
1072c0112ce8SGreg Roach        return view('statistics/individuals/top10-nolist', [
10738add1155SRico Sonntag            'records' => $records,
1074c0112ce8SGreg Roach        ]);
10758add1155SRico Sonntag    }
10768add1155SRico Sonntag
10778add1155SRico Sonntag    /**
10788add1155SRico Sonntag     * Find the oldest living females.
10798add1155SRico Sonntag     *
10808add1155SRico Sonntag     * @param int $total
10818add1155SRico Sonntag     *
10828add1155SRico Sonntag     * @return string
10838add1155SRico Sonntag     */
10848add1155SRico Sonntag    public function topTenOldestFemaleList(int $total = 10): string
10858add1155SRico Sonntag    {
10868add1155SRico Sonntag        $records = $this->topTenOldestQuery('F', $total);
10878add1155SRico Sonntag
1088c0112ce8SGreg Roach        return view('statistics/individuals/top10-list', [
10898add1155SRico Sonntag            'records' => $records,
1090c0112ce8SGreg Roach        ]);
10918add1155SRico Sonntag    }
10928add1155SRico Sonntag
10938add1155SRico Sonntag    /**
10948add1155SRico Sonntag     * Find the longest lived males.
10958add1155SRico Sonntag     *
10968add1155SRico Sonntag     * @param int $total
10978add1155SRico Sonntag     *
10988add1155SRico Sonntag     * @return string
10998add1155SRico Sonntag     */
11008add1155SRico Sonntag    public function topTenOldestMale(int $total = 10): string
11018add1155SRico Sonntag    {
11028add1155SRico Sonntag        $records = $this->topTenOldestQuery('M', $total);
11038add1155SRico Sonntag
1104c0112ce8SGreg Roach        return view('statistics/individuals/top10-nolist', [
11058add1155SRico Sonntag            'records' => $records,
1106c0112ce8SGreg Roach        ]);
11078add1155SRico Sonntag    }
11088add1155SRico Sonntag
11098add1155SRico Sonntag    /**
11108add1155SRico Sonntag     * Find the longest lived males.
11118add1155SRico Sonntag     *
11128add1155SRico Sonntag     * @param int $total
11138add1155SRico Sonntag     *
11148add1155SRico Sonntag     * @return string
11158add1155SRico Sonntag     */
11168add1155SRico Sonntag    public function topTenOldestMaleList(int $total = 10): string
11178add1155SRico Sonntag    {
11188add1155SRico Sonntag        $records = $this->topTenOldestQuery('M', $total);
11198add1155SRico Sonntag
1120c0112ce8SGreg Roach        return view('statistics/individuals/top10-list', [
11218add1155SRico Sonntag            'records' => $records,
1122c0112ce8SGreg Roach        ]);
11238add1155SRico Sonntag    }
11248add1155SRico Sonntag
11258add1155SRico Sonntag    /**
11268add1155SRico Sonntag     * Find the oldest living individuals.
11278add1155SRico Sonntag     *
1128c0112ce8SGreg Roach     * @param string $sex   "M", "F" or "BOTH"
11298add1155SRico Sonntag     * @param int    $total
11308add1155SRico Sonntag     *
113191c84b80SGreg Roach     * @return array<array<string,mixed>>
11328add1155SRico Sonntag     */
1133c0112ce8SGreg Roach    private function topTenOldestAliveQuery(string $sex, int $total): array
11348add1155SRico Sonntag    {
1135d1a467e4SGreg Roach        $query = DB::table('dates')
11360b5fd0a6SGreg Roach            ->join('individuals', static function (JoinClause $join): void {
1137d1a467e4SGreg Roach                $join
1138d1a467e4SGreg Roach                    ->on('i_id', '=', 'd_gid')
1139d1a467e4SGreg Roach                    ->on('i_file', '=', 'd_file');
1140d1a467e4SGreg Roach            })
1141d1a467e4SGreg Roach            ->where('d_file', '=', $this->tree->id())
1142d1a467e4SGreg Roach            ->where('d_julianday1', '<>', 0)
1143d1a467e4SGreg Roach            ->where('d_fact', '=', 'BIRT')
1144d1a467e4SGreg Roach            ->where('i_gedcom', 'NOT LIKE', "%\n1 DEAT%")
1145d1a467e4SGreg Roach            ->where('i_gedcom', 'NOT LIKE', "%\n1 BURI%")
1146d1a467e4SGreg Roach            ->where('i_gedcom', 'NOT LIKE', "%\n1 CREM%");
1147d1a467e4SGreg Roach
1148d1a467e4SGreg Roach        if ($sex === 'F' || $sex === 'M') {
1149d1a467e4SGreg Roach            $query->where('i_sex', '=', $sex);
11508add1155SRico Sonntag        }
11518add1155SRico Sonntag
1152c0112ce8SGreg Roach        return $query
1153d1a467e4SGreg Roach            ->groupBy(['i_id', 'i_file'])
1154a69f5655SGreg Roach            ->orderBy(new Expression('MIN(d_julianday1)'))
115547256fc5SGreg Roach            ->select(['individuals.*'])
1156d1a467e4SGreg Roach            ->take($total)
1157d1a467e4SGreg Roach            ->get()
11586b9cb339SGreg Roach            ->map(Registry::individualFactory()->mapper($this->tree))
1159c0112ce8SGreg Roach            ->filter(GedcomRecord::accessFilter())
1160c0112ce8SGreg Roach            ->map(function (Individual $individual): array {
1161c0112ce8SGreg Roach                return [
1162d1a467e4SGreg Roach                    'person' => $individual,
1163d97083feSGreg Roach                    'age'    => $this->calculateAge(Registry::timestampFactory()->now()->julianDay() - $individual->getBirthDate()->minimumJulianDay()),
11648add1155SRico Sonntag                ];
1165c0112ce8SGreg Roach            })
1166c0112ce8SGreg Roach            ->all();
11678add1155SRico Sonntag    }
11688add1155SRico Sonntag
11698add1155SRico Sonntag    /**
11708add1155SRico Sonntag     * Find the oldest living individuals.
11718add1155SRico Sonntag     *
11728add1155SRico Sonntag     * @param int $total
11738add1155SRico Sonntag     *
11748add1155SRico Sonntag     * @return string
11758add1155SRico Sonntag     */
11768add1155SRico Sonntag    public function topTenOldestAlive(int $total = 10): string
11778add1155SRico Sonntag    {
11788add1155SRico Sonntag        if (!Auth::isMember($this->tree)) {
11798add1155SRico Sonntag            return I18N::translate('This information is private and cannot be shown.');
11808add1155SRico Sonntag        }
11818add1155SRico Sonntag
11828add1155SRico Sonntag        $records = $this->topTenOldestAliveQuery('BOTH', $total);
11838add1155SRico Sonntag
1184c0112ce8SGreg Roach        return view('statistics/individuals/top10-nolist', [
11858add1155SRico Sonntag            'records' => $records,
1186c0112ce8SGreg Roach        ]);
11878add1155SRico Sonntag    }
11888add1155SRico Sonntag
11898add1155SRico Sonntag    /**
11908add1155SRico Sonntag     * Find the oldest living individuals.
11918add1155SRico Sonntag     *
11928add1155SRico Sonntag     * @param int $total
11938add1155SRico Sonntag     *
11948add1155SRico Sonntag     * @return string
11958add1155SRico Sonntag     */
11968add1155SRico Sonntag    public function topTenOldestListAlive(int $total = 10): string
11978add1155SRico Sonntag    {
11988add1155SRico Sonntag        if (!Auth::isMember($this->tree)) {
11998add1155SRico Sonntag            return I18N::translate('This information is private and cannot be shown.');
12008add1155SRico Sonntag        }
12018add1155SRico Sonntag
12028add1155SRico Sonntag        $records = $this->topTenOldestAliveQuery('BOTH', $total);
12038add1155SRico Sonntag
1204c0112ce8SGreg Roach        return view('statistics/individuals/top10-list', [
12058add1155SRico Sonntag            'records' => $records,
1206c0112ce8SGreg Roach        ]);
12078add1155SRico Sonntag    }
12088add1155SRico Sonntag
12098add1155SRico Sonntag    /**
12108add1155SRico Sonntag     * Find the oldest living females.
12118add1155SRico Sonntag     *
12128add1155SRico Sonntag     * @param int $total
12138add1155SRico Sonntag     *
12148add1155SRico Sonntag     * @return string
12158add1155SRico Sonntag     */
12168add1155SRico Sonntag    public function topTenOldestFemaleAlive(int $total = 10): string
12178add1155SRico Sonntag    {
12188add1155SRico Sonntag        if (!Auth::isMember($this->tree)) {
12198add1155SRico Sonntag            return I18N::translate('This information is private and cannot be shown.');
12208add1155SRico Sonntag        }
12218add1155SRico Sonntag
12228add1155SRico Sonntag        $records = $this->topTenOldestAliveQuery('F', $total);
12238add1155SRico Sonntag
1224c0112ce8SGreg Roach        return view('statistics/individuals/top10-nolist', [
12258add1155SRico Sonntag            'records' => $records,
1226c0112ce8SGreg Roach        ]);
12278add1155SRico Sonntag    }
12288add1155SRico Sonntag
12298add1155SRico Sonntag    /**
12308add1155SRico Sonntag     * Find the oldest living females.
12318add1155SRico Sonntag     *
12328add1155SRico Sonntag     * @param int $total
12338add1155SRico Sonntag     *
12348add1155SRico Sonntag     * @return string
12358add1155SRico Sonntag     */
12368add1155SRico Sonntag    public function topTenOldestFemaleListAlive(int $total = 10): string
12378add1155SRico Sonntag    {
12388add1155SRico Sonntag        if (!Auth::isMember($this->tree)) {
12398add1155SRico Sonntag            return I18N::translate('This information is private and cannot be shown.');
12408add1155SRico Sonntag        }
12418add1155SRico Sonntag
12428add1155SRico Sonntag        $records = $this->topTenOldestAliveQuery('F', $total);
12438add1155SRico Sonntag
1244c0112ce8SGreg Roach        return view('statistics/individuals/top10-list', [
12458add1155SRico Sonntag            'records' => $records,
1246c0112ce8SGreg Roach        ]);
12478add1155SRico Sonntag    }
12488add1155SRico Sonntag
12498add1155SRico Sonntag    /**
12508add1155SRico Sonntag     * Find the longest lived living males.
12518add1155SRico Sonntag     *
12528add1155SRico Sonntag     * @param int $total
12538add1155SRico Sonntag     *
12548add1155SRico Sonntag     * @return string
12558add1155SRico Sonntag     */
12568add1155SRico Sonntag    public function topTenOldestMaleAlive(int $total = 10): string
12578add1155SRico Sonntag    {
12588add1155SRico Sonntag        if (!Auth::isMember($this->tree)) {
12598add1155SRico Sonntag            return I18N::translate('This information is private and cannot be shown.');
12608add1155SRico Sonntag        }
12618add1155SRico Sonntag
12628add1155SRico Sonntag        $records = $this->topTenOldestAliveQuery('M', $total);
12638add1155SRico Sonntag
1264c0112ce8SGreg Roach        return view('statistics/individuals/top10-nolist', [
12658add1155SRico Sonntag            'records' => $records,
1266c0112ce8SGreg Roach        ]);
12678add1155SRico Sonntag    }
12688add1155SRico Sonntag
12698add1155SRico Sonntag    /**
12708add1155SRico Sonntag     * Find the longest lived living males.
12718add1155SRico Sonntag     *
12728add1155SRico Sonntag     * @param int $total
12738add1155SRico Sonntag     *
12748add1155SRico Sonntag     * @return string
12758add1155SRico Sonntag     */
12768add1155SRico Sonntag    public function topTenOldestMaleListAlive(int $total = 10): string
12778add1155SRico Sonntag    {
12788add1155SRico Sonntag        if (!Auth::isMember($this->tree)) {
12798add1155SRico Sonntag            return I18N::translate('This information is private and cannot be shown.');
12808add1155SRico Sonntag        }
12818add1155SRico Sonntag
12828add1155SRico Sonntag        $records = $this->topTenOldestAliveQuery('M', $total);
12838add1155SRico Sonntag
1284c0112ce8SGreg Roach        return view('statistics/individuals/top10-list', [
12858add1155SRico Sonntag            'records' => $records,
1286c0112ce8SGreg Roach        ]);
12878add1155SRico Sonntag    }
12888add1155SRico Sonntag
12898add1155SRico Sonntag    /**
12908add1155SRico Sonntag     * Find the average lifespan.
12918add1155SRico Sonntag     *
1292c0112ce8SGreg Roach     * @param string $sex        "M", "F" or "BOTH"
12938add1155SRico Sonntag     * @param bool   $show_years
12948add1155SRico Sonntag     *
12958add1155SRico Sonntag     * @return string
12968add1155SRico Sonntag     */
1297c0112ce8SGreg Roach    private function averageLifespanQuery(string $sex, bool $show_years): string
12988add1155SRico Sonntag    {
129944cdc21eSGreg Roach        $prefix = DB::connection()->getTablePrefix();
13008add1155SRico Sonntag
130144cdc21eSGreg Roach        $days = (int) $this->birthAndDeathQuery($sex)
1302a69f5655SGreg Roach            ->select(new Expression('AVG(' . $prefix . 'death.d_julianday2 - ' . $prefix . 'birth.d_julianday1) AS days'))
130344cdc21eSGreg Roach            ->value('days');
13048add1155SRico Sonntag
13058add1155SRico Sonntag        if ($show_years) {
130644cdc21eSGreg Roach            return $this->calculateAge($days);
13078add1155SRico Sonntag        }
13088add1155SRico Sonntag
130944cdc21eSGreg Roach        return I18N::number((int) ($days / 365.25));
13108add1155SRico Sonntag    }
13118add1155SRico Sonntag
13128add1155SRico Sonntag    /**
13138add1155SRico Sonntag     * Find the average lifespan.
13148add1155SRico Sonntag     *
13158add1155SRico Sonntag     * @param bool $show_years
13168add1155SRico Sonntag     *
13178add1155SRico Sonntag     * @return string
13188add1155SRico Sonntag     */
131973d58381SGreg Roach    public function averageLifespan(bool $show_years): string
13208add1155SRico Sonntag    {
13218add1155SRico Sonntag        return $this->averageLifespanQuery('BOTH', $show_years);
13228add1155SRico Sonntag    }
13238add1155SRico Sonntag
13248add1155SRico Sonntag    /**
13258add1155SRico Sonntag     * Find the average lifespan of females.
13268add1155SRico Sonntag     *
13278add1155SRico Sonntag     * @param bool $show_years
13288add1155SRico Sonntag     *
13298add1155SRico Sonntag     * @return string
13308add1155SRico Sonntag     */
133173d58381SGreg Roach    public function averageLifespanFemale(bool $show_years): string
13328add1155SRico Sonntag    {
13338add1155SRico Sonntag        return $this->averageLifespanQuery('F', $show_years);
13348add1155SRico Sonntag    }
13358add1155SRico Sonntag
13368add1155SRico Sonntag    /**
13378add1155SRico Sonntag     * Find the average male lifespan.
13388add1155SRico Sonntag     *
13398add1155SRico Sonntag     * @param bool $show_years
13408add1155SRico Sonntag     *
13418add1155SRico Sonntag     * @return string
13428add1155SRico Sonntag     */
134373d58381SGreg Roach    public function averageLifespanMale(bool $show_years): string
13448add1155SRico Sonntag    {
13458add1155SRico Sonntag        return $this->averageLifespanQuery('M', $show_years);
13468add1155SRico Sonntag    }
13478add1155SRico Sonntag
13488add1155SRico Sonntag    /**
13498add1155SRico Sonntag     * Convert totals into percentages.
13508add1155SRico Sonntag     *
13518add1155SRico Sonntag     * @param int $count
13528add1155SRico Sonntag     * @param int $total
13538add1155SRico Sonntag     *
13548add1155SRico Sonntag     * @return string
13558add1155SRico Sonntag     */
13568add1155SRico Sonntag    private function getPercentage(int $count, int $total): string
13578add1155SRico Sonntag    {
1358f78da678SGreg Roach        return $total !== 0 ? I18N::percentage($count / $total, 1) : '';
13598add1155SRico Sonntag    }
13608add1155SRico Sonntag
13618add1155SRico Sonntag    /**
13628add1155SRico Sonntag     * Returns how many individuals exist in the tree.
13638add1155SRico Sonntag     *
13648add1155SRico Sonntag     * @return int
13658add1155SRico Sonntag     */
13668add1155SRico Sonntag    private function totalIndividualsQuery(): int
13678add1155SRico Sonntag    {
13688add1155SRico Sonntag        return DB::table('individuals')
13698add1155SRico Sonntag            ->where('i_file', '=', $this->tree->id())
13708add1155SRico Sonntag            ->count();
13718add1155SRico Sonntag    }
13728add1155SRico Sonntag
13738add1155SRico Sonntag    /**
13748add1155SRico Sonntag     * Count the number of living individuals.
13758add1155SRico Sonntag     *
13768add1155SRico Sonntag     * The totalLiving/totalDeceased queries assume that every dead person will
13778add1155SRico Sonntag     * have a DEAT record. It will not include individuals who were born more
13788add1155SRico Sonntag     * than MAX_ALIVE_AGE years ago, and who have no DEAT record.
13798add1155SRico Sonntag     * A good reason to run the “Add missing DEAT records” batch-update!
13808add1155SRico Sonntag     *
13818add1155SRico Sonntag     * @return int
13828add1155SRico Sonntag     */
13838add1155SRico Sonntag    private function totalLivingQuery(): int
13848add1155SRico Sonntag    {
13853dc8167dSGreg Roach        $query = DB::table('individuals')
13863dc8167dSGreg Roach            ->where('i_file', '=', $this->tree->id());
13873dc8167dSGreg Roach
13883dc8167dSGreg Roach        foreach (Gedcom::DEATH_EVENTS as $death_event) {
138968d9d7c9SRico Sonntag            $query->where('i_gedcom', 'NOT LIKE', "%\n1 " . $death_event . '%');
13903dc8167dSGreg Roach        }
13913dc8167dSGreg Roach
13923dc8167dSGreg Roach        return $query->count();
13938add1155SRico Sonntag    }
13948add1155SRico Sonntag
13958add1155SRico Sonntag    /**
13968add1155SRico Sonntag     * Count the number of dead individuals.
13978add1155SRico Sonntag     *
13988add1155SRico Sonntag     * @return int
13998add1155SRico Sonntag     */
14008add1155SRico Sonntag    private function totalDeceasedQuery(): int
14018add1155SRico Sonntag    {
14028add1155SRico Sonntag        return DB::table('individuals')
14038add1155SRico Sonntag            ->where('i_file', '=', $this->tree->id())
14040b5fd0a6SGreg Roach            ->where(static function (Builder $query): void {
14053dc8167dSGreg Roach                foreach (Gedcom::DEATH_EVENTS as $death_event) {
140668d9d7c9SRico Sonntag                    $query->orWhere('i_gedcom', 'LIKE', "%\n1 " . $death_event . '%');
14073dc8167dSGreg Roach                }
14083dc8167dSGreg Roach            })
14098add1155SRico Sonntag            ->count();
14108add1155SRico Sonntag    }
14118add1155SRico Sonntag
14128add1155SRico Sonntag    /**
14138add1155SRico Sonntag     * Returns the total count of a specific sex.
14148add1155SRico Sonntag     *
14158add1155SRico Sonntag     * @param string $sex The sex to query
14168add1155SRico Sonntag     *
14178add1155SRico Sonntag     * @return int
14188add1155SRico Sonntag     */
14198add1155SRico Sonntag    private function getTotalSexQuery(string $sex): int
14208add1155SRico Sonntag    {
14218add1155SRico Sonntag        return DB::table('individuals')
14228add1155SRico Sonntag            ->where('i_file', '=', $this->tree->id())
14238add1155SRico Sonntag            ->where('i_sex', '=', $sex)
14248add1155SRico Sonntag            ->count();
14258add1155SRico Sonntag    }
14268add1155SRico Sonntag
14278add1155SRico Sonntag    /**
14288add1155SRico Sonntag     * Returns the total number of males.
14298add1155SRico Sonntag     *
14308add1155SRico Sonntag     * @return int
14318add1155SRico Sonntag     */
14328add1155SRico Sonntag    private function totalSexMalesQuery(): int
14338add1155SRico Sonntag    {
14348add1155SRico Sonntag        return $this->getTotalSexQuery('M');
14358add1155SRico Sonntag    }
14368add1155SRico Sonntag
14378add1155SRico Sonntag    /**
14388add1155SRico Sonntag     * Returns the total number of females.
14398add1155SRico Sonntag     *
14408add1155SRico Sonntag     * @return int
14418add1155SRico Sonntag     */
14428add1155SRico Sonntag    private function totalSexFemalesQuery(): int
14438add1155SRico Sonntag    {
14448add1155SRico Sonntag        return $this->getTotalSexQuery('F');
14458add1155SRico Sonntag    }
14468add1155SRico Sonntag
14478add1155SRico Sonntag    /**
14488add1155SRico Sonntag     * Returns the total number of individuals with unknown sex.
14498add1155SRico Sonntag     *
14508add1155SRico Sonntag     * @return int
14518add1155SRico Sonntag     */
14528add1155SRico Sonntag    private function totalSexUnknownQuery(): int
14538add1155SRico Sonntag    {
14548add1155SRico Sonntag        return $this->getTotalSexQuery('U');
14558add1155SRico Sonntag    }
14568add1155SRico Sonntag
14578add1155SRico Sonntag    /**
14588add1155SRico Sonntag     * Count the total families.
14598add1155SRico Sonntag     *
14608add1155SRico Sonntag     * @return int
14618add1155SRico Sonntag     */
14628add1155SRico Sonntag    private function totalFamiliesQuery(): int
14638add1155SRico Sonntag    {
14648add1155SRico Sonntag        return DB::table('families')
14658add1155SRico Sonntag            ->where('f_file', '=', $this->tree->id())
14668add1155SRico Sonntag            ->count();
14678add1155SRico Sonntag    }
14688add1155SRico Sonntag
14698add1155SRico Sonntag    /**
14708add1155SRico Sonntag     * How many individuals have one or more sources.
14718add1155SRico Sonntag     *
14728add1155SRico Sonntag     * @return int
14738add1155SRico Sonntag     */
14748add1155SRico Sonntag    private function totalIndisWithSourcesQuery(): int
14758add1155SRico Sonntag    {
14768add1155SRico Sonntag        return DB::table('individuals')
14778add1155SRico Sonntag            ->select(['i_id'])
14788add1155SRico Sonntag            ->distinct()
14790b5fd0a6SGreg Roach            ->join('link', static function (JoinClause $join): void {
14808add1155SRico Sonntag                $join->on('i_id', '=', 'l_from')
14818add1155SRico Sonntag                    ->on('i_file', '=', 'l_file');
14828add1155SRico Sonntag            })
14838add1155SRico Sonntag            ->where('l_file', '=', $this->tree->id())
14848add1155SRico Sonntag            ->where('l_type', '=', 'SOUR')
14858add1155SRico Sonntag            ->count('i_id');
14868add1155SRico Sonntag    }
14878add1155SRico Sonntag
14888add1155SRico Sonntag    /**
14898add1155SRico Sonntag     * Count the families with source records.
14908add1155SRico Sonntag     *
14918add1155SRico Sonntag     * @return int
14928add1155SRico Sonntag     */
14938add1155SRico Sonntag    private function totalFamsWithSourcesQuery(): int
14948add1155SRico Sonntag    {
14958add1155SRico Sonntag        return DB::table('families')
14968add1155SRico Sonntag            ->select(['f_id'])
14978add1155SRico Sonntag            ->distinct()
14980b5fd0a6SGreg Roach            ->join('link', static function (JoinClause $join): void {
14998add1155SRico Sonntag                $join->on('f_id', '=', 'l_from')
15008add1155SRico Sonntag                    ->on('f_file', '=', 'l_file');
15018add1155SRico Sonntag            })
15028add1155SRico Sonntag            ->where('l_file', '=', $this->tree->id())
15038add1155SRico Sonntag            ->where('l_type', '=', 'SOUR')
15048add1155SRico Sonntag            ->count('f_id');
15058add1155SRico Sonntag    }
15068add1155SRico Sonntag
15078add1155SRico Sonntag    /**
15088add1155SRico Sonntag     * Count the number of repositories.
15098add1155SRico Sonntag     *
15108add1155SRico Sonntag     * @return int
15118add1155SRico Sonntag     */
15128add1155SRico Sonntag    private function totalRepositoriesQuery(): int
15138add1155SRico Sonntag    {
15148add1155SRico Sonntag        return DB::table('other')
15158add1155SRico Sonntag            ->where('o_file', '=', $this->tree->id())
15168add1155SRico Sonntag            ->where('o_type', '=', 'REPO')
15178add1155SRico Sonntag            ->count();
15188add1155SRico Sonntag    }
15198add1155SRico Sonntag
15208add1155SRico Sonntag    /**
15218add1155SRico Sonntag     * Count the total number of sources.
15228add1155SRico Sonntag     *
15238add1155SRico Sonntag     * @return int
15248add1155SRico Sonntag     */
15258add1155SRico Sonntag    private function totalSourcesQuery(): int
15268add1155SRico Sonntag    {
15278add1155SRico Sonntag        return DB::table('sources')
15288add1155SRico Sonntag            ->where('s_file', '=', $this->tree->id())
15298add1155SRico Sonntag            ->count();
15308add1155SRico Sonntag    }
15318add1155SRico Sonntag
15328add1155SRico Sonntag    /**
15338add1155SRico Sonntag     * Count the number of notes.
15348add1155SRico Sonntag     *
15358add1155SRico Sonntag     * @return int
15368add1155SRico Sonntag     */
15378add1155SRico Sonntag    private function totalNotesQuery(): int
15388add1155SRico Sonntag    {
15398add1155SRico Sonntag        return DB::table('other')
15408add1155SRico Sonntag            ->where('o_file', '=', $this->tree->id())
15418add1155SRico Sonntag            ->where('o_type', '=', 'NOTE')
15428add1155SRico Sonntag            ->count();
15438add1155SRico Sonntag    }
15448add1155SRico Sonntag
15458add1155SRico Sonntag    /**
15469969300fSBogie     * Count the total media.
15479969300fSBogie     *
15489969300fSBogie     * @return int
15499969300fSBogie     */
15509969300fSBogie    private function totalMediaQuery(): int
15519969300fSBogie    {
15529969300fSBogie        return DB::table('media')
15539969300fSBogie            ->where('m_file', '=', $this->tree->id())
15549969300fSBogie            ->count();
15559969300fSBogie    }
15569969300fSBogie
15579969300fSBogie    /**
15588add1155SRico Sonntag     * Returns the total number of records.
15598add1155SRico Sonntag     *
15608add1155SRico Sonntag     * @return int
15618add1155SRico Sonntag     */
15628add1155SRico Sonntag    private function totalRecordsQuery(): int
15638add1155SRico Sonntag    {
15648add1155SRico Sonntag        return $this->totalIndividualsQuery()
15658add1155SRico Sonntag            + $this->totalFamiliesQuery()
15669969300fSBogie            + $this->totalMediaQuery()
15678add1155SRico Sonntag            + $this->totalNotesQuery()
15688add1155SRico Sonntag            + $this->totalRepositoriesQuery()
15698add1155SRico Sonntag            + $this->totalSourcesQuery();
15708add1155SRico Sonntag    }
15718add1155SRico Sonntag
15728add1155SRico Sonntag    /**
15730dcd9387SGreg Roach     * @return string
15748add1155SRico Sonntag     */
15758add1155SRico Sonntag    public function totalRecords(): string
15768add1155SRico Sonntag    {
15778add1155SRico Sonntag        return I18N::number($this->totalRecordsQuery());
15788add1155SRico Sonntag    }
15798add1155SRico Sonntag
15808add1155SRico Sonntag    /**
15810dcd9387SGreg Roach     * @return string
15828add1155SRico Sonntag     */
15838add1155SRico Sonntag    public function totalIndividuals(): string
15848add1155SRico Sonntag    {
15858add1155SRico Sonntag        return I18N::number($this->totalIndividualsQuery());
15868add1155SRico Sonntag    }
15878add1155SRico Sonntag
15888add1155SRico Sonntag    /**
15898add1155SRico Sonntag     * Count the number of living individuals.
15908add1155SRico Sonntag     *
15918add1155SRico Sonntag     * @return string
15928add1155SRico Sonntag     */
15938add1155SRico Sonntag    public function totalLiving(): string
15948add1155SRico Sonntag    {
15958add1155SRico Sonntag        return I18N::number($this->totalLivingQuery());
15968add1155SRico Sonntag    }
15978add1155SRico Sonntag
15988add1155SRico Sonntag    /**
15998add1155SRico Sonntag     * Count the number of dead individuals.
16008add1155SRico Sonntag     *
16018add1155SRico Sonntag     * @return string
16028add1155SRico Sonntag     */
16038add1155SRico Sonntag    public function totalDeceased(): string
16048add1155SRico Sonntag    {
16058add1155SRico Sonntag        return I18N::number($this->totalDeceasedQuery());
16068add1155SRico Sonntag    }
16078add1155SRico Sonntag
16088add1155SRico Sonntag    /**
16090dcd9387SGreg Roach     * @return string
16108add1155SRico Sonntag     */
16118add1155SRico Sonntag    public function totalSexMales(): string
16128add1155SRico Sonntag    {
16138add1155SRico Sonntag        return I18N::number($this->totalSexMalesQuery());
16148add1155SRico Sonntag    }
16158add1155SRico Sonntag
16168add1155SRico Sonntag    /**
16170dcd9387SGreg Roach     * @return string
16188add1155SRico Sonntag     */
16198add1155SRico Sonntag    public function totalSexFemales(): string
16208add1155SRico Sonntag    {
16218add1155SRico Sonntag        return I18N::number($this->totalSexFemalesQuery());
16228add1155SRico Sonntag    }
16238add1155SRico Sonntag
16248add1155SRico Sonntag    /**
16250dcd9387SGreg Roach     * @return string
16268add1155SRico Sonntag     */
16278add1155SRico Sonntag    public function totalSexUnknown(): string
16288add1155SRico Sonntag    {
16298add1155SRico Sonntag        return I18N::number($this->totalSexUnknownQuery());
16308add1155SRico Sonntag    }
16318add1155SRico Sonntag
16328add1155SRico Sonntag    /**
16330dcd9387SGreg Roach     * @return string
16348add1155SRico Sonntag     */
16358add1155SRico Sonntag    public function totalFamilies(): string
16368add1155SRico Sonntag    {
16378add1155SRico Sonntag        return I18N::number($this->totalFamiliesQuery());
16388add1155SRico Sonntag    }
16398add1155SRico Sonntag
16408add1155SRico Sonntag    /**
16418add1155SRico Sonntag     * How many individuals have one or more sources.
16428add1155SRico Sonntag     *
16438add1155SRico Sonntag     * @return string
16448add1155SRico Sonntag     */
16458add1155SRico Sonntag    public function totalIndisWithSources(): string
16468add1155SRico Sonntag    {
16478add1155SRico Sonntag        return I18N::number($this->totalIndisWithSourcesQuery());
16488add1155SRico Sonntag    }
16498add1155SRico Sonntag
16508add1155SRico Sonntag    /**
16518add1155SRico Sonntag     * Count the families with with source records.
16528add1155SRico Sonntag     *
16538add1155SRico Sonntag     * @return string
16548add1155SRico Sonntag     */
16558add1155SRico Sonntag    public function totalFamsWithSources(): string
16568add1155SRico Sonntag    {
16578add1155SRico Sonntag        return I18N::number($this->totalFamsWithSourcesQuery());
16588add1155SRico Sonntag    }
16598add1155SRico Sonntag
16608add1155SRico Sonntag    /**
16610dcd9387SGreg Roach     * @return string
16628add1155SRico Sonntag     */
16638add1155SRico Sonntag    public function totalRepositories(): string
16648add1155SRico Sonntag    {
16658add1155SRico Sonntag        return I18N::number($this->totalRepositoriesQuery());
16668add1155SRico Sonntag    }
16678add1155SRico Sonntag
16688add1155SRico Sonntag    /**
16690dcd9387SGreg Roach     * @return string
16708add1155SRico Sonntag     */
16718add1155SRico Sonntag    public function totalSources(): string
16728add1155SRico Sonntag    {
16738add1155SRico Sonntag        return I18N::number($this->totalSourcesQuery());
16748add1155SRico Sonntag    }
16758add1155SRico Sonntag
16768add1155SRico Sonntag    /**
16770dcd9387SGreg Roach     * @return string
16788add1155SRico Sonntag     */
16798add1155SRico Sonntag    public function totalNotes(): string
16808add1155SRico Sonntag    {
16818add1155SRico Sonntag        return I18N::number($this->totalNotesQuery());
16828add1155SRico Sonntag    }
16838add1155SRico Sonntag
16848add1155SRico Sonntag    /**
16850dcd9387SGreg Roach     * @return string
16868add1155SRico Sonntag     */
16878add1155SRico Sonntag    public function totalIndividualsPercentage(): string
16888add1155SRico Sonntag    {
16898add1155SRico Sonntag        return $this->getPercentage(
16908add1155SRico Sonntag            $this->totalIndividualsQuery(),
16918add1155SRico Sonntag            $this->totalRecordsQuery()
16928add1155SRico Sonntag        );
16938add1155SRico Sonntag    }
16948add1155SRico Sonntag
16958add1155SRico Sonntag    /**
16960dcd9387SGreg Roach     * @return string
16978add1155SRico Sonntag     */
1698*baa78b21STheDutchJewel    public function totalIndisWithSourcesPercentage(): string
1699*baa78b21STheDutchJewel    {
1700*baa78b21STheDutchJewel        return $this->getPercentage(
1701*baa78b21STheDutchJewel            $this->totalIndisWithSourcesQuery(),
1702*baa78b21STheDutchJewel            $this->totalIndividualsQuery()
1703*baa78b21STheDutchJewel        );
1704*baa78b21STheDutchJewel    }
1705*baa78b21STheDutchJewel
1706*baa78b21STheDutchJewel    /**
1707*baa78b21STheDutchJewel     * @return string
1708*baa78b21STheDutchJewel     */
17098add1155SRico Sonntag    public function totalFamiliesPercentage(): string
17108add1155SRico Sonntag    {
17118add1155SRico Sonntag        return $this->getPercentage(
17128add1155SRico Sonntag            $this->totalFamiliesQuery(),
17138add1155SRico Sonntag            $this->totalRecordsQuery()
17148add1155SRico Sonntag        );
17158add1155SRico Sonntag    }
17168add1155SRico Sonntag
17178add1155SRico Sonntag    /**
17180dcd9387SGreg Roach     * @return string
17198add1155SRico Sonntag     */
1720*baa78b21STheDutchJewel    public function totalFamsWithSourcesPercentage(): string
1721*baa78b21STheDutchJewel    {
1722*baa78b21STheDutchJewel        return $this->getPercentage(
1723*baa78b21STheDutchJewel            $this->totalFamsWithSourcesQuery(),
1724*baa78b21STheDutchJewel            $this->totalFamiliesQuery()
1725*baa78b21STheDutchJewel        );
1726*baa78b21STheDutchJewel    }
1727*baa78b21STheDutchJewel
1728*baa78b21STheDutchJewel    /**
1729*baa78b21STheDutchJewel     * @return string
1730*baa78b21STheDutchJewel     */
17318add1155SRico Sonntag    public function totalRepositoriesPercentage(): string
17328add1155SRico Sonntag    {
17338add1155SRico Sonntag        return $this->getPercentage(
17348add1155SRico Sonntag            $this->totalRepositoriesQuery(),
17358add1155SRico Sonntag            $this->totalRecordsQuery()
17368add1155SRico Sonntag        );
17378add1155SRico Sonntag    }
17388add1155SRico Sonntag
17398add1155SRico Sonntag    /**
17400dcd9387SGreg Roach     * @return string
17418add1155SRico Sonntag     */
17428add1155SRico Sonntag    public function totalSourcesPercentage(): string
17438add1155SRico Sonntag    {
17448add1155SRico Sonntag        return $this->getPercentage(
17458add1155SRico Sonntag            $this->totalSourcesQuery(),
17468add1155SRico Sonntag            $this->totalRecordsQuery()
17478add1155SRico Sonntag        );
17488add1155SRico Sonntag    }
17498add1155SRico Sonntag
17508add1155SRico Sonntag    /**
17510dcd9387SGreg Roach     * @return string
17528add1155SRico Sonntag     */
17538add1155SRico Sonntag    public function totalNotesPercentage(): string
17548add1155SRico Sonntag    {
17558add1155SRico Sonntag        return $this->getPercentage(
17568add1155SRico Sonntag            $this->totalNotesQuery(),
17578add1155SRico Sonntag            $this->totalRecordsQuery()
17588add1155SRico Sonntag        );
17598add1155SRico Sonntag    }
17608add1155SRico Sonntag
17618add1155SRico Sonntag    /**
17620dcd9387SGreg Roach     * @return string
17638add1155SRico Sonntag     */
17648add1155SRico Sonntag    public function totalLivingPercentage(): string
17658add1155SRico Sonntag    {
17668add1155SRico Sonntag        return $this->getPercentage(
17678add1155SRico Sonntag            $this->totalLivingQuery(),
17688add1155SRico Sonntag            $this->totalIndividualsQuery()
17698add1155SRico Sonntag        );
17708add1155SRico Sonntag    }
17718add1155SRico Sonntag
17728add1155SRico Sonntag    /**
17730dcd9387SGreg Roach     * @return string
17748add1155SRico Sonntag     */
17758add1155SRico Sonntag    public function totalDeceasedPercentage(): string
17768add1155SRico Sonntag    {
17778add1155SRico Sonntag        return $this->getPercentage(
17788add1155SRico Sonntag            $this->totalDeceasedQuery(),
17798add1155SRico Sonntag            $this->totalIndividualsQuery()
17808add1155SRico Sonntag        );
17818add1155SRico Sonntag    }
17828add1155SRico Sonntag
17838add1155SRico Sonntag    /**
17840dcd9387SGreg Roach     * @return string
17858add1155SRico Sonntag     */
17868add1155SRico Sonntag    public function totalSexMalesPercentage(): string
17878add1155SRico Sonntag    {
17888add1155SRico Sonntag        return $this->getPercentage(
17898add1155SRico Sonntag            $this->totalSexMalesQuery(),
17908add1155SRico Sonntag            $this->totalIndividualsQuery()
17918add1155SRico Sonntag        );
17928add1155SRico Sonntag    }
17938add1155SRico Sonntag
17948add1155SRico Sonntag    /**
17950dcd9387SGreg Roach     * @return string
17968add1155SRico Sonntag     */
17978add1155SRico Sonntag    public function totalSexFemalesPercentage(): string
17988add1155SRico Sonntag    {
17998add1155SRico Sonntag        return $this->getPercentage(
18008add1155SRico Sonntag            $this->totalSexFemalesQuery(),
18018add1155SRico Sonntag            $this->totalIndividualsQuery()
18028add1155SRico Sonntag        );
18038add1155SRico Sonntag    }
18048add1155SRico Sonntag
18058add1155SRico Sonntag    /**
18060dcd9387SGreg Roach     * @return string
18078add1155SRico Sonntag     */
18088add1155SRico Sonntag    public function totalSexUnknownPercentage(): string
18098add1155SRico Sonntag    {
18108add1155SRico Sonntag        return $this->getPercentage(
18118add1155SRico Sonntag            $this->totalSexUnknownQuery(),
18128add1155SRico Sonntag            $this->totalIndividualsQuery()
18138add1155SRico Sonntag        );
18148add1155SRico Sonntag    }
18158add1155SRico Sonntag
18168add1155SRico Sonntag    /**
18178add1155SRico Sonntag     * Create a chart of common given names.
18188add1155SRico Sonntag     *
18198add1155SRico Sonntag     * @param string|null $color_from
18208add1155SRico Sonntag     * @param string|null $color_to
18218add1155SRico Sonntag     * @param int         $maxtoshow
18228add1155SRico Sonntag     *
18238add1155SRico Sonntag     * @return string
18248add1155SRico Sonntag     */
18258add1155SRico Sonntag    public function chartCommonGiven(
18268add1155SRico Sonntag        string $color_from = null,
18278add1155SRico Sonntag        string $color_to = null,
18288add1155SRico Sonntag        int $maxtoshow = 7
18298add1155SRico Sonntag    ): string {
18308add1155SRico Sonntag        $tot_indi = $this->totalIndividualsQuery();
18318add1155SRico Sonntag        $given    = $this->commonGivenQuery('B', 'chart', false, 1, $maxtoshow);
18328add1155SRico Sonntag
1833320f6a24SGreg Roach        if ($given === []) {
1834dd7dd2a1SRico Sonntag            return I18N::translate('This information is not available.');
183588de55fdSRico Sonntag        }
183688de55fdSRico Sonntag
1837f78da678SGreg Roach        return (new ChartCommonGiven($this->color_service))
183888de55fdSRico Sonntag            ->chartCommonGiven($tot_indi, $given, $color_from, $color_to);
18398add1155SRico Sonntag    }
18408add1155SRico Sonntag
18418add1155SRico Sonntag    /**
18428add1155SRico Sonntag     * Create a chart of common surnames.
18438add1155SRico Sonntag     *
18448add1155SRico Sonntag     * @param string|null $color_from
18458add1155SRico Sonntag     * @param string|null $color_to
18468add1155SRico Sonntag     * @param int         $number_of_surnames
18478add1155SRico Sonntag     *
18488add1155SRico Sonntag     * @return string
18498add1155SRico Sonntag     */
18508add1155SRico Sonntag    public function chartCommonSurnames(
18518add1155SRico Sonntag        string $color_from = null,
18528add1155SRico Sonntag        string $color_to = null,
18538add1155SRico Sonntag        int $number_of_surnames = 10
18548add1155SRico Sonntag    ): string {
18558add1155SRico Sonntag        $tot_indi     = $this->totalIndividualsQuery();
18568add1155SRico Sonntag        $all_surnames = $this->topSurnames($number_of_surnames, 0);
18578add1155SRico Sonntag
1858320f6a24SGreg Roach        if ($all_surnames === []) {
1859dd7dd2a1SRico Sonntag            return I18N::translate('This information is not available.');
186088de55fdSRico Sonntag        }
186188de55fdSRico Sonntag
18627e128bbfSGreg Roach        $surname_tradition = Registry::surnameTraditionFactory()
18637e128bbfSGreg Roach            ->make($this->tree->getPreference('SURNAME_TRADITION'));
1864f78da678SGreg Roach
1865f78da678SGreg Roach        return (new ChartCommonSurname($this->color_service, $surname_tradition))
186688de55fdSRico Sonntag            ->chartCommonSurnames($tot_indi, $all_surnames, $color_from, $color_to);
18678add1155SRico Sonntag    }
18688add1155SRico Sonntag
18698add1155SRico Sonntag    /**
18708add1155SRico Sonntag     * Create a chart showing mortality.
18718add1155SRico Sonntag     *
18728add1155SRico Sonntag     * @param string|null $color_living
18738add1155SRico Sonntag     * @param string|null $color_dead
18748add1155SRico Sonntag     *
18758add1155SRico Sonntag     * @return string
18768add1155SRico Sonntag     */
187788de55fdSRico Sonntag    public function chartMortality(string $color_living = null, string $color_dead = null): string
18788add1155SRico Sonntag    {
18798add1155SRico Sonntag        $tot_l = $this->totalLivingQuery();
18808add1155SRico Sonntag        $tot_d = $this->totalDeceasedQuery();
18818add1155SRico Sonntag
1882f78da678SGreg Roach        return (new ChartMortality($this->color_service))
188388de55fdSRico Sonntag            ->chartMortality($tot_l, $tot_d, $color_living, $color_dead);
18848add1155SRico Sonntag    }
18858add1155SRico Sonntag
18868add1155SRico Sonntag    /**
18878add1155SRico Sonntag     * Create a chart showing individuals with/without sources.
18888add1155SRico Sonntag     *
18898add1155SRico Sonntag     * @param string|null $color_from
18908add1155SRico Sonntag     * @param string|null $color_to
18918add1155SRico Sonntag     *
18928add1155SRico Sonntag     * @return string
18938add1155SRico Sonntag     */
18948add1155SRico Sonntag    public function chartIndisWithSources(
18958add1155SRico Sonntag        string $color_from = null,
18968add1155SRico Sonntag        string $color_to = null
18978add1155SRico Sonntag    ): string {
18988add1155SRico Sonntag        $tot_indi        = $this->totalIndividualsQuery();
18998add1155SRico Sonntag        $tot_indi_source = $this->totalIndisWithSourcesQuery();
19008add1155SRico Sonntag
1901f78da678SGreg Roach        return (new ChartIndividualWithSources($this->color_service))
190288de55fdSRico Sonntag            ->chartIndisWithSources($tot_indi, $tot_indi_source, $color_from, $color_to);
19038add1155SRico Sonntag    }
19048add1155SRico Sonntag
19058add1155SRico Sonntag    /**
19068add1155SRico Sonntag     * Create a chart of individuals with/without sources.
19078add1155SRico Sonntag     *
19088add1155SRico Sonntag     * @param string|null $color_from
19098add1155SRico Sonntag     * @param string|null $color_to
19108add1155SRico Sonntag     *
19118add1155SRico Sonntag     * @return string
19128add1155SRico Sonntag     */
19138add1155SRico Sonntag    public function chartFamsWithSources(
19148add1155SRico Sonntag        string $color_from = null,
19158add1155SRico Sonntag        string $color_to = null
19168add1155SRico Sonntag    ): string {
19178add1155SRico Sonntag        $tot_fam        = $this->totalFamiliesQuery();
19188add1155SRico Sonntag        $tot_fam_source = $this->totalFamsWithSourcesQuery();
19198add1155SRico Sonntag
1920f78da678SGreg Roach        return (new ChartFamilyWithSources($this->color_service))
192188de55fdSRico Sonntag            ->chartFamsWithSources($tot_fam, $tot_fam_source, $color_from, $color_to);
19228add1155SRico Sonntag    }
19238add1155SRico Sonntag
19248add1155SRico Sonntag    /**
19250dcd9387SGreg Roach     * @param string|null $color_female
19260dcd9387SGreg Roach     * @param string|null $color_male
19270dcd9387SGreg Roach     * @param string|null $color_unknown
19280dcd9387SGreg Roach     *
19290dcd9387SGreg Roach     * @return string
19308add1155SRico Sonntag     */
19318add1155SRico Sonntag    public function chartSex(
19328add1155SRico Sonntag        string $color_female = null,
19338add1155SRico Sonntag        string $color_male = null,
19348add1155SRico Sonntag        string $color_unknown = null
19358add1155SRico Sonntag    ): string {
19368add1155SRico Sonntag        $tot_m = $this->totalSexMalesQuery();
19378add1155SRico Sonntag        $tot_f = $this->totalSexFemalesQuery();
19388add1155SRico Sonntag        $tot_u = $this->totalSexUnknownQuery();
19398add1155SRico Sonntag
194093ccd686SRico Sonntag        return (new ChartSex())
194188de55fdSRico Sonntag            ->chartSex($tot_m, $tot_f, $tot_u, $color_female, $color_male, $color_unknown);
19428add1155SRico Sonntag    }
194344cdc21eSGreg Roach
194444cdc21eSGreg Roach    /**
194544cdc21eSGreg Roach     * Query individuals, with their births and deaths.
194644cdc21eSGreg Roach     *
194744cdc21eSGreg Roach     * @param string $sex
194844cdc21eSGreg Roach     *
194944cdc21eSGreg Roach     * @return Builder
195044cdc21eSGreg Roach     */
1951e2cbf57aSGreg Roach    private function birthAndDeathQuery(string $sex): Builder
1952e2cbf57aSGreg Roach    {
195344cdc21eSGreg Roach        $query = DB::table('individuals')
195444cdc21eSGreg Roach            ->where('i_file', '=', $this->tree->id())
19550b5fd0a6SGreg Roach            ->join('dates AS birth', static function (JoinClause $join): void {
195644cdc21eSGreg Roach                $join
195744cdc21eSGreg Roach                    ->on('birth.d_file', '=', 'i_file')
195844cdc21eSGreg Roach                    ->on('birth.d_gid', '=', 'i_id');
195944cdc21eSGreg Roach            })
19600b5fd0a6SGreg Roach            ->join('dates AS death', static function (JoinClause $join): void {
196144cdc21eSGreg Roach                $join
196244cdc21eSGreg Roach                    ->on('death.d_file', '=', 'i_file')
196344cdc21eSGreg Roach                    ->on('death.d_gid', '=', 'i_id');
196444cdc21eSGreg Roach            })
196544cdc21eSGreg Roach            ->where('birth.d_fact', '=', 'BIRT')
196644cdc21eSGreg Roach            ->where('death.d_fact', '=', 'DEAT')
196744cdc21eSGreg Roach            ->whereColumn('death.d_julianday1', '>=', 'birth.d_julianday2')
196844cdc21eSGreg Roach            ->where('birth.d_julianday2', '<>', 0);
196944cdc21eSGreg Roach
197044cdc21eSGreg Roach        if ($sex === 'M' || $sex === 'F') {
197144cdc21eSGreg Roach            $query->where('i_sex', '=', $sex);
197244cdc21eSGreg Roach        }
197344cdc21eSGreg Roach
197444cdc21eSGreg Roach        return $query;
197544cdc21eSGreg Roach    }
19768add1155SRico Sonntag}
1977