xref: /webtrees/app/Statistics/Repository/IndividualRepository.php (revision 09482a558a7989d76059e7f9911605cf836b77ba)
18add1155SRico Sonntag<?php
23976b470SGreg Roach
38add1155SRico Sonntag/**
48add1155SRico Sonntag * webtrees: online genealogy
52da2e0a6SGreg Roach * Copyright (C) 2021 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;
234459dc9aSGreg Roachuse Fisharebest\Webtrees\Carbon;
246b9cb339SGreg Roachuse Fisharebest\Webtrees\Registry;
258add1155SRico Sonntaguse Fisharebest\Webtrees\Functions\FunctionsPrintLists;
268add1155SRico Sonntaguse Fisharebest\Webtrees\Gedcom;
27d1a467e4SGreg Roachuse Fisharebest\Webtrees\GedcomRecord;
288add1155SRico Sonntaguse Fisharebest\Webtrees\I18N;
298add1155SRico Sonntaguse Fisharebest\Webtrees\Individual;
3067992b6aSRichard Cisseeuse Fisharebest\Webtrees\Module\IndividualListModule;
3167992b6aSRichard Cisseeuse Fisharebest\Webtrees\Module\ModuleInterface;
3287cca37cSGreg Roachuse Fisharebest\Webtrees\Module\ModuleListInterface;
3367992b6aSRichard Cisseeuse Fisharebest\Webtrees\Services\ModuleService;
348add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartAge;
358add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartBirth;
368add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartCommonGiven;
378add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartCommonSurname;
388add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartDeath;
398add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartFamilyWithSources;
4088de55fdSRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartIndividualWithSources;
418add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartMortality;
428add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartSex;
438add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Repository\Interfaces\IndividualRepositoryInterface;
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;
4991c84b80SGreg Roachuse stdClass;
5091c84b80SGreg Roach
5171378461SGreg Roachuse function array_key_exists;
5271378461SGreg Roachuse function array_slice;
5371378461SGreg Roach
548add1155SRico Sonntag/**
558add1155SRico Sonntag *
568add1155SRico Sonntag */
578add1155SRico Sonntagclass IndividualRepository implements IndividualRepositoryInterface
588add1155SRico Sonntag{
598add1155SRico Sonntag    /**
608add1155SRico Sonntag     * @var Tree
618add1155SRico Sonntag     */
628add1155SRico Sonntag    private $tree;
638add1155SRico Sonntag
648add1155SRico Sonntag    /**
658add1155SRico Sonntag     * Constructor.
668add1155SRico Sonntag     *
678add1155SRico Sonntag     * @param Tree $tree
688add1155SRico Sonntag     */
698add1155SRico Sonntag    public function __construct(Tree $tree)
708add1155SRico Sonntag    {
718add1155SRico Sonntag        $this->tree = $tree;
728add1155SRico Sonntag    }
738add1155SRico Sonntag
748add1155SRico Sonntag    /**
758add1155SRico Sonntag     * Find common given names.
768add1155SRico Sonntag     *
778add1155SRico Sonntag     * @param string $sex
788add1155SRico Sonntag     * @param string $type
798add1155SRico Sonntag     * @param bool   $show_tot
808add1155SRico Sonntag     * @param int    $threshold
818add1155SRico Sonntag     * @param int    $maxtoshow
828add1155SRico Sonntag     *
83*09482a55SGreg Roach     * @return string|array<int>
848add1155SRico Sonntag     */
858add1155SRico Sonntag    private function commonGivenQuery(string $sex, string $type, bool $show_tot, int $threshold, int $maxtoshow)
868add1155SRico Sonntag    {
87d1a467e4SGreg Roach        $query = DB::table('name')
880b5fd0a6SGreg Roach            ->join('individuals', static function (JoinClause $join): void {
89d1a467e4SGreg Roach                $join
90d1a467e4SGreg Roach                    ->on('i_file', '=', 'n_file')
91d1a467e4SGreg Roach                    ->on('i_id', '=', 'n_id');
92d1a467e4SGreg Roach            })
93d1a467e4SGreg Roach            ->where('n_file', '=', $this->tree->id())
94d1a467e4SGreg Roach            ->where('n_type', '<>', '_MARNM')
958fb4e87cSGreg Roach            ->where('n_givn', '<>', Individual::PRAENOMEN_NESCIO)
96a69f5655SGreg Roach            ->where(new Expression('LENGTH(n_givn)'), '>', 1);
97d1a467e4SGreg Roach
988add1155SRico Sonntag        switch ($sex) {
998add1155SRico Sonntag            case 'M':
1008add1155SRico Sonntag            case 'F':
1018add1155SRico Sonntag            case 'U':
102d1a467e4SGreg Roach                $query->where('i_sex', '=', $sex);
1038add1155SRico Sonntag                break;
104d1a467e4SGreg Roach
1058add1155SRico Sonntag            case 'B':
1068add1155SRico Sonntag            default:
107d1a467e4SGreg Roach                $query->where('i_sex', '<>', 'U');
1088add1155SRico Sonntag                break;
1098add1155SRico Sonntag        }
1108add1155SRico Sonntag
111d1a467e4SGreg Roach        $rows = $query
1123413ec75SRico Sonntag            ->groupBy(['n_givn'])
113a69f5655SGreg Roach            ->select(['n_givn', new Expression('COUNT(distinct n_id) AS count')])
114d1a467e4SGreg Roach            ->pluck('count', 'n_givn');
1158add1155SRico Sonntag
1168add1155SRico Sonntag        $nameList = [];
1178add1155SRico Sonntag
118d1a467e4SGreg Roach        foreach ($rows as $n_givn => $count) {
1198add1155SRico Sonntag            // Split “John Thomas” into “John” and “Thomas” and count against both totals
12021f5b67aSGreg Roach            foreach (explode(' ', (string) $n_givn) as $given) {
1218add1155SRico Sonntag                // Exclude initials and particles.
1228add1155SRico Sonntag                if (!preg_match('/^([A-Z]|[a-z]{1,3})$/', $given)) {
1236ccdf4f0SGreg Roach                    if (array_key_exists($given, $nameList)) {
124d1a467e4SGreg Roach                        $nameList[$given] += (int) $count;
1258add1155SRico Sonntag                    } else {
126d1a467e4SGreg Roach                        $nameList[$given] = (int) $count;
1278add1155SRico Sonntag                    }
1288add1155SRico Sonntag                }
1298add1155SRico Sonntag            }
1308add1155SRico Sonntag        }
1318add1155SRico Sonntag        arsort($nameList);
1326ccdf4f0SGreg Roach        $nameList = array_slice($nameList, 0, $maxtoshow);
1338add1155SRico Sonntag
1348add1155SRico Sonntag        foreach ($nameList as $given => $total) {
1358add1155SRico Sonntag            if ($total < $threshold) {
1368add1155SRico Sonntag                unset($nameList[$given]);
1378add1155SRico Sonntag            }
1388add1155SRico Sonntag        }
1398add1155SRico Sonntag
1408add1155SRico Sonntag        switch ($type) {
1418add1155SRico Sonntag            case 'chart':
1428add1155SRico Sonntag                return $nameList;
1438add1155SRico Sonntag
1448add1155SRico Sonntag            case 'table':
1458add1155SRico Sonntag                return view('lists/given-names-table', [
1468add1155SRico Sonntag                    'given_names' => $nameList,
1478add1155SRico Sonntag                ]);
1488add1155SRico Sonntag
1498add1155SRico Sonntag            case 'list':
1508add1155SRico Sonntag                return view('lists/given-names-list', [
1518add1155SRico Sonntag                    'given_names' => $nameList,
1528add1155SRico Sonntag                    'show_totals' => $show_tot,
1538add1155SRico Sonntag                ]);
1548add1155SRico Sonntag
1558add1155SRico Sonntag            case 'nolist':
1568add1155SRico Sonntag            default:
1570b5fd0a6SGreg Roach                array_walk($nameList, static function (string &$value, string $key) use ($show_tot): void {
1588add1155SRico Sonntag                    if ($show_tot) {
159315eb316SGreg Roach                        $value = '<bdi>' . e($key) . '</bdi> (' . I18N::number((int) $value) . ')';
1602da2e0a6SGreg Roach                    } else {
161315eb316SGreg Roach                        $value = '<bdi>' . e($key) . '</bdi>';
1628add1155SRico Sonntag                    }
1638add1155SRico Sonntag                });
1648add1155SRico Sonntag
1658add1155SRico Sonntag                return implode(I18N::$list_separator, $nameList);
1668add1155SRico Sonntag        }
1678add1155SRico Sonntag    }
1688add1155SRico Sonntag
1698add1155SRico Sonntag    /**
1708add1155SRico Sonntag     * Find common give names.
1718add1155SRico Sonntag     *
1728add1155SRico Sonntag     * @param int $threshold
1738add1155SRico Sonntag     * @param int $maxtoshow
1748add1155SRico Sonntag     *
1758add1155SRico Sonntag     * @return string
1768add1155SRico Sonntag     */
1778add1155SRico Sonntag    public function commonGiven(int $threshold = 1, int $maxtoshow = 10): string
1788add1155SRico Sonntag    {
1798add1155SRico Sonntag        return $this->commonGivenQuery('B', 'nolist', false, $threshold, $maxtoshow);
1808add1155SRico Sonntag    }
1818add1155SRico Sonntag
1828add1155SRico Sonntag    /**
1838add1155SRico Sonntag     * Find common give names.
1848add1155SRico Sonntag     *
1858add1155SRico Sonntag     * @param int $threshold
1868add1155SRico Sonntag     * @param int $maxtoshow
1878add1155SRico Sonntag     *
1888add1155SRico Sonntag     * @return string
1898add1155SRico Sonntag     */
1908add1155SRico Sonntag    public function commonGivenTotals(int $threshold = 1, int $maxtoshow = 10): string
1918add1155SRico Sonntag    {
1928add1155SRico Sonntag        return $this->commonGivenQuery('B', 'nolist', true, $threshold, $maxtoshow);
1938add1155SRico Sonntag    }
1948add1155SRico Sonntag
1958add1155SRico Sonntag    /**
1968add1155SRico Sonntag     * Find common give names.
1978add1155SRico Sonntag     *
1988add1155SRico Sonntag     * @param int $threshold
1998add1155SRico Sonntag     * @param int $maxtoshow
2008add1155SRico Sonntag     *
2018add1155SRico Sonntag     * @return string
2028add1155SRico Sonntag     */
2038add1155SRico Sonntag    public function commonGivenList(int $threshold = 1, int $maxtoshow = 10): string
2048add1155SRico Sonntag    {
2058add1155SRico Sonntag        return $this->commonGivenQuery('B', 'list', false, $threshold, $maxtoshow);
2068add1155SRico Sonntag    }
2078add1155SRico Sonntag
2088add1155SRico Sonntag    /**
2098add1155SRico Sonntag     * Find common give names.
2108add1155SRico Sonntag     *
2118add1155SRico Sonntag     * @param int $threshold
2128add1155SRico Sonntag     * @param int $maxtoshow
2138add1155SRico Sonntag     *
2148add1155SRico Sonntag     * @return string
2158add1155SRico Sonntag     */
2168add1155SRico Sonntag    public function commonGivenListTotals(int $threshold = 1, int $maxtoshow = 10): string
2178add1155SRico Sonntag    {
2188add1155SRico Sonntag        return $this->commonGivenQuery('B', 'list', true, $threshold, $maxtoshow);
2198add1155SRico Sonntag    }
2208add1155SRico Sonntag
2218add1155SRico Sonntag    /**
2228add1155SRico Sonntag     * Find common give names.
2238add1155SRico Sonntag     *
2248add1155SRico Sonntag     * @param int $threshold
2258add1155SRico Sonntag     * @param int $maxtoshow
2268add1155SRico Sonntag     *
2278add1155SRico Sonntag     * @return string
2288add1155SRico Sonntag     */
2298add1155SRico Sonntag    public function commonGivenTable(int $threshold = 1, int $maxtoshow = 10): string
2308add1155SRico Sonntag    {
2318add1155SRico Sonntag        return $this->commonGivenQuery('B', 'table', false, $threshold, $maxtoshow);
2328add1155SRico Sonntag    }
2338add1155SRico Sonntag
2348add1155SRico Sonntag    /**
2358add1155SRico Sonntag     * Find common give names of females.
2368add1155SRico Sonntag     *
2378add1155SRico Sonntag     * @param int $threshold
2388add1155SRico Sonntag     * @param int $maxtoshow
2398add1155SRico Sonntag     *
2408add1155SRico Sonntag     * @return string
2418add1155SRico Sonntag     */
2428add1155SRico Sonntag    public function commonGivenFemale(int $threshold = 1, int $maxtoshow = 10): string
2438add1155SRico Sonntag    {
2448add1155SRico Sonntag        return $this->commonGivenQuery('F', 'nolist', false, $threshold, $maxtoshow);
2458add1155SRico Sonntag    }
2468add1155SRico Sonntag
2478add1155SRico Sonntag    /**
2488add1155SRico Sonntag     * Find common give names of females.
2498add1155SRico Sonntag     *
2508add1155SRico Sonntag     * @param int $threshold
2518add1155SRico Sonntag     * @param int $maxtoshow
2528add1155SRico Sonntag     *
2538add1155SRico Sonntag     * @return string
2548add1155SRico Sonntag     */
2558add1155SRico Sonntag    public function commonGivenFemaleTotals(int $threshold = 1, int $maxtoshow = 10): string
2568add1155SRico Sonntag    {
2578add1155SRico Sonntag        return $this->commonGivenQuery('F', 'nolist', true, $threshold, $maxtoshow);
2588add1155SRico Sonntag    }
2598add1155SRico Sonntag
2608add1155SRico Sonntag    /**
2618add1155SRico Sonntag     * Find common give names of females.
2628add1155SRico Sonntag     *
2638add1155SRico Sonntag     * @param int $threshold
2648add1155SRico Sonntag     * @param int $maxtoshow
2658add1155SRico Sonntag     *
2668add1155SRico Sonntag     * @return string
2678add1155SRico Sonntag     */
2688add1155SRico Sonntag    public function commonGivenFemaleList(int $threshold = 1, int $maxtoshow = 10): string
2698add1155SRico Sonntag    {
2708add1155SRico Sonntag        return $this->commonGivenQuery('F', 'list', false, $threshold, $maxtoshow);
2718add1155SRico Sonntag    }
2728add1155SRico Sonntag
2738add1155SRico Sonntag    /**
2748add1155SRico Sonntag     * Find common give names of females.
2758add1155SRico Sonntag     *
2768add1155SRico Sonntag     * @param int $threshold
2778add1155SRico Sonntag     * @param int $maxtoshow
2788add1155SRico Sonntag     *
2798add1155SRico Sonntag     * @return string
2808add1155SRico Sonntag     */
2818add1155SRico Sonntag    public function commonGivenFemaleListTotals(int $threshold = 1, int $maxtoshow = 10): string
2828add1155SRico Sonntag    {
2838add1155SRico Sonntag        return $this->commonGivenQuery('F', 'list', true, $threshold, $maxtoshow);
2848add1155SRico Sonntag    }
2858add1155SRico Sonntag
2868add1155SRico Sonntag    /**
2878add1155SRico Sonntag     * Find common give names of females.
2888add1155SRico Sonntag     *
2898add1155SRico Sonntag     * @param int $threshold
2908add1155SRico Sonntag     * @param int $maxtoshow
2918add1155SRico Sonntag     *
2928add1155SRico Sonntag     * @return string
2938add1155SRico Sonntag     */
2948add1155SRico Sonntag    public function commonGivenFemaleTable(int $threshold = 1, int $maxtoshow = 10): string
2958add1155SRico Sonntag    {
2968add1155SRico Sonntag        return $this->commonGivenQuery('F', 'table', false, $threshold, $maxtoshow);
2978add1155SRico Sonntag    }
2988add1155SRico Sonntag
2998add1155SRico Sonntag    /**
3008add1155SRico Sonntag     * Find common give names of males.
3018add1155SRico Sonntag     *
3028add1155SRico Sonntag     * @param int $threshold
3038add1155SRico Sonntag     * @param int $maxtoshow
3048add1155SRico Sonntag     *
3058add1155SRico Sonntag     * @return string
3068add1155SRico Sonntag     */
3078add1155SRico Sonntag    public function commonGivenMale(int $threshold = 1, int $maxtoshow = 10): string
3088add1155SRico Sonntag    {
3098add1155SRico Sonntag        return $this->commonGivenQuery('M', 'nolist', false, $threshold, $maxtoshow);
3108add1155SRico Sonntag    }
3118add1155SRico Sonntag
3128add1155SRico Sonntag    /**
3138add1155SRico Sonntag     * Find common give names of males.
3148add1155SRico Sonntag     *
3158add1155SRico Sonntag     * @param int $threshold
3168add1155SRico Sonntag     * @param int $maxtoshow
3178add1155SRico Sonntag     *
3188add1155SRico Sonntag     * @return string
3198add1155SRico Sonntag     */
3208add1155SRico Sonntag    public function commonGivenMaleTotals(int $threshold = 1, int $maxtoshow = 10): string
3218add1155SRico Sonntag    {
3228add1155SRico Sonntag        return $this->commonGivenQuery('M', 'nolist', true, $threshold, $maxtoshow);
3238add1155SRico Sonntag    }
3248add1155SRico Sonntag
3258add1155SRico Sonntag    /**
3268add1155SRico Sonntag     * Find common give names of males.
3278add1155SRico Sonntag     *
3288add1155SRico Sonntag     * @param int $threshold
3298add1155SRico Sonntag     * @param int $maxtoshow
3308add1155SRico Sonntag     *
3318add1155SRico Sonntag     * @return string
3328add1155SRico Sonntag     */
3338add1155SRico Sonntag    public function commonGivenMaleList(int $threshold = 1, int $maxtoshow = 10): string
3348add1155SRico Sonntag    {
3358add1155SRico Sonntag        return $this->commonGivenQuery('M', 'list', false, $threshold, $maxtoshow);
3368add1155SRico Sonntag    }
3378add1155SRico Sonntag
3388add1155SRico Sonntag    /**
3398add1155SRico Sonntag     * Find common give names of males.
3408add1155SRico Sonntag     *
3418add1155SRico Sonntag     * @param int $threshold
3428add1155SRico Sonntag     * @param int $maxtoshow
3438add1155SRico Sonntag     *
3448add1155SRico Sonntag     * @return string
3458add1155SRico Sonntag     */
3468add1155SRico Sonntag    public function commonGivenMaleListTotals(int $threshold = 1, int $maxtoshow = 10): string
3478add1155SRico Sonntag    {
3488add1155SRico Sonntag        return $this->commonGivenQuery('M', 'list', true, $threshold, $maxtoshow);
3498add1155SRico Sonntag    }
3508add1155SRico Sonntag
3518add1155SRico Sonntag    /**
3528add1155SRico Sonntag     * Find common give names of males.
3538add1155SRico Sonntag     *
3548add1155SRico Sonntag     * @param int $threshold
3558add1155SRico Sonntag     * @param int $maxtoshow
3568add1155SRico Sonntag     *
3578add1155SRico Sonntag     * @return string
3588add1155SRico Sonntag     */
3598add1155SRico Sonntag    public function commonGivenMaleTable(int $threshold = 1, int $maxtoshow = 10): string
3608add1155SRico Sonntag    {
3618add1155SRico Sonntag        return $this->commonGivenQuery('M', 'table', false, $threshold, $maxtoshow);
3628add1155SRico Sonntag    }
3638add1155SRico Sonntag
3648add1155SRico Sonntag    /**
3658add1155SRico Sonntag     * Find common give names of unknown sexes.
3668add1155SRico Sonntag     *
3678add1155SRico Sonntag     * @param int $threshold
3688add1155SRico Sonntag     * @param int $maxtoshow
3698add1155SRico Sonntag     *
3708add1155SRico Sonntag     * @return string
3718add1155SRico Sonntag     */
3728add1155SRico Sonntag    public function commonGivenUnknown(int $threshold = 1, int $maxtoshow = 10): string
3738add1155SRico Sonntag    {
3748add1155SRico Sonntag        return $this->commonGivenQuery('U', 'nolist', false, $threshold, $maxtoshow);
3758add1155SRico Sonntag    }
3768add1155SRico Sonntag
3778add1155SRico Sonntag    /**
3788add1155SRico Sonntag     * Find common give names of unknown sexes.
3798add1155SRico Sonntag     *
3808add1155SRico Sonntag     * @param int $threshold
3818add1155SRico Sonntag     * @param int $maxtoshow
3828add1155SRico Sonntag     *
3838add1155SRico Sonntag     * @return string
3848add1155SRico Sonntag     */
3858add1155SRico Sonntag    public function commonGivenUnknownTotals(int $threshold = 1, int $maxtoshow = 10): string
3868add1155SRico Sonntag    {
3878add1155SRico Sonntag        return $this->commonGivenQuery('U', 'nolist', true, $threshold, $maxtoshow);
3888add1155SRico Sonntag    }
3898add1155SRico Sonntag
3908add1155SRico Sonntag    /**
3918add1155SRico Sonntag     * Find common give names of unknown sexes.
3928add1155SRico Sonntag     *
3938add1155SRico Sonntag     * @param int $threshold
3948add1155SRico Sonntag     * @param int $maxtoshow
3958add1155SRico Sonntag     *
3968add1155SRico Sonntag     * @return string
3978add1155SRico Sonntag     */
3988add1155SRico Sonntag    public function commonGivenUnknownList(int $threshold = 1, int $maxtoshow = 10): string
3998add1155SRico Sonntag    {
4008add1155SRico Sonntag        return $this->commonGivenQuery('U', 'list', false, $threshold, $maxtoshow);
4018add1155SRico Sonntag    }
4028add1155SRico Sonntag
4038add1155SRico Sonntag    /**
4048add1155SRico Sonntag     * Find common give names of unknown sexes.
4058add1155SRico Sonntag     *
4068add1155SRico Sonntag     * @param int $threshold
4078add1155SRico Sonntag     * @param int $maxtoshow
4088add1155SRico Sonntag     *
4098add1155SRico Sonntag     * @return string
4108add1155SRico Sonntag     */
4118add1155SRico Sonntag    public function commonGivenUnknownListTotals(int $threshold = 1, int $maxtoshow = 10): string
4128add1155SRico Sonntag    {
4138add1155SRico Sonntag        return $this->commonGivenQuery('U', 'list', true, $threshold, $maxtoshow);
4148add1155SRico Sonntag    }
4158add1155SRico Sonntag
4168add1155SRico Sonntag    /**
4178add1155SRico Sonntag     * Find common give names of unknown sexes.
4188add1155SRico Sonntag     *
4198add1155SRico Sonntag     * @param int $threshold
4208add1155SRico Sonntag     * @param int $maxtoshow
4218add1155SRico Sonntag     *
4228add1155SRico Sonntag     * @return string
4238add1155SRico Sonntag     */
4248add1155SRico Sonntag    public function commonGivenUnknownTable(int $threshold = 1, int $maxtoshow = 10): string
4258add1155SRico Sonntag    {
4268add1155SRico Sonntag        return $this->commonGivenQuery('U', 'table', false, $threshold, $maxtoshow);
4278add1155SRico Sonntag    }
4288add1155SRico Sonntag
4298add1155SRico Sonntag    /**
4303dc8167dSGreg Roach     * Count the number of distinct given names (or the number of occurences of specific given names).
4318add1155SRico Sonntag     *
432*09482a55SGreg Roach     * @param array<string> ...$params
4338add1155SRico Sonntag     *
4348add1155SRico Sonntag     * @return string
4358add1155SRico Sonntag     */
4368add1155SRico Sonntag    public function totalGivennames(...$params): string
4378add1155SRico Sonntag    {
4383dc8167dSGreg Roach        $query = DB::table('name')
4393dc8167dSGreg Roach            ->where('n_file', '=', $this->tree->id());
4403dc8167dSGreg Roach
441320f6a24SGreg Roach        if ($params === []) {
442c09e99bfSRico Sonntag            // Count number of distinct given names.
4433dc8167dSGreg Roach            $query
444c09e99bfSRico Sonntag                ->distinct()
4458fb4e87cSGreg Roach                ->where('n_givn', '<>', Individual::PRAENOMEN_NESCIO)
446c09e99bfSRico Sonntag                ->whereNotNull('n_givn');
4478add1155SRico Sonntag        } else {
448c09e99bfSRico Sonntag            // Count number of occurences of specific given names.
44944cdc21eSGreg Roach            $query->whereIn('n_givn', $params);
4508add1155SRico Sonntag        }
4518add1155SRico Sonntag
452c09e99bfSRico Sonntag        $count = $query->count('n_givn');
4533dc8167dSGreg Roach
4543dc8167dSGreg Roach        return I18N::number($count);
4558add1155SRico Sonntag    }
4568add1155SRico Sonntag
4578add1155SRico Sonntag    /**
458320f6a24SGreg Roach     * Count the number of distinct surnames (or the number of occurrences of specific surnames).
4598add1155SRico Sonntag     *
460*09482a55SGreg Roach     * @param array<string> ...$params
4618add1155SRico Sonntag     *
4628add1155SRico Sonntag     * @return string
4638add1155SRico Sonntag     */
4648add1155SRico Sonntag    public function totalSurnames(...$params): string
4658add1155SRico Sonntag    {
4663dc8167dSGreg Roach        $query = DB::table('name')
4673dc8167dSGreg Roach            ->where('n_file', '=', $this->tree->id());
4683dc8167dSGreg Roach
469320f6a24SGreg Roach        if ($params === []) {
4703dc8167dSGreg Roach            // Count number of distinct surnames
471c09e99bfSRico Sonntag            $query->distinct()
472c09e99bfSRico Sonntag                ->whereNotNull('n_surn');
4738add1155SRico Sonntag        } else {
4743dc8167dSGreg Roach            // Count number of occurences of specific surnames.
4753dc8167dSGreg Roach            $query->whereIn('n_surn', $params);
4768add1155SRico Sonntag        }
4778add1155SRico Sonntag
478c09e99bfSRico Sonntag        $count = $query->count('n_surn');
4798add1155SRico Sonntag
4803dc8167dSGreg Roach        return I18N::number($count);
4818add1155SRico Sonntag    }
4828add1155SRico Sonntag
4838add1155SRico Sonntag    /**
4848add1155SRico Sonntag     * @param int $number_of_surnames
4858add1155SRico Sonntag     * @param int $threshold
4868add1155SRico Sonntag     *
487*09482a55SGreg Roach     * @return array<array<int>>
4888add1155SRico Sonntag     */
4898add1155SRico Sonntag    private function topSurnames(int $number_of_surnames, int $threshold): array
4908add1155SRico Sonntag    {
4918add1155SRico Sonntag        // Use the count of base surnames.
492d1a467e4SGreg Roach        $top_surnames = DB::table('name')
493d1a467e4SGreg Roach            ->where('n_file', '=', $this->tree->id())
494d1a467e4SGreg Roach            ->where('n_type', '<>', '_MARNM')
4958fb4e87cSGreg Roach            ->whereNotIn('n_surn', ['', Individual::NOMEN_NESCIO])
49647256fc5SGreg Roach            ->select(['n_surn'])
4977f5c2944SGreg Roach            ->groupBy(['n_surn'])
4982da2e0a6SGreg Roach            ->orderByRaw('COUNT(n_surn) DESC')
4992da2e0a6SGreg Roach            ->orderBy(new Expression('COUNT(n_surn)'), 'DESC')
5002da2e0a6SGreg Roach            ->having(new Expression('COUNT(n_surn)'), '>=', $threshold)
501d1a467e4SGreg Roach            ->take($number_of_surnames)
502d1a467e4SGreg Roach            ->get()
503d1a467e4SGreg Roach            ->pluck('n_surn')
504d1a467e4SGreg Roach            ->all();
5058add1155SRico Sonntag
5068add1155SRico Sonntag        $surnames = [];
5072da2e0a6SGreg Roach
5088add1155SRico Sonntag        foreach ($top_surnames as $top_surname) {
5092da2e0a6SGreg Roach            $surnames[$top_surname] = DB::table('name')
510d1a467e4SGreg Roach                ->where('n_file', '=', $this->tree->id())
5112da2e0a6SGreg Roach                ->where('n_type', '<>', '_MARNM')
5122da2e0a6SGreg Roach                ->where('n_surn', '=', $top_surname)
5132da2e0a6SGreg Roach                ->select(['n_surn', new Expression('COUNT(n_surn) AS count')])
5147f5c2944SGreg Roach                ->groupBy(['n_surn'])
5152da2e0a6SGreg Roach                ->orderBy('n_surn')
516d1a467e4SGreg Roach                ->get()
517d1a467e4SGreg Roach                ->pluck('count', 'n_surn')
518d1a467e4SGreg Roach                ->all();
5198add1155SRico Sonntag        }
5208add1155SRico Sonntag
5218add1155SRico Sonntag        return $surnames;
5228add1155SRico Sonntag    }
5238add1155SRico Sonntag
5248add1155SRico Sonntag    /**
5258add1155SRico Sonntag     * Find common surnames.
5268add1155SRico Sonntag     *
5278add1155SRico Sonntag     * @return string
5288add1155SRico Sonntag     */
5298add1155SRico Sonntag    public function getCommonSurname(): string
5308add1155SRico Sonntag    {
5318add1155SRico Sonntag        $top_surname = $this->topSurnames(1, 0);
532f24db0ceSRico Sonntag
533f24db0ceSRico Sonntag        return $top_surname
534f24db0ceSRico Sonntag            ? implode(', ', array_keys(array_shift($top_surname)) ?? [])
535f24db0ceSRico Sonntag            : '';
5368add1155SRico Sonntag    }
5378add1155SRico Sonntag
5388add1155SRico Sonntag    /**
5398add1155SRico Sonntag     * Find common surnames.
5408add1155SRico Sonntag     *
5418add1155SRico Sonntag     * @param string $type
5428add1155SRico Sonntag     * @param bool   $show_tot
5438add1155SRico Sonntag     * @param int    $threshold
5448add1155SRico Sonntag     * @param int    $number_of_surnames
5458add1155SRico Sonntag     * @param string $sorting
5468add1155SRico Sonntag     *
5478add1155SRico Sonntag     * @return string
5488add1155SRico Sonntag     */
5498add1155SRico Sonntag    private function commonSurnamesQuery(
5508add1155SRico Sonntag        string $type,
5518add1155SRico Sonntag        bool $show_tot,
5528add1155SRico Sonntag        int $threshold,
5538add1155SRico Sonntag        int $number_of_surnames,
5548add1155SRico Sonntag        string $sorting
5558add1155SRico Sonntag    ): string {
5568add1155SRico Sonntag        $surnames = $this->topSurnames($number_of_surnames, $threshold);
5578add1155SRico Sonntag
5588add1155SRico Sonntag        switch ($sorting) {
5598add1155SRico Sonntag            default:
5608add1155SRico Sonntag            case 'alpha':
56137646143SGreg Roach                uksort($surnames, I18N::comparator());
5628add1155SRico Sonntag                break;
5638add1155SRico Sonntag            case 'count':
5648add1155SRico Sonntag                break;
5658add1155SRico Sonntag            case 'rcount':
5668add1155SRico Sonntag                $surnames = array_reverse($surnames, true);
5678add1155SRico Sonntag                break;
5688add1155SRico Sonntag        }
5698add1155SRico Sonntag
57067992b6aSRichard Cissee        //find a module providing individual lists
5710b5fd0a6SGreg Roach        $module = app(ModuleService::class)->findByComponent(ModuleListInterface::class, $this->tree, Auth::user())->first(static function (ModuleInterface $module): bool {
57267992b6aSRichard Cissee            return $module instanceof IndividualListModule;
57367992b6aSRichard Cissee        });
57467992b6aSRichard Cissee
5758add1155SRico Sonntag        return FunctionsPrintLists::surnameList(
5768add1155SRico Sonntag            $surnames,
5778add1155SRico Sonntag            ($type === 'list' ? 1 : 2),
5788add1155SRico Sonntag            $show_tot,
57967992b6aSRichard Cissee            $module,
5808add1155SRico Sonntag            $this->tree
5818add1155SRico Sonntag        );
5828add1155SRico Sonntag    }
5838add1155SRico Sonntag
5848add1155SRico Sonntag    /**
5858add1155SRico Sonntag     * Find common surnames.
5868add1155SRico Sonntag     *
5878add1155SRico Sonntag     * @param int    $threshold
5888add1155SRico Sonntag     * @param int    $number_of_surnames
5898add1155SRico Sonntag     * @param string $sorting
5908add1155SRico Sonntag     *
5918add1155SRico Sonntag     * @return string
5928add1155SRico Sonntag     */
5938add1155SRico Sonntag    public function commonSurnames(
5948add1155SRico Sonntag        int $threshold = 1,
5958add1155SRico Sonntag        int $number_of_surnames = 10,
5968add1155SRico Sonntag        string $sorting = 'alpha'
5978add1155SRico Sonntag    ): string {
5988add1155SRico Sonntag        return $this->commonSurnamesQuery('nolist', false, $threshold, $number_of_surnames, $sorting);
5998add1155SRico Sonntag    }
6008add1155SRico Sonntag
6018add1155SRico Sonntag    /**
6028add1155SRico Sonntag     * Find common surnames.
6038add1155SRico Sonntag     *
6048add1155SRico Sonntag     * @param int    $threshold
6058add1155SRico Sonntag     * @param int    $number_of_surnames
6068add1155SRico Sonntag     * @param string $sorting
6078add1155SRico Sonntag     *
6088add1155SRico Sonntag     * @return string
6098add1155SRico Sonntag     */
6108add1155SRico Sonntag    public function commonSurnamesTotals(
6118add1155SRico Sonntag        int $threshold = 1,
6128add1155SRico Sonntag        int $number_of_surnames = 10,
6132da2e0a6SGreg Roach        string $sorting = 'count'
6148add1155SRico Sonntag    ): string {
6158add1155SRico Sonntag        return $this->commonSurnamesQuery('nolist', true, $threshold, $number_of_surnames, $sorting);
6168add1155SRico Sonntag    }
6178add1155SRico Sonntag
6188add1155SRico Sonntag    /**
6198add1155SRico Sonntag     * Find common surnames.
6208add1155SRico Sonntag     *
6218add1155SRico Sonntag     * @param int    $threshold
6228add1155SRico Sonntag     * @param int    $number_of_surnames
6238add1155SRico Sonntag     * @param string $sorting
6248add1155SRico Sonntag     *
6258add1155SRico Sonntag     * @return string
6268add1155SRico Sonntag     */
6278add1155SRico Sonntag    public function commonSurnamesList(
6288add1155SRico Sonntag        int $threshold = 1,
6298add1155SRico Sonntag        int $number_of_surnames = 10,
6308add1155SRico Sonntag        string $sorting = 'alpha'
6318add1155SRico Sonntag    ): string {
6328add1155SRico Sonntag        return $this->commonSurnamesQuery('list', false, $threshold, $number_of_surnames, $sorting);
6338add1155SRico Sonntag    }
6348add1155SRico Sonntag
6358add1155SRico Sonntag    /**
6368add1155SRico Sonntag     * Find common surnames.
6378add1155SRico Sonntag     *
6388add1155SRico Sonntag     * @param int    $threshold
6398add1155SRico Sonntag     * @param int    $number_of_surnames
6408add1155SRico Sonntag     * @param string $sorting
6418add1155SRico Sonntag     *
6428add1155SRico Sonntag     * @return string
6438add1155SRico Sonntag     */
6448add1155SRico Sonntag    public function commonSurnamesListTotals(
6458add1155SRico Sonntag        int $threshold = 1,
6468add1155SRico Sonntag        int $number_of_surnames = 10,
6472da2e0a6SGreg Roach        string $sorting = 'count'
6488add1155SRico Sonntag    ): string {
6498add1155SRico Sonntag        return $this->commonSurnamesQuery('list', true, $threshold, $number_of_surnames, $sorting);
6508add1155SRico Sonntag    }
6518add1155SRico Sonntag
6528add1155SRico Sonntag    /**
653cde1d378SGreg Roach     * Get a count of births by month.
6548add1155SRico Sonntag     *
6558add1155SRico Sonntag     * @param int  $year1
6568add1155SRico Sonntag     * @param int  $year2
6578add1155SRico Sonntag     *
658cde1d378SGreg Roach     * @return Builder
6598add1155SRico Sonntag     */
660cde1d378SGreg Roach    public function statsBirthQuery(int $year1 = -1, int $year2 = -1): Builder
6618add1155SRico Sonntag    {
662d1a467e4SGreg Roach        $query = DB::table('dates')
663a69f5655SGreg Roach            ->select(['d_month', new Expression('COUNT(*) AS total')])
664d1a467e4SGreg Roach            ->where('d_file', '=', $this->tree->id())
665d1a467e4SGreg Roach            ->where('d_fact', '=', 'BIRT')
666d1a467e4SGreg Roach            ->whereIn('d_type', ['@#DGREGORIAN@', '@#DJULIAN@'])
6677f5c2944SGreg Roach            ->groupBy(['d_month']);
6688add1155SRico Sonntag
6698add1155SRico Sonntag        if ($year1 >= 0 && $year2 >= 0) {
670d1a467e4SGreg Roach            $query->whereBetween('d_year', [$year1, $year2]);
6718add1155SRico Sonntag        }
6728add1155SRico Sonntag
673cde1d378SGreg Roach        return $query;
674cde1d378SGreg Roach    }
675cde1d378SGreg Roach
676cde1d378SGreg Roach    /**
677cde1d378SGreg Roach     * Get a count of births by month.
678cde1d378SGreg Roach     *
679cde1d378SGreg Roach     * @param int  $year1
680cde1d378SGreg Roach     * @param int  $year2
681cde1d378SGreg Roach     *
682cde1d378SGreg Roach     * @return Builder
683cde1d378SGreg Roach     */
684cde1d378SGreg Roach    public function statsBirthBySexQuery(int $year1 = -1, int $year2 = -1): Builder
685cde1d378SGreg Roach    {
686cde1d378SGreg Roach        return $this->statsBirthQuery($year1, $year2)
687a69f5655SGreg Roach                ->select(['d_month', 'i_sex', new Expression('COUNT(*) AS total')])
6880b5fd0a6SGreg Roach                ->join('individuals', static function (JoinClause $join): void {
689d1a467e4SGreg Roach                    $join
690d1a467e4SGreg Roach                        ->on('i_id', '=', 'd_gid')
691d1a467e4SGreg Roach                        ->on('i_file', '=', 'd_file');
692d1a467e4SGreg Roach                })
6937f5c2944SGreg Roach                ->groupBy(['i_sex']);
6948add1155SRico Sonntag    }
6958add1155SRico Sonntag
6968add1155SRico Sonntag    /**
6978add1155SRico Sonntag     * General query on births.
6988add1155SRico Sonntag     *
6998add1155SRico Sonntag     * @param string|null $color_from
7008add1155SRico Sonntag     * @param string|null $color_to
7018add1155SRico Sonntag     *
7028add1155SRico Sonntag     * @return string
7038add1155SRico Sonntag     */
70488de55fdSRico Sonntag    public function statsBirth(string $color_from = null, string $color_to = null): string
7058add1155SRico Sonntag    {
7068add1155SRico Sonntag        return (new ChartBirth($this->tree))
70788de55fdSRico Sonntag            ->chartBirth($color_from, $color_to);
7088add1155SRico Sonntag    }
7098add1155SRico Sonntag
7108add1155SRico Sonntag    /**
7118add1155SRico Sonntag     * Get a list of death dates.
7128add1155SRico Sonntag     *
7138add1155SRico Sonntag     * @param int  $year1
7148add1155SRico Sonntag     * @param int  $year2
7158add1155SRico Sonntag     *
716cde1d378SGreg Roach     * @return Builder
7178add1155SRico Sonntag     */
718cde1d378SGreg Roach    public function statsDeathQuery(int $year1 = -1, int $year2 = -1): Builder
7198add1155SRico Sonntag    {
720d1a467e4SGreg Roach        $query = DB::table('dates')
721a69f5655SGreg Roach            ->select(['d_month', new Expression('COUNT(*) AS total')])
722d1a467e4SGreg Roach            ->where('d_file', '=', $this->tree->id())
723d1a467e4SGreg Roach            ->where('d_fact', '=', 'DEAT')
724d1a467e4SGreg Roach            ->whereIn('d_type', ['@#DGREGORIAN@', '@#DJULIAN@'])
7257f5c2944SGreg Roach            ->groupBy(['d_month']);
7268add1155SRico Sonntag
7278add1155SRico Sonntag        if ($year1 >= 0 && $year2 >= 0) {
728d1a467e4SGreg Roach            $query->whereBetween('d_year', [$year1, $year2]);
7298add1155SRico Sonntag        }
7308add1155SRico Sonntag
731cde1d378SGreg Roach        return $query;
732cde1d378SGreg Roach    }
733cde1d378SGreg Roach
734cde1d378SGreg Roach    /**
735cde1d378SGreg Roach     * Get a list of death dates.
736cde1d378SGreg Roach     *
737cde1d378SGreg Roach     * @param int  $year1
738cde1d378SGreg Roach     * @param int  $year2
739cde1d378SGreg Roach     *
740cde1d378SGreg Roach     * @return Builder
741cde1d378SGreg Roach     */
742cde1d378SGreg Roach    public function statsDeathBySexQuery(int $year1 = -1, int $year2 = -1): Builder
743cde1d378SGreg Roach    {
744cde1d378SGreg Roach        return $this->statsDeathQuery($year1, $year2)
745a69f5655SGreg Roach                ->select(['d_month', 'i_sex', new Expression('COUNT(*) AS total')])
7460b5fd0a6SGreg Roach                ->join('individuals', static function (JoinClause $join): void {
747d1a467e4SGreg Roach                    $join
748d1a467e4SGreg Roach                        ->on('i_id', '=', 'd_gid')
749d1a467e4SGreg Roach                        ->on('i_file', '=', 'd_file');
750d1a467e4SGreg Roach                })
7517f5c2944SGreg Roach                ->groupBy(['i_sex']);
7528add1155SRico Sonntag    }
7538add1155SRico Sonntag
7548add1155SRico Sonntag    /**
7558add1155SRico Sonntag     * General query on deaths.
7568add1155SRico Sonntag     *
7578add1155SRico Sonntag     * @param string|null $color_from
7588add1155SRico Sonntag     * @param string|null $color_to
7598add1155SRico Sonntag     *
7608add1155SRico Sonntag     * @return string
7618add1155SRico Sonntag     */
76288de55fdSRico Sonntag    public function statsDeath(string $color_from = null, string $color_to = null): string
7638add1155SRico Sonntag    {
7648add1155SRico Sonntag        return (new ChartDeath($this->tree))
76588de55fdSRico Sonntag            ->chartDeath($color_from, $color_to);
7668add1155SRico Sonntag    }
7678add1155SRico Sonntag
7688add1155SRico Sonntag    /**
7698add1155SRico Sonntag     * General query on ages.
7708add1155SRico Sonntag     *
7718add1155SRico Sonntag     * @param string $related
7728add1155SRico Sonntag     * @param string $sex
7738add1155SRico Sonntag     * @param int    $year1
7748add1155SRico Sonntag     * @param int    $year2
7758add1155SRico Sonntag     *
77691c84b80SGreg Roach     * @return array<stdClass>
7778add1155SRico Sonntag     */
778d823340dSGreg Roach    public function statsAgeQuery(string $related = 'BIRT', string $sex = 'BOTH', int $year1 = -1, int $year2 = -1): array
7798add1155SRico Sonntag    {
78044cdc21eSGreg Roach        $prefix = DB::connection()->getTablePrefix();
7818add1155SRico Sonntag
78244cdc21eSGreg Roach        $query = $this->birthAndDeathQuery($sex);
7838add1155SRico Sonntag
7848add1155SRico Sonntag        if ($year1 >= 0 && $year2 >= 0) {
78544cdc21eSGreg Roach            $query
78644cdc21eSGreg Roach                ->whereIn('birth.d_type', ['@#DGREGORIAN@', '@#DJULIAN@'])
78744cdc21eSGreg Roach                ->whereIn('death.d_type', ['@#DGREGORIAN@', '@#DJULIAN@']);
78844cdc21eSGreg Roach
7898add1155SRico Sonntag            if ($related === 'BIRT') {
79044cdc21eSGreg Roach                $query->whereBetween('birth.d_year', [$year1, $year2]);
7918add1155SRico Sonntag            } elseif ($related === 'DEAT') {
79244cdc21eSGreg Roach                $query->whereBetween('death.d_year', [$year1, $year2]);
7938add1155SRico Sonntag            }
7948add1155SRico Sonntag        }
7958add1155SRico Sonntag
79644cdc21eSGreg Roach        return $query
797a69f5655SGreg Roach            ->select(new Expression($prefix . 'death.d_julianday2 - ' . $prefix . 'birth.d_julianday1 AS days'))
798c8c87812SRico Sonntag            ->orderBy('days', 'desc')
79944cdc21eSGreg Roach            ->get()
80044cdc21eSGreg Roach            ->all();
8018add1155SRico Sonntag    }
8028add1155SRico Sonntag
8038add1155SRico Sonntag    /**
8048add1155SRico Sonntag     * General query on ages.
8058add1155SRico Sonntag     *
8068add1155SRico Sonntag     * @return string
8078add1155SRico Sonntag     */
80888de55fdSRico Sonntag    public function statsAge(): string
8098add1155SRico Sonntag    {
81088de55fdSRico Sonntag        return (new ChartAge($this->tree))->chartAge();
8118add1155SRico Sonntag    }
8128add1155SRico Sonntag
8138add1155SRico Sonntag    /**
8148add1155SRico Sonntag     * Lifespan
8158add1155SRico Sonntag     *
8168add1155SRico Sonntag     * @param string $type
8178add1155SRico Sonntag     * @param string $sex
8188add1155SRico Sonntag     *
8198add1155SRico Sonntag     * @return string
8208add1155SRico Sonntag     */
8218add1155SRico Sonntag    private function longlifeQuery(string $type, string $sex): string
8228add1155SRico Sonntag    {
82344cdc21eSGreg Roach        $prefix = DB::connection()->getTablePrefix();
8248add1155SRico Sonntag
82544cdc21eSGreg Roach        $row = $this->birthAndDeathQuery($sex)
82644cdc21eSGreg Roach            ->orderBy('days', 'desc')
827a69f5655SGreg Roach            ->select(['individuals.*', new Expression($prefix . 'death.d_julianday2 - ' . $prefix . 'birth.d_julianday1 AS days')])
82844cdc21eSGreg Roach            ->first();
82944cdc21eSGreg Roach
83044cdc21eSGreg Roach        if ($row === null) {
8318add1155SRico Sonntag            return '';
8328add1155SRico Sonntag        }
83344cdc21eSGreg Roach
83444cdc21eSGreg Roach        /** @var Individual $individual */
8356b9cb339SGreg Roach        $individual = Registry::individualFactory()->mapper($this->tree)($row);
83644cdc21eSGreg Roach
837747d54c2SGreg Roach        if ($type !== 'age' && !$individual->canShow()) {
83844cdc21eSGreg Roach            return I18N::translate('This information is private and cannot be shown.');
83944cdc21eSGreg Roach        }
84044cdc21eSGreg Roach
8418add1155SRico Sonntag        switch ($type) {
8428add1155SRico Sonntag            default:
8438add1155SRico Sonntag            case 'full':
84444cdc21eSGreg Roach                return $individual->formatList();
8458add1155SRico Sonntag
84644cdc21eSGreg Roach            case 'age':
84744cdc21eSGreg Roach                return I18N::number((int) ($row->days / 365.25));
84844cdc21eSGreg Roach
84944cdc21eSGreg Roach            case 'name':
85039ca88baSGreg Roach                return '<a href="' . e($individual->url()) . '">' . $individual->fullName() . '</a>';
85144cdc21eSGreg Roach        }
8528add1155SRico Sonntag    }
8538add1155SRico Sonntag
8548add1155SRico Sonntag    /**
8558add1155SRico Sonntag     * Find the longest lived individual.
8568add1155SRico Sonntag     *
8578add1155SRico Sonntag     * @return string
8588add1155SRico Sonntag     */
8598add1155SRico Sonntag    public function longestLife(): string
8608add1155SRico Sonntag    {
8618add1155SRico Sonntag        return $this->longlifeQuery('full', 'BOTH');
8628add1155SRico Sonntag    }
8638add1155SRico Sonntag
8648add1155SRico Sonntag    /**
8658add1155SRico Sonntag     * Find the age of the longest lived individual.
8668add1155SRico Sonntag     *
8678add1155SRico Sonntag     * @return string
8688add1155SRico Sonntag     */
8698add1155SRico Sonntag    public function longestLifeAge(): string
8708add1155SRico Sonntag    {
8718add1155SRico Sonntag        return $this->longlifeQuery('age', 'BOTH');
8728add1155SRico Sonntag    }
8738add1155SRico Sonntag
8748add1155SRico Sonntag    /**
8758add1155SRico Sonntag     * Find the name of the longest lived individual.
8768add1155SRico Sonntag     *
8778add1155SRico Sonntag     * @return string
8788add1155SRico Sonntag     */
8798add1155SRico Sonntag    public function longestLifeName(): string
8808add1155SRico Sonntag    {
8818add1155SRico Sonntag        return $this->longlifeQuery('name', 'BOTH');
8828add1155SRico Sonntag    }
8838add1155SRico Sonntag
8848add1155SRico Sonntag    /**
8858add1155SRico Sonntag     * Find the longest lived female.
8868add1155SRico Sonntag     *
8878add1155SRico Sonntag     * @return string
8888add1155SRico Sonntag     */
8898add1155SRico Sonntag    public function longestLifeFemale(): string
8908add1155SRico Sonntag    {
8918add1155SRico Sonntag        return $this->longlifeQuery('full', 'F');
8928add1155SRico Sonntag    }
8938add1155SRico Sonntag
8948add1155SRico Sonntag    /**
8958add1155SRico Sonntag     * Find the age of the longest lived female.
8968add1155SRico Sonntag     *
8978add1155SRico Sonntag     * @return string
8988add1155SRico Sonntag     */
8998add1155SRico Sonntag    public function longestLifeFemaleAge(): string
9008add1155SRico Sonntag    {
9018add1155SRico Sonntag        return $this->longlifeQuery('age', 'F');
9028add1155SRico Sonntag    }
9038add1155SRico Sonntag
9048add1155SRico Sonntag    /**
9058add1155SRico Sonntag     * Find the name of the longest lived female.
9068add1155SRico Sonntag     *
9078add1155SRico Sonntag     * @return string
9088add1155SRico Sonntag     */
9098add1155SRico Sonntag    public function longestLifeFemaleName(): string
9108add1155SRico Sonntag    {
9118add1155SRico Sonntag        return $this->longlifeQuery('name', 'F');
9128add1155SRico Sonntag    }
9138add1155SRico Sonntag
9148add1155SRico Sonntag    /**
9158add1155SRico Sonntag     * Find the longest lived male.
9168add1155SRico Sonntag     *
9178add1155SRico Sonntag     * @return string
9188add1155SRico Sonntag     */
9198add1155SRico Sonntag    public function longestLifeMale(): string
9208add1155SRico Sonntag    {
9218add1155SRico Sonntag        return $this->longlifeQuery('full', 'M');
9228add1155SRico Sonntag    }
9238add1155SRico Sonntag
9248add1155SRico Sonntag    /**
9258add1155SRico Sonntag     * Find the age of the longest lived male.
9268add1155SRico Sonntag     *
9278add1155SRico Sonntag     * @return string
9288add1155SRico Sonntag     */
9298add1155SRico Sonntag    public function longestLifeMaleAge(): string
9308add1155SRico Sonntag    {
9318add1155SRico Sonntag        return $this->longlifeQuery('age', 'M');
9328add1155SRico Sonntag    }
9338add1155SRico Sonntag
9348add1155SRico Sonntag    /**
9358add1155SRico Sonntag     * Find the name of the longest lived male.
9368add1155SRico Sonntag     *
9378add1155SRico Sonntag     * @return string
9388add1155SRico Sonntag     */
9398add1155SRico Sonntag    public function longestLifeMaleName(): string
9408add1155SRico Sonntag    {
9418add1155SRico Sonntag        return $this->longlifeQuery('name', 'M');
9428add1155SRico Sonntag    }
9438add1155SRico Sonntag
9448add1155SRico Sonntag    /**
9458add1155SRico Sonntag     * Returns the calculated age the time of event.
9468add1155SRico Sonntag     *
947054771e9SGreg Roach     * @param int $days The age from the database record
9488add1155SRico Sonntag     *
9498add1155SRico Sonntag     * @return string
9508add1155SRico Sonntag     */
951054771e9SGreg Roach    private function calculateAge(int $days): string
9528add1155SRico Sonntag    {
953054771e9SGreg Roach        if ($days < 31) {
954054771e9SGreg Roach            return I18N::plural('%s day', '%s days', $days, I18N::number($days));
9558add1155SRico Sonntag        }
9568add1155SRico Sonntag
957054771e9SGreg Roach        if ($days < 365) {
958054771e9SGreg Roach            $months = (int) ($days / 30.5);
9591061e22bSGreg Roach            return I18N::plural('%s month', '%s months', $months, I18N::number($months));
960054771e9SGreg Roach        }
961054771e9SGreg Roach
962054771e9SGreg Roach        $years = (int) ($days / 365.25);
963054771e9SGreg Roach
964054771e9SGreg Roach        return I18N::plural('%s year', '%s years', $years, I18N::number($years));
9658add1155SRico Sonntag    }
9668add1155SRico Sonntag
9678add1155SRico Sonntag    /**
9688add1155SRico Sonntag     * Find the oldest individuals.
9698add1155SRico Sonntag     *
9708add1155SRico Sonntag     * @param string $sex
9718add1155SRico Sonntag     * @param int    $total
9728add1155SRico Sonntag     *
97391c84b80SGreg Roach     * @return array<array<string,mixed>>
9748add1155SRico Sonntag     */
9758add1155SRico Sonntag    private function topTenOldestQuery(string $sex, int $total): array
9768add1155SRico Sonntag    {
97744cdc21eSGreg Roach        $prefix = DB::connection()->getTablePrefix();
9788add1155SRico Sonntag
97944cdc21eSGreg Roach        $rows = $this->birthAndDeathQuery($sex)
98044cdc21eSGreg Roach            ->groupBy(['i_id', 'i_file'])
98144cdc21eSGreg Roach            ->orderBy('days', 'desc')
982a69f5655SGreg Roach            ->select(['individuals.*', new Expression('MAX(' . $prefix . 'death.d_julianday2 - ' . $prefix . 'birth.d_julianday1) AS days')])
98344cdc21eSGreg Roach            ->take($total)
98444cdc21eSGreg Roach            ->get();
9858add1155SRico Sonntag
9868add1155SRico Sonntag        $top10 = [];
9878add1155SRico Sonntag        foreach ($rows as $row) {
98844cdc21eSGreg Roach            /** @var Individual $individual */
9896b9cb339SGreg Roach            $individual = Registry::individualFactory()->mapper($this->tree)($row);
9908add1155SRico Sonntag
99144cdc21eSGreg Roach            if ($individual->canShow()) {
9928add1155SRico Sonntag                $top10[] = [
99344cdc21eSGreg Roach                    'person' => $individual,
99444cdc21eSGreg Roach                    'age'    => $this->calculateAge((int) $row->days),
9958add1155SRico Sonntag                ];
9968add1155SRico Sonntag            }
9978add1155SRico Sonntag        }
9988add1155SRico Sonntag
9998add1155SRico Sonntag        return $top10;
10008add1155SRico Sonntag    }
10018add1155SRico Sonntag
10028add1155SRico Sonntag    /**
10038add1155SRico Sonntag     * Find the oldest individuals.
10048add1155SRico Sonntag     *
10058add1155SRico Sonntag     * @param int $total
10068add1155SRico Sonntag     *
10078add1155SRico Sonntag     * @return string
10088add1155SRico Sonntag     */
10098add1155SRico Sonntag    public function topTenOldest(int $total = 10): string
10108add1155SRico Sonntag    {
10118add1155SRico Sonntag        $records = $this->topTenOldestQuery('BOTH', $total);
10128add1155SRico Sonntag
1013c0112ce8SGreg Roach        return view('statistics/individuals/top10-nolist', [
10148add1155SRico Sonntag            'records' => $records,
1015c0112ce8SGreg Roach        ]);
10168add1155SRico Sonntag    }
10178add1155SRico Sonntag
10188add1155SRico Sonntag    /**
10198add1155SRico Sonntag     * Find the oldest living individuals.
10208add1155SRico Sonntag     *
10218add1155SRico Sonntag     * @param int $total
10228add1155SRico Sonntag     *
10238add1155SRico Sonntag     * @return string
10248add1155SRico Sonntag     */
10258add1155SRico Sonntag    public function topTenOldestList(int $total = 10): string
10268add1155SRico Sonntag    {
10278add1155SRico Sonntag        $records = $this->topTenOldestQuery('BOTH', $total);
10288add1155SRico Sonntag
1029c0112ce8SGreg Roach        return view('statistics/individuals/top10-list', [
10308add1155SRico Sonntag            'records' => $records,
1031c0112ce8SGreg Roach        ]);
10328add1155SRico Sonntag    }
10338add1155SRico Sonntag
10348add1155SRico Sonntag    /**
10358add1155SRico Sonntag     * Find the oldest females.
10368add1155SRico Sonntag     *
10378add1155SRico Sonntag     * @param int $total
10388add1155SRico Sonntag     *
10398add1155SRico Sonntag     * @return string
10408add1155SRico Sonntag     */
10418add1155SRico Sonntag    public function topTenOldestFemale(int $total = 10): string
10428add1155SRico Sonntag    {
10438add1155SRico Sonntag        $records = $this->topTenOldestQuery('F', $total);
10448add1155SRico Sonntag
1045c0112ce8SGreg Roach        return view('statistics/individuals/top10-nolist', [
10468add1155SRico Sonntag            'records' => $records,
1047c0112ce8SGreg Roach        ]);
10488add1155SRico Sonntag    }
10498add1155SRico Sonntag
10508add1155SRico Sonntag    /**
10518add1155SRico Sonntag     * Find the oldest living females.
10528add1155SRico Sonntag     *
10538add1155SRico Sonntag     * @param int $total
10548add1155SRico Sonntag     *
10558add1155SRico Sonntag     * @return string
10568add1155SRico Sonntag     */
10578add1155SRico Sonntag    public function topTenOldestFemaleList(int $total = 10): string
10588add1155SRico Sonntag    {
10598add1155SRico Sonntag        $records = $this->topTenOldestQuery('F', $total);
10608add1155SRico Sonntag
1061c0112ce8SGreg Roach        return view('statistics/individuals/top10-list', [
10628add1155SRico Sonntag            'records' => $records,
1063c0112ce8SGreg Roach        ]);
10648add1155SRico Sonntag    }
10658add1155SRico Sonntag
10668add1155SRico Sonntag    /**
10678add1155SRico Sonntag     * Find the longest lived males.
10688add1155SRico Sonntag     *
10698add1155SRico Sonntag     * @param int $total
10708add1155SRico Sonntag     *
10718add1155SRico Sonntag     * @return string
10728add1155SRico Sonntag     */
10738add1155SRico Sonntag    public function topTenOldestMale(int $total = 10): string
10748add1155SRico Sonntag    {
10758add1155SRico Sonntag        $records = $this->topTenOldestQuery('M', $total);
10768add1155SRico Sonntag
1077c0112ce8SGreg Roach        return view('statistics/individuals/top10-nolist', [
10788add1155SRico Sonntag            'records' => $records,
1079c0112ce8SGreg Roach        ]);
10808add1155SRico Sonntag    }
10818add1155SRico Sonntag
10828add1155SRico Sonntag    /**
10838add1155SRico Sonntag     * Find the longest lived males.
10848add1155SRico Sonntag     *
10858add1155SRico Sonntag     * @param int $total
10868add1155SRico Sonntag     *
10878add1155SRico Sonntag     * @return string
10888add1155SRico Sonntag     */
10898add1155SRico Sonntag    public function topTenOldestMaleList(int $total = 10): string
10908add1155SRico Sonntag    {
10918add1155SRico Sonntag        $records = $this->topTenOldestQuery('M', $total);
10928add1155SRico Sonntag
1093c0112ce8SGreg Roach        return view('statistics/individuals/top10-list', [
10948add1155SRico Sonntag            'records' => $records,
1095c0112ce8SGreg Roach        ]);
10968add1155SRico Sonntag    }
10978add1155SRico Sonntag
10988add1155SRico Sonntag    /**
10998add1155SRico Sonntag     * Find the oldest living individuals.
11008add1155SRico Sonntag     *
1101c0112ce8SGreg Roach     * @param string $sex   "M", "F" or "BOTH"
11028add1155SRico Sonntag     * @param int    $total
11038add1155SRico Sonntag     *
110491c84b80SGreg Roach     * @return array<array<string,mixed>>
11058add1155SRico Sonntag     */
1106c0112ce8SGreg Roach    private function topTenOldestAliveQuery(string $sex, int $total): array
11078add1155SRico Sonntag    {
1108d1a467e4SGreg Roach        $query = DB::table('dates')
11090b5fd0a6SGreg Roach            ->join('individuals', static function (JoinClause $join): void {
1110d1a467e4SGreg Roach                $join
1111d1a467e4SGreg Roach                    ->on('i_id', '=', 'd_gid')
1112d1a467e4SGreg Roach                    ->on('i_file', '=', 'd_file');
1113d1a467e4SGreg Roach            })
1114d1a467e4SGreg Roach            ->where('d_file', '=', $this->tree->id())
1115d1a467e4SGreg Roach            ->where('d_julianday1', '<>', 0)
1116d1a467e4SGreg Roach            ->where('d_fact', '=', 'BIRT')
1117d1a467e4SGreg Roach            ->where('i_gedcom', 'NOT LIKE', "%\n1 DEAT%")
1118d1a467e4SGreg Roach            ->where('i_gedcom', 'NOT LIKE', "%\n1 BURI%")
1119d1a467e4SGreg Roach            ->where('i_gedcom', 'NOT LIKE', "%\n1 CREM%");
1120d1a467e4SGreg Roach
1121d1a467e4SGreg Roach        if ($sex === 'F' || $sex === 'M') {
1122d1a467e4SGreg Roach            $query->where('i_sex', '=', $sex);
11238add1155SRico Sonntag        }
11248add1155SRico Sonntag
1125c0112ce8SGreg Roach        return $query
1126d1a467e4SGreg Roach            ->groupBy(['i_id', 'i_file'])
1127a69f5655SGreg Roach            ->orderBy(new Expression('MIN(d_julianday1)'))
112847256fc5SGreg Roach            ->select(['individuals.*'])
1129d1a467e4SGreg Roach            ->take($total)
1130d1a467e4SGreg Roach            ->get()
11316b9cb339SGreg Roach            ->map(Registry::individualFactory()->mapper($this->tree))
1132c0112ce8SGreg Roach            ->filter(GedcomRecord::accessFilter())
1133c0112ce8SGreg Roach            ->map(function (Individual $individual): array {
1134c0112ce8SGreg Roach                return [
1135d1a467e4SGreg Roach                    'person' => $individual,
11364459dc9aSGreg Roach                    'age'    => $this->calculateAge(Carbon::now()->julianDay() - $individual->getBirthDate()->minimumJulianDay()),
11378add1155SRico Sonntag                ];
1138c0112ce8SGreg Roach            })
1139c0112ce8SGreg Roach            ->all();
11408add1155SRico Sonntag    }
11418add1155SRico Sonntag
11428add1155SRico Sonntag    /**
11438add1155SRico Sonntag     * Find the oldest living individuals.
11448add1155SRico Sonntag     *
11458add1155SRico Sonntag     * @param int $total
11468add1155SRico Sonntag     *
11478add1155SRico Sonntag     * @return string
11488add1155SRico Sonntag     */
11498add1155SRico Sonntag    public function topTenOldestAlive(int $total = 10): string
11508add1155SRico Sonntag    {
11518add1155SRico Sonntag        if (!Auth::isMember($this->tree)) {
11528add1155SRico Sonntag            return I18N::translate('This information is private and cannot be shown.');
11538add1155SRico Sonntag        }
11548add1155SRico Sonntag
11558add1155SRico Sonntag        $records = $this->topTenOldestAliveQuery('BOTH', $total);
11568add1155SRico Sonntag
1157c0112ce8SGreg Roach        return view('statistics/individuals/top10-nolist', [
11588add1155SRico Sonntag            'records' => $records,
1159c0112ce8SGreg Roach        ]);
11608add1155SRico Sonntag    }
11618add1155SRico Sonntag
11628add1155SRico Sonntag    /**
11638add1155SRico Sonntag     * Find the oldest living individuals.
11648add1155SRico Sonntag     *
11658add1155SRico Sonntag     * @param int $total
11668add1155SRico Sonntag     *
11678add1155SRico Sonntag     * @return string
11688add1155SRico Sonntag     */
11698add1155SRico Sonntag    public function topTenOldestListAlive(int $total = 10): string
11708add1155SRico Sonntag    {
11718add1155SRico Sonntag        if (!Auth::isMember($this->tree)) {
11728add1155SRico Sonntag            return I18N::translate('This information is private and cannot be shown.');
11738add1155SRico Sonntag        }
11748add1155SRico Sonntag
11758add1155SRico Sonntag        $records = $this->topTenOldestAliveQuery('BOTH', $total);
11768add1155SRico Sonntag
1177c0112ce8SGreg Roach        return view('statistics/individuals/top10-list', [
11788add1155SRico Sonntag            'records' => $records,
1179c0112ce8SGreg Roach        ]);
11808add1155SRico Sonntag    }
11818add1155SRico Sonntag
11828add1155SRico Sonntag    /**
11838add1155SRico Sonntag     * Find the oldest living females.
11848add1155SRico Sonntag     *
11858add1155SRico Sonntag     * @param int $total
11868add1155SRico Sonntag     *
11878add1155SRico Sonntag     * @return string
11888add1155SRico Sonntag     */
11898add1155SRico Sonntag    public function topTenOldestFemaleAlive(int $total = 10): string
11908add1155SRico Sonntag    {
11918add1155SRico Sonntag        if (!Auth::isMember($this->tree)) {
11928add1155SRico Sonntag            return I18N::translate('This information is private and cannot be shown.');
11938add1155SRico Sonntag        }
11948add1155SRico Sonntag
11958add1155SRico Sonntag        $records = $this->topTenOldestAliveQuery('F', $total);
11968add1155SRico Sonntag
1197c0112ce8SGreg Roach        return view('statistics/individuals/top10-nolist', [
11988add1155SRico Sonntag            'records' => $records,
1199c0112ce8SGreg Roach        ]);
12008add1155SRico Sonntag    }
12018add1155SRico Sonntag
12028add1155SRico Sonntag    /**
12038add1155SRico Sonntag     * Find the oldest living females.
12048add1155SRico Sonntag     *
12058add1155SRico Sonntag     * @param int $total
12068add1155SRico Sonntag     *
12078add1155SRico Sonntag     * @return string
12088add1155SRico Sonntag     */
12098add1155SRico Sonntag    public function topTenOldestFemaleListAlive(int $total = 10): string
12108add1155SRico Sonntag    {
12118add1155SRico Sonntag        if (!Auth::isMember($this->tree)) {
12128add1155SRico Sonntag            return I18N::translate('This information is private and cannot be shown.');
12138add1155SRico Sonntag        }
12148add1155SRico Sonntag
12158add1155SRico Sonntag        $records = $this->topTenOldestAliveQuery('F', $total);
12168add1155SRico Sonntag
1217c0112ce8SGreg Roach        return view('statistics/individuals/top10-list', [
12188add1155SRico Sonntag            'records' => $records,
1219c0112ce8SGreg Roach        ]);
12208add1155SRico Sonntag    }
12218add1155SRico Sonntag
12228add1155SRico Sonntag    /**
12238add1155SRico Sonntag     * Find the longest lived living males.
12248add1155SRico Sonntag     *
12258add1155SRico Sonntag     * @param int $total
12268add1155SRico Sonntag     *
12278add1155SRico Sonntag     * @return string
12288add1155SRico Sonntag     */
12298add1155SRico Sonntag    public function topTenOldestMaleAlive(int $total = 10): string
12308add1155SRico Sonntag    {
12318add1155SRico Sonntag        if (!Auth::isMember($this->tree)) {
12328add1155SRico Sonntag            return I18N::translate('This information is private and cannot be shown.');
12338add1155SRico Sonntag        }
12348add1155SRico Sonntag
12358add1155SRico Sonntag        $records = $this->topTenOldestAliveQuery('M', $total);
12368add1155SRico Sonntag
1237c0112ce8SGreg Roach        return view('statistics/individuals/top10-nolist', [
12388add1155SRico Sonntag            'records' => $records,
1239c0112ce8SGreg Roach        ]);
12408add1155SRico Sonntag    }
12418add1155SRico Sonntag
12428add1155SRico Sonntag    /**
12438add1155SRico Sonntag     * Find the longest lived living males.
12448add1155SRico Sonntag     *
12458add1155SRico Sonntag     * @param int $total
12468add1155SRico Sonntag     *
12478add1155SRico Sonntag     * @return string
12488add1155SRico Sonntag     */
12498add1155SRico Sonntag    public function topTenOldestMaleListAlive(int $total = 10): string
12508add1155SRico Sonntag    {
12518add1155SRico Sonntag        if (!Auth::isMember($this->tree)) {
12528add1155SRico Sonntag            return I18N::translate('This information is private and cannot be shown.');
12538add1155SRico Sonntag        }
12548add1155SRico Sonntag
12558add1155SRico Sonntag        $records = $this->topTenOldestAliveQuery('M', $total);
12568add1155SRico Sonntag
1257c0112ce8SGreg Roach        return view('statistics/individuals/top10-list', [
12588add1155SRico Sonntag            'records' => $records,
1259c0112ce8SGreg Roach        ]);
12608add1155SRico Sonntag    }
12618add1155SRico Sonntag
12628add1155SRico Sonntag    /**
12638add1155SRico Sonntag     * Find the average lifespan.
12648add1155SRico Sonntag     *
1265c0112ce8SGreg Roach     * @param string $sex        "M", "F" or "BOTH"
12668add1155SRico Sonntag     * @param bool   $show_years
12678add1155SRico Sonntag     *
12688add1155SRico Sonntag     * @return string
12698add1155SRico Sonntag     */
1270c0112ce8SGreg Roach    private function averageLifespanQuery(string $sex, bool $show_years): string
12718add1155SRico Sonntag    {
127244cdc21eSGreg Roach        $prefix = DB::connection()->getTablePrefix();
12738add1155SRico Sonntag
127444cdc21eSGreg Roach        $days = (int) $this->birthAndDeathQuery($sex)
1275a69f5655SGreg Roach            ->select(new Expression('AVG(' . $prefix . 'death.d_julianday2 - ' . $prefix . 'birth.d_julianday1) AS days'))
127644cdc21eSGreg Roach            ->value('days');
12778add1155SRico Sonntag
12788add1155SRico Sonntag        if ($show_years) {
127944cdc21eSGreg Roach            return $this->calculateAge($days);
12808add1155SRico Sonntag        }
12818add1155SRico Sonntag
128244cdc21eSGreg Roach        return I18N::number((int) ($days / 365.25));
12838add1155SRico Sonntag    }
12848add1155SRico Sonntag
12858add1155SRico Sonntag    /**
12868add1155SRico Sonntag     * Find the average lifespan.
12878add1155SRico Sonntag     *
12888add1155SRico Sonntag     * @param bool $show_years
12898add1155SRico Sonntag     *
12908add1155SRico Sonntag     * @return string
12918add1155SRico Sonntag     */
12928add1155SRico Sonntag    public function averageLifespan($show_years = false): string
12938add1155SRico Sonntag    {
12948add1155SRico Sonntag        return $this->averageLifespanQuery('BOTH', $show_years);
12958add1155SRico Sonntag    }
12968add1155SRico Sonntag
12978add1155SRico Sonntag    /**
12988add1155SRico Sonntag     * Find the average lifespan of females.
12998add1155SRico Sonntag     *
13008add1155SRico Sonntag     * @param bool $show_years
13018add1155SRico Sonntag     *
13028add1155SRico Sonntag     * @return string
13038add1155SRico Sonntag     */
13048add1155SRico Sonntag    public function averageLifespanFemale($show_years = false): string
13058add1155SRico Sonntag    {
13068add1155SRico Sonntag        return $this->averageLifespanQuery('F', $show_years);
13078add1155SRico Sonntag    }
13088add1155SRico Sonntag
13098add1155SRico Sonntag    /**
13108add1155SRico Sonntag     * Find the average male lifespan.
13118add1155SRico Sonntag     *
13128add1155SRico Sonntag     * @param bool $show_years
13138add1155SRico Sonntag     *
13148add1155SRico Sonntag     * @return string
13158add1155SRico Sonntag     */
13168add1155SRico Sonntag    public function averageLifespanMale($show_years = false): string
13178add1155SRico Sonntag    {
13188add1155SRico Sonntag        return $this->averageLifespanQuery('M', $show_years);
13198add1155SRico Sonntag    }
13208add1155SRico Sonntag
13218add1155SRico Sonntag    /**
13228add1155SRico Sonntag     * Convert totals into percentages.
13238add1155SRico Sonntag     *
13248add1155SRico Sonntag     * @param int $count
13258add1155SRico Sonntag     * @param int $total
13268add1155SRico Sonntag     *
13278add1155SRico Sonntag     * @return string
13288add1155SRico Sonntag     */
13298add1155SRico Sonntag    private function getPercentage(int $count, int $total): string
13308add1155SRico Sonntag    {
1331f24db0ceSRico Sonntag        return ($total !== 0) ? I18N::percentage($count / $total, 1) : '';
13328add1155SRico Sonntag    }
13338add1155SRico Sonntag
13348add1155SRico Sonntag    /**
13358add1155SRico Sonntag     * Returns how many individuals exist in the tree.
13368add1155SRico Sonntag     *
13378add1155SRico Sonntag     * @return int
13388add1155SRico Sonntag     */
13398add1155SRico Sonntag    private function totalIndividualsQuery(): int
13408add1155SRico Sonntag    {
13418add1155SRico Sonntag        return DB::table('individuals')
13428add1155SRico Sonntag            ->where('i_file', '=', $this->tree->id())
13438add1155SRico Sonntag            ->count();
13448add1155SRico Sonntag    }
13458add1155SRico Sonntag
13468add1155SRico Sonntag    /**
13478add1155SRico Sonntag     * Count the number of living individuals.
13488add1155SRico Sonntag     *
13498add1155SRico Sonntag     * The totalLiving/totalDeceased queries assume that every dead person will
13508add1155SRico Sonntag     * have a DEAT record. It will not include individuals who were born more
13518add1155SRico Sonntag     * than MAX_ALIVE_AGE years ago, and who have no DEAT record.
13528add1155SRico Sonntag     * A good reason to run the “Add missing DEAT records” batch-update!
13538add1155SRico Sonntag     *
13548add1155SRico Sonntag     * @return int
13558add1155SRico Sonntag     */
13568add1155SRico Sonntag    private function totalLivingQuery(): int
13578add1155SRico Sonntag    {
13583dc8167dSGreg Roach        $query = DB::table('individuals')
13593dc8167dSGreg Roach            ->where('i_file', '=', $this->tree->id());
13603dc8167dSGreg Roach
13613dc8167dSGreg Roach        foreach (Gedcom::DEATH_EVENTS as $death_event) {
136268d9d7c9SRico Sonntag            $query->where('i_gedcom', 'NOT LIKE', "%\n1 " . $death_event . '%');
13633dc8167dSGreg Roach        }
13643dc8167dSGreg Roach
13653dc8167dSGreg Roach        return $query->count();
13668add1155SRico Sonntag    }
13678add1155SRico Sonntag
13688add1155SRico Sonntag    /**
13698add1155SRico Sonntag     * Count the number of dead individuals.
13708add1155SRico Sonntag     *
13718add1155SRico Sonntag     * @return int
13728add1155SRico Sonntag     */
13738add1155SRico Sonntag    private function totalDeceasedQuery(): int
13748add1155SRico Sonntag    {
13758add1155SRico Sonntag        return DB::table('individuals')
13768add1155SRico Sonntag            ->where('i_file', '=', $this->tree->id())
13770b5fd0a6SGreg Roach            ->where(static function (Builder $query): void {
13783dc8167dSGreg Roach                foreach (Gedcom::DEATH_EVENTS as $death_event) {
137968d9d7c9SRico Sonntag                    $query->orWhere('i_gedcom', 'LIKE', "%\n1 " . $death_event . '%');
13803dc8167dSGreg Roach                }
13813dc8167dSGreg Roach            })
13828add1155SRico Sonntag            ->count();
13838add1155SRico Sonntag    }
13848add1155SRico Sonntag
13858add1155SRico Sonntag    /**
13868add1155SRico Sonntag     * Returns the total count of a specific sex.
13878add1155SRico Sonntag     *
13888add1155SRico Sonntag     * @param string $sex The sex to query
13898add1155SRico Sonntag     *
13908add1155SRico Sonntag     * @return int
13918add1155SRico Sonntag     */
13928add1155SRico Sonntag    private function getTotalSexQuery(string $sex): int
13938add1155SRico Sonntag    {
13948add1155SRico Sonntag        return DB::table('individuals')
13958add1155SRico Sonntag            ->where('i_file', '=', $this->tree->id())
13968add1155SRico Sonntag            ->where('i_sex', '=', $sex)
13978add1155SRico Sonntag            ->count();
13988add1155SRico Sonntag    }
13998add1155SRico Sonntag
14008add1155SRico Sonntag    /**
14018add1155SRico Sonntag     * Returns the total number of males.
14028add1155SRico Sonntag     *
14038add1155SRico Sonntag     * @return int
14048add1155SRico Sonntag     */
14058add1155SRico Sonntag    private function totalSexMalesQuery(): int
14068add1155SRico Sonntag    {
14078add1155SRico Sonntag        return $this->getTotalSexQuery('M');
14088add1155SRico Sonntag    }
14098add1155SRico Sonntag
14108add1155SRico Sonntag    /**
14118add1155SRico Sonntag     * Returns the total number of females.
14128add1155SRico Sonntag     *
14138add1155SRico Sonntag     * @return int
14148add1155SRico Sonntag     */
14158add1155SRico Sonntag    private function totalSexFemalesQuery(): int
14168add1155SRico Sonntag    {
14178add1155SRico Sonntag        return $this->getTotalSexQuery('F');
14188add1155SRico Sonntag    }
14198add1155SRico Sonntag
14208add1155SRico Sonntag    /**
14218add1155SRico Sonntag     * Returns the total number of individuals with unknown sex.
14228add1155SRico Sonntag     *
14238add1155SRico Sonntag     * @return int
14248add1155SRico Sonntag     */
14258add1155SRico Sonntag    private function totalSexUnknownQuery(): int
14268add1155SRico Sonntag    {
14278add1155SRico Sonntag        return $this->getTotalSexQuery('U');
14288add1155SRico Sonntag    }
14298add1155SRico Sonntag
14308add1155SRico Sonntag    /**
14318add1155SRico Sonntag     * Count the total families.
14328add1155SRico Sonntag     *
14338add1155SRico Sonntag     * @return int
14348add1155SRico Sonntag     */
14358add1155SRico Sonntag    private function totalFamiliesQuery(): int
14368add1155SRico Sonntag    {
14378add1155SRico Sonntag        return DB::table('families')
14388add1155SRico Sonntag            ->where('f_file', '=', $this->tree->id())
14398add1155SRico Sonntag            ->count();
14408add1155SRico Sonntag    }
14418add1155SRico Sonntag
14428add1155SRico Sonntag    /**
14438add1155SRico Sonntag     * How many individuals have one or more sources.
14448add1155SRico Sonntag     *
14458add1155SRico Sonntag     * @return int
14468add1155SRico Sonntag     */
14478add1155SRico Sonntag    private function totalIndisWithSourcesQuery(): int
14488add1155SRico Sonntag    {
14498add1155SRico Sonntag        return DB::table('individuals')
14508add1155SRico Sonntag            ->select(['i_id'])
14518add1155SRico Sonntag            ->distinct()
14520b5fd0a6SGreg Roach            ->join('link', static function (JoinClause $join): void {
14538add1155SRico Sonntag                $join->on('i_id', '=', 'l_from')
14548add1155SRico Sonntag                    ->on('i_file', '=', 'l_file');
14558add1155SRico Sonntag            })
14568add1155SRico Sonntag            ->where('l_file', '=', $this->tree->id())
14578add1155SRico Sonntag            ->where('l_type', '=', 'SOUR')
14588add1155SRico Sonntag            ->count('i_id');
14598add1155SRico Sonntag    }
14608add1155SRico Sonntag
14618add1155SRico Sonntag    /**
14628add1155SRico Sonntag     * Count the families with source records.
14638add1155SRico Sonntag     *
14648add1155SRico Sonntag     * @return int
14658add1155SRico Sonntag     */
14668add1155SRico Sonntag    private function totalFamsWithSourcesQuery(): int
14678add1155SRico Sonntag    {
14688add1155SRico Sonntag        return DB::table('families')
14698add1155SRico Sonntag            ->select(['f_id'])
14708add1155SRico Sonntag            ->distinct()
14710b5fd0a6SGreg Roach            ->join('link', static function (JoinClause $join): void {
14728add1155SRico Sonntag                $join->on('f_id', '=', 'l_from')
14738add1155SRico Sonntag                    ->on('f_file', '=', 'l_file');
14748add1155SRico Sonntag            })
14758add1155SRico Sonntag            ->where('l_file', '=', $this->tree->id())
14768add1155SRico Sonntag            ->where('l_type', '=', 'SOUR')
14778add1155SRico Sonntag            ->count('f_id');
14788add1155SRico Sonntag    }
14798add1155SRico Sonntag
14808add1155SRico Sonntag    /**
14818add1155SRico Sonntag     * Count the number of repositories.
14828add1155SRico Sonntag     *
14838add1155SRico Sonntag     * @return int
14848add1155SRico Sonntag     */
14858add1155SRico Sonntag    private function totalRepositoriesQuery(): int
14868add1155SRico Sonntag    {
14878add1155SRico Sonntag        return DB::table('other')
14888add1155SRico Sonntag            ->where('o_file', '=', $this->tree->id())
14898add1155SRico Sonntag            ->where('o_type', '=', 'REPO')
14908add1155SRico Sonntag            ->count();
14918add1155SRico Sonntag    }
14928add1155SRico Sonntag
14938add1155SRico Sonntag    /**
14948add1155SRico Sonntag     * Count the total number of sources.
14958add1155SRico Sonntag     *
14968add1155SRico Sonntag     * @return int
14978add1155SRico Sonntag     */
14988add1155SRico Sonntag    private function totalSourcesQuery(): int
14998add1155SRico Sonntag    {
15008add1155SRico Sonntag        return DB::table('sources')
15018add1155SRico Sonntag            ->where('s_file', '=', $this->tree->id())
15028add1155SRico Sonntag            ->count();
15038add1155SRico Sonntag    }
15048add1155SRico Sonntag
15058add1155SRico Sonntag    /**
15068add1155SRico Sonntag     * Count the number of notes.
15078add1155SRico Sonntag     *
15088add1155SRico Sonntag     * @return int
15098add1155SRico Sonntag     */
15108add1155SRico Sonntag    private function totalNotesQuery(): int
15118add1155SRico Sonntag    {
15128add1155SRico Sonntag        return DB::table('other')
15138add1155SRico Sonntag            ->where('o_file', '=', $this->tree->id())
15148add1155SRico Sonntag            ->where('o_type', '=', 'NOTE')
15158add1155SRico Sonntag            ->count();
15168add1155SRico Sonntag    }
15178add1155SRico Sonntag
15188add1155SRico Sonntag    /**
15198add1155SRico Sonntag     * Returns the total number of records.
15208add1155SRico Sonntag     *
15218add1155SRico Sonntag     * @return int
15228add1155SRico Sonntag     */
15238add1155SRico Sonntag    private function totalRecordsQuery(): int
15248add1155SRico Sonntag    {
15258add1155SRico Sonntag        return $this->totalIndividualsQuery()
15268add1155SRico Sonntag            + $this->totalFamiliesQuery()
15278add1155SRico Sonntag            + $this->totalNotesQuery()
15288add1155SRico Sonntag            + $this->totalRepositoriesQuery()
15298add1155SRico Sonntag            + $this->totalSourcesQuery();
15308add1155SRico Sonntag    }
15318add1155SRico Sonntag
15328add1155SRico Sonntag    /**
15330dcd9387SGreg Roach     * @return string
15348add1155SRico Sonntag     */
15358add1155SRico Sonntag    public function totalRecords(): string
15368add1155SRico Sonntag    {
15378add1155SRico Sonntag        return I18N::number($this->totalRecordsQuery());
15388add1155SRico Sonntag    }
15398add1155SRico Sonntag
15408add1155SRico Sonntag    /**
15410dcd9387SGreg Roach     * @return string
15428add1155SRico Sonntag     */
15438add1155SRico Sonntag    public function totalIndividuals(): string
15448add1155SRico Sonntag    {
15458add1155SRico Sonntag        return I18N::number($this->totalIndividualsQuery());
15468add1155SRico Sonntag    }
15478add1155SRico Sonntag
15488add1155SRico Sonntag    /**
15498add1155SRico Sonntag     * Count the number of living individuals.
15508add1155SRico Sonntag     *
15518add1155SRico Sonntag     * @return string
15528add1155SRico Sonntag     */
15538add1155SRico Sonntag    public function totalLiving(): string
15548add1155SRico Sonntag    {
15558add1155SRico Sonntag        return I18N::number($this->totalLivingQuery());
15568add1155SRico Sonntag    }
15578add1155SRico Sonntag
15588add1155SRico Sonntag    /**
15598add1155SRico Sonntag     * Count the number of dead individuals.
15608add1155SRico Sonntag     *
15618add1155SRico Sonntag     * @return string
15628add1155SRico Sonntag     */
15638add1155SRico Sonntag    public function totalDeceased(): string
15648add1155SRico Sonntag    {
15658add1155SRico Sonntag        return I18N::number($this->totalDeceasedQuery());
15668add1155SRico Sonntag    }
15678add1155SRico Sonntag
15688add1155SRico Sonntag    /**
15690dcd9387SGreg Roach     * @return string
15708add1155SRico Sonntag     */
15718add1155SRico Sonntag    public function totalSexMales(): string
15728add1155SRico Sonntag    {
15738add1155SRico Sonntag        return I18N::number($this->totalSexMalesQuery());
15748add1155SRico Sonntag    }
15758add1155SRico Sonntag
15768add1155SRico Sonntag    /**
15770dcd9387SGreg Roach     * @return string
15788add1155SRico Sonntag     */
15798add1155SRico Sonntag    public function totalSexFemales(): string
15808add1155SRico Sonntag    {
15818add1155SRico Sonntag        return I18N::number($this->totalSexFemalesQuery());
15828add1155SRico Sonntag    }
15838add1155SRico Sonntag
15848add1155SRico Sonntag    /**
15850dcd9387SGreg Roach     * @return string
15868add1155SRico Sonntag     */
15878add1155SRico Sonntag    public function totalSexUnknown(): string
15888add1155SRico Sonntag    {
15898add1155SRico Sonntag        return I18N::number($this->totalSexUnknownQuery());
15908add1155SRico Sonntag    }
15918add1155SRico Sonntag
15928add1155SRico Sonntag    /**
15930dcd9387SGreg Roach     * @return string
15948add1155SRico Sonntag     */
15958add1155SRico Sonntag    public function totalFamilies(): string
15968add1155SRico Sonntag    {
15978add1155SRico Sonntag        return I18N::number($this->totalFamiliesQuery());
15988add1155SRico Sonntag    }
15998add1155SRico Sonntag
16008add1155SRico Sonntag    /**
16018add1155SRico Sonntag     * How many individuals have one or more sources.
16028add1155SRico Sonntag     *
16038add1155SRico Sonntag     * @return string
16048add1155SRico Sonntag     */
16058add1155SRico Sonntag    public function totalIndisWithSources(): string
16068add1155SRico Sonntag    {
16078add1155SRico Sonntag        return I18N::number($this->totalIndisWithSourcesQuery());
16088add1155SRico Sonntag    }
16098add1155SRico Sonntag
16108add1155SRico Sonntag    /**
16118add1155SRico Sonntag     * Count the families with with source records.
16128add1155SRico Sonntag     *
16138add1155SRico Sonntag     * @return string
16148add1155SRico Sonntag     */
16158add1155SRico Sonntag    public function totalFamsWithSources(): string
16168add1155SRico Sonntag    {
16178add1155SRico Sonntag        return I18N::number($this->totalFamsWithSourcesQuery());
16188add1155SRico Sonntag    }
16198add1155SRico Sonntag
16208add1155SRico Sonntag    /**
16210dcd9387SGreg Roach     * @return string
16228add1155SRico Sonntag     */
16238add1155SRico Sonntag    public function totalRepositories(): string
16248add1155SRico Sonntag    {
16258add1155SRico Sonntag        return I18N::number($this->totalRepositoriesQuery());
16268add1155SRico Sonntag    }
16278add1155SRico Sonntag
16288add1155SRico Sonntag    /**
16290dcd9387SGreg Roach     * @return string
16308add1155SRico Sonntag     */
16318add1155SRico Sonntag    public function totalSources(): string
16328add1155SRico Sonntag    {
16338add1155SRico Sonntag        return I18N::number($this->totalSourcesQuery());
16348add1155SRico Sonntag    }
16358add1155SRico Sonntag
16368add1155SRico Sonntag    /**
16370dcd9387SGreg Roach     * @return string
16388add1155SRico Sonntag     */
16398add1155SRico Sonntag    public function totalNotes(): string
16408add1155SRico Sonntag    {
16418add1155SRico Sonntag        return I18N::number($this->totalNotesQuery());
16428add1155SRico Sonntag    }
16438add1155SRico Sonntag
16448add1155SRico Sonntag    /**
16450dcd9387SGreg Roach     * @return string
16468add1155SRico Sonntag     */
16478add1155SRico Sonntag    public function totalIndividualsPercentage(): string
16488add1155SRico Sonntag    {
16498add1155SRico Sonntag        return $this->getPercentage(
16508add1155SRico Sonntag            $this->totalIndividualsQuery(),
16518add1155SRico Sonntag            $this->totalRecordsQuery()
16528add1155SRico Sonntag        );
16538add1155SRico Sonntag    }
16548add1155SRico Sonntag
16558add1155SRico Sonntag    /**
16560dcd9387SGreg Roach     * @return string
16578add1155SRico Sonntag     */
16588add1155SRico Sonntag    public function totalFamiliesPercentage(): string
16598add1155SRico Sonntag    {
16608add1155SRico Sonntag        return $this->getPercentage(
16618add1155SRico Sonntag            $this->totalFamiliesQuery(),
16628add1155SRico Sonntag            $this->totalRecordsQuery()
16638add1155SRico Sonntag        );
16648add1155SRico Sonntag    }
16658add1155SRico Sonntag
16668add1155SRico Sonntag    /**
16670dcd9387SGreg Roach     * @return string
16688add1155SRico Sonntag     */
16698add1155SRico Sonntag    public function totalRepositoriesPercentage(): string
16708add1155SRico Sonntag    {
16718add1155SRico Sonntag        return $this->getPercentage(
16728add1155SRico Sonntag            $this->totalRepositoriesQuery(),
16738add1155SRico Sonntag            $this->totalRecordsQuery()
16748add1155SRico Sonntag        );
16758add1155SRico Sonntag    }
16768add1155SRico Sonntag
16778add1155SRico Sonntag    /**
16780dcd9387SGreg Roach     * @return string
16798add1155SRico Sonntag     */
16808add1155SRico Sonntag    public function totalSourcesPercentage(): string
16818add1155SRico Sonntag    {
16828add1155SRico Sonntag        return $this->getPercentage(
16838add1155SRico Sonntag            $this->totalSourcesQuery(),
16848add1155SRico Sonntag            $this->totalRecordsQuery()
16858add1155SRico Sonntag        );
16868add1155SRico Sonntag    }
16878add1155SRico Sonntag
16888add1155SRico Sonntag    /**
16890dcd9387SGreg Roach     * @return string
16908add1155SRico Sonntag     */
16918add1155SRico Sonntag    public function totalNotesPercentage(): string
16928add1155SRico Sonntag    {
16938add1155SRico Sonntag        return $this->getPercentage(
16948add1155SRico Sonntag            $this->totalNotesQuery(),
16958add1155SRico Sonntag            $this->totalRecordsQuery()
16968add1155SRico Sonntag        );
16978add1155SRico Sonntag    }
16988add1155SRico Sonntag
16998add1155SRico Sonntag    /**
17000dcd9387SGreg Roach     * @return string
17018add1155SRico Sonntag     */
17028add1155SRico Sonntag    public function totalLivingPercentage(): string
17038add1155SRico Sonntag    {
17048add1155SRico Sonntag        return $this->getPercentage(
17058add1155SRico Sonntag            $this->totalLivingQuery(),
17068add1155SRico Sonntag            $this->totalIndividualsQuery()
17078add1155SRico Sonntag        );
17088add1155SRico Sonntag    }
17098add1155SRico Sonntag
17108add1155SRico Sonntag    /**
17110dcd9387SGreg Roach     * @return string
17128add1155SRico Sonntag     */
17138add1155SRico Sonntag    public function totalDeceasedPercentage(): string
17148add1155SRico Sonntag    {
17158add1155SRico Sonntag        return $this->getPercentage(
17168add1155SRico Sonntag            $this->totalDeceasedQuery(),
17178add1155SRico Sonntag            $this->totalIndividualsQuery()
17188add1155SRico Sonntag        );
17198add1155SRico Sonntag    }
17208add1155SRico Sonntag
17218add1155SRico Sonntag    /**
17220dcd9387SGreg Roach     * @return string
17238add1155SRico Sonntag     */
17248add1155SRico Sonntag    public function totalSexMalesPercentage(): string
17258add1155SRico Sonntag    {
17268add1155SRico Sonntag        return $this->getPercentage(
17278add1155SRico Sonntag            $this->totalSexMalesQuery(),
17288add1155SRico Sonntag            $this->totalIndividualsQuery()
17298add1155SRico Sonntag        );
17308add1155SRico Sonntag    }
17318add1155SRico Sonntag
17328add1155SRico Sonntag    /**
17330dcd9387SGreg Roach     * @return string
17348add1155SRico Sonntag     */
17358add1155SRico Sonntag    public function totalSexFemalesPercentage(): string
17368add1155SRico Sonntag    {
17378add1155SRico Sonntag        return $this->getPercentage(
17388add1155SRico Sonntag            $this->totalSexFemalesQuery(),
17398add1155SRico Sonntag            $this->totalIndividualsQuery()
17408add1155SRico Sonntag        );
17418add1155SRico Sonntag    }
17428add1155SRico Sonntag
17438add1155SRico Sonntag    /**
17440dcd9387SGreg Roach     * @return string
17458add1155SRico Sonntag     */
17468add1155SRico Sonntag    public function totalSexUnknownPercentage(): string
17478add1155SRico Sonntag    {
17488add1155SRico Sonntag        return $this->getPercentage(
17498add1155SRico Sonntag            $this->totalSexUnknownQuery(),
17508add1155SRico Sonntag            $this->totalIndividualsQuery()
17518add1155SRico Sonntag        );
17528add1155SRico Sonntag    }
17538add1155SRico Sonntag
17548add1155SRico Sonntag    /**
17558add1155SRico Sonntag     * Create a chart of common given names.
17568add1155SRico Sonntag     *
17578add1155SRico Sonntag     * @param string|null $color_from
17588add1155SRico Sonntag     * @param string|null $color_to
17598add1155SRico Sonntag     * @param int         $maxtoshow
17608add1155SRico Sonntag     *
17618add1155SRico Sonntag     * @return string
17628add1155SRico Sonntag     */
17638add1155SRico Sonntag    public function chartCommonGiven(
17648add1155SRico Sonntag        string $color_from = null,
17658add1155SRico Sonntag        string $color_to = null,
17668add1155SRico Sonntag        int $maxtoshow = 7
17678add1155SRico Sonntag    ): string {
17688add1155SRico Sonntag        $tot_indi = $this->totalIndividualsQuery();
17698add1155SRico Sonntag        $given    = $this->commonGivenQuery('B', 'chart', false, 1, $maxtoshow);
17708add1155SRico Sonntag
1771320f6a24SGreg Roach        if ($given === []) {
1772dd7dd2a1SRico Sonntag            return I18N::translate('This information is not available.');
177388de55fdSRico Sonntag        }
177488de55fdSRico Sonntag
177593ccd686SRico Sonntag        return (new ChartCommonGiven())
177688de55fdSRico Sonntag            ->chartCommonGiven($tot_indi, $given, $color_from, $color_to);
17778add1155SRico Sonntag    }
17788add1155SRico Sonntag
17798add1155SRico Sonntag    /**
17808add1155SRico Sonntag     * Create a chart of common surnames.
17818add1155SRico Sonntag     *
17828add1155SRico Sonntag     * @param string|null $color_from
17838add1155SRico Sonntag     * @param string|null $color_to
17848add1155SRico Sonntag     * @param int         $number_of_surnames
17858add1155SRico Sonntag     *
17868add1155SRico Sonntag     * @return string
17878add1155SRico Sonntag     */
17888add1155SRico Sonntag    public function chartCommonSurnames(
17898add1155SRico Sonntag        string $color_from = null,
17908add1155SRico Sonntag        string $color_to = null,
17918add1155SRico Sonntag        int $number_of_surnames = 10
17928add1155SRico Sonntag    ): string {
17938add1155SRico Sonntag        $tot_indi     = $this->totalIndividualsQuery();
17948add1155SRico Sonntag        $all_surnames = $this->topSurnames($number_of_surnames, 0);
17958add1155SRico Sonntag
1796320f6a24SGreg Roach        if ($all_surnames === []) {
1797dd7dd2a1SRico Sonntag            return I18N::translate('This information is not available.');
179888de55fdSRico Sonntag        }
179988de55fdSRico Sonntag
18008add1155SRico Sonntag        return (new ChartCommonSurname($this->tree))
180188de55fdSRico Sonntag            ->chartCommonSurnames($tot_indi, $all_surnames, $color_from, $color_to);
18028add1155SRico Sonntag    }
18038add1155SRico Sonntag
18048add1155SRico Sonntag    /**
18058add1155SRico Sonntag     * Create a chart showing mortality.
18068add1155SRico Sonntag     *
18078add1155SRico Sonntag     * @param string|null $color_living
18088add1155SRico Sonntag     * @param string|null $color_dead
18098add1155SRico Sonntag     *
18108add1155SRico Sonntag     * @return string
18118add1155SRico Sonntag     */
181288de55fdSRico Sonntag    public function chartMortality(string $color_living = null, string $color_dead = null): string
18138add1155SRico Sonntag    {
18148add1155SRico Sonntag        $tot_l = $this->totalLivingQuery();
18158add1155SRico Sonntag        $tot_d = $this->totalDeceasedQuery();
18168add1155SRico Sonntag
181793ccd686SRico Sonntag        return (new ChartMortality())
181888de55fdSRico Sonntag            ->chartMortality($tot_l, $tot_d, $color_living, $color_dead);
18198add1155SRico Sonntag    }
18208add1155SRico Sonntag
18218add1155SRico Sonntag    /**
18228add1155SRico Sonntag     * Create a chart showing individuals with/without sources.
18238add1155SRico Sonntag     *
18248add1155SRico Sonntag     * @param string|null $color_from
18258add1155SRico Sonntag     * @param string|null $color_to
18268add1155SRico Sonntag     *
18278add1155SRico Sonntag     * @return string
18288add1155SRico Sonntag     */
18298add1155SRico Sonntag    public function chartIndisWithSources(
18308add1155SRico Sonntag        string $color_from = null,
18318add1155SRico Sonntag        string $color_to = null
18328add1155SRico Sonntag    ): string {
18338add1155SRico Sonntag        $tot_indi        = $this->totalIndividualsQuery();
18348add1155SRico Sonntag        $tot_indi_source = $this->totalIndisWithSourcesQuery();
18358add1155SRico Sonntag
183693ccd686SRico Sonntag        return (new ChartIndividualWithSources())
183788de55fdSRico Sonntag            ->chartIndisWithSources($tot_indi, $tot_indi_source, $color_from, $color_to);
18388add1155SRico Sonntag    }
18398add1155SRico Sonntag
18408add1155SRico Sonntag    /**
18418add1155SRico Sonntag     * Create a chart of individuals with/without sources.
18428add1155SRico Sonntag     *
18438add1155SRico Sonntag     * @param string|null $color_from
18448add1155SRico Sonntag     * @param string|null $color_to
18458add1155SRico Sonntag     *
18468add1155SRico Sonntag     * @return string
18478add1155SRico Sonntag     */
18488add1155SRico Sonntag    public function chartFamsWithSources(
18498add1155SRico Sonntag        string $color_from = null,
18508add1155SRico Sonntag        string $color_to = null
18518add1155SRico Sonntag    ): string {
18528add1155SRico Sonntag        $tot_fam        = $this->totalFamiliesQuery();
18538add1155SRico Sonntag        $tot_fam_source = $this->totalFamsWithSourcesQuery();
18548add1155SRico Sonntag
185593ccd686SRico Sonntag        return (new ChartFamilyWithSources())
185688de55fdSRico Sonntag            ->chartFamsWithSources($tot_fam, $tot_fam_source, $color_from, $color_to);
18578add1155SRico Sonntag    }
18588add1155SRico Sonntag
18598add1155SRico Sonntag    /**
18600dcd9387SGreg Roach     * @param string|null $color_female
18610dcd9387SGreg Roach     * @param string|null $color_male
18620dcd9387SGreg Roach     * @param string|null $color_unknown
18630dcd9387SGreg Roach     *
18640dcd9387SGreg Roach     * @return string
18658add1155SRico Sonntag     */
18668add1155SRico Sonntag    public function chartSex(
18678add1155SRico Sonntag        string $color_female = null,
18688add1155SRico Sonntag        string $color_male = null,
18698add1155SRico Sonntag        string $color_unknown = null
18708add1155SRico Sonntag    ): string {
18718add1155SRico Sonntag        $tot_m = $this->totalSexMalesQuery();
18728add1155SRico Sonntag        $tot_f = $this->totalSexFemalesQuery();
18738add1155SRico Sonntag        $tot_u = $this->totalSexUnknownQuery();
18748add1155SRico Sonntag
187593ccd686SRico Sonntag        return (new ChartSex())
187688de55fdSRico Sonntag            ->chartSex($tot_m, $tot_f, $tot_u, $color_female, $color_male, $color_unknown);
18778add1155SRico Sonntag    }
187844cdc21eSGreg Roach
187944cdc21eSGreg Roach    /**
188044cdc21eSGreg Roach     * Query individuals, with their births and deaths.
188144cdc21eSGreg Roach     *
188244cdc21eSGreg Roach     * @param string $sex
188344cdc21eSGreg Roach     *
188444cdc21eSGreg Roach     * @return Builder
188544cdc21eSGreg Roach     */
1886e2cbf57aSGreg Roach    private function birthAndDeathQuery(string $sex): Builder
1887e2cbf57aSGreg Roach    {
188844cdc21eSGreg Roach        $query = DB::table('individuals')
188944cdc21eSGreg Roach            ->where('i_file', '=', $this->tree->id())
18900b5fd0a6SGreg Roach            ->join('dates AS birth', static function (JoinClause $join): void {
189144cdc21eSGreg Roach                $join
189244cdc21eSGreg Roach                    ->on('birth.d_file', '=', 'i_file')
189344cdc21eSGreg Roach                    ->on('birth.d_gid', '=', 'i_id');
189444cdc21eSGreg Roach            })
18950b5fd0a6SGreg Roach            ->join('dates AS death', static function (JoinClause $join): void {
189644cdc21eSGreg Roach                $join
189744cdc21eSGreg Roach                    ->on('death.d_file', '=', 'i_file')
189844cdc21eSGreg Roach                    ->on('death.d_gid', '=', 'i_id');
189944cdc21eSGreg Roach            })
190044cdc21eSGreg Roach            ->where('birth.d_fact', '=', 'BIRT')
190144cdc21eSGreg Roach            ->where('death.d_fact', '=', 'DEAT')
190244cdc21eSGreg Roach            ->whereColumn('death.d_julianday1', '>=', 'birth.d_julianday2')
190344cdc21eSGreg Roach            ->where('birth.d_julianday2', '<>', 0);
190444cdc21eSGreg Roach
190544cdc21eSGreg Roach        if ($sex === 'M' || $sex === 'F') {
190644cdc21eSGreg Roach            $query->where('i_sex', '=', $sex);
190744cdc21eSGreg Roach        }
190844cdc21eSGreg Roach
190944cdc21eSGreg Roach        return $query;
191044cdc21eSGreg Roach    }
19118add1155SRico Sonntag}
1912