xref: /webtrees/app/Statistics/Repository/IndividualRepository.php (revision 44cdc21ee12565886c949560a5bb58de01901f3e)
18add1155SRico Sonntag<?php
28add1155SRico Sonntag/**
38add1155SRico Sonntag * webtrees: online genealogy
48add1155SRico Sonntag * Copyright (C) 2018 webtrees development team
58add1155SRico Sonntag * This program is free software: you can redistribute it and/or modify
68add1155SRico Sonntag * it under the terms of the GNU General Public License as published by
78add1155SRico Sonntag * the Free Software Foundation, either version 3 of the License, or
88add1155SRico Sonntag * (at your option) any later version.
98add1155SRico Sonntag * This program is distributed in the hope that it will be useful,
108add1155SRico Sonntag * but WITHOUT ANY WARRANTY; without even the implied warranty of
118add1155SRico Sonntag * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
128add1155SRico Sonntag * GNU General Public License for more details.
138add1155SRico Sonntag * You should have received a copy of the GNU General Public License
148add1155SRico Sonntag * along with this program. If not, see <http://www.gnu.org/licenses/>.
158add1155SRico Sonntag */
168add1155SRico Sonntagdeclare(strict_types=1);
178add1155SRico Sonntag
188add1155SRico Sonntagnamespace Fisharebest\Webtrees\Statistics\Repository;
198add1155SRico Sonntag
208add1155SRico Sonntaguse Fisharebest\Webtrees\Auth;
218add1155SRico Sonntaguse Fisharebest\Webtrees\Database;
228add1155SRico Sonntaguse Fisharebest\Webtrees\Functions\FunctionsDate;
238add1155SRico Sonntaguse Fisharebest\Webtrees\Functions\FunctionsPrintLists;
248add1155SRico Sonntaguse Fisharebest\Webtrees\Gedcom;
258add1155SRico Sonntaguse Fisharebest\Webtrees\I18N;
268add1155SRico Sonntaguse Fisharebest\Webtrees\Individual;
278add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartAge;
288add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartBirth;
298add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartCommonGiven;
308add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartCommonSurname;
318add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartDeath;
328add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartFamilyWithSources;
338add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartIndividual;
348add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartMortality;
358add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartSex;
368add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Helper\Sql;
378add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Repository\Interfaces\IndividualRepositoryInterface;
388add1155SRico Sonntaguse Fisharebest\Webtrees\Tree;
398add1155SRico Sonntaguse Illuminate\Database\Capsule\Manager as DB;
403dc8167dSGreg Roachuse Illuminate\Database\Query\Builder;
418add1155SRico Sonntaguse Illuminate\Database\Query\JoinClause;
428add1155SRico Sonntag
438add1155SRico Sonntag/**
448add1155SRico Sonntag *
458add1155SRico Sonntag */
468add1155SRico Sonntagclass IndividualRepository implements IndividualRepositoryInterface
478add1155SRico Sonntag{
488add1155SRico Sonntag    /**
498add1155SRico Sonntag     * @var Tree
508add1155SRico Sonntag     */
518add1155SRico Sonntag    private $tree;
528add1155SRico Sonntag
538add1155SRico Sonntag    /**
548add1155SRico Sonntag     * Constructor.
558add1155SRico Sonntag     *
568add1155SRico Sonntag     * @param Tree $tree
578add1155SRico Sonntag     */
588add1155SRico Sonntag    public function __construct(Tree $tree)
598add1155SRico Sonntag    {
608add1155SRico Sonntag        $this->tree = $tree;
618add1155SRico Sonntag    }
628add1155SRico Sonntag
638add1155SRico Sonntag    /**
648add1155SRico Sonntag     * Run an SQL query and cache the result.
658add1155SRico Sonntag     *
668add1155SRico Sonntag     * @param string $sql
678add1155SRico Sonntag     *
688add1155SRico Sonntag     * @return \stdClass[]
698add1155SRico Sonntag     */
708add1155SRico Sonntag    private function runSql(string $sql): array
718add1155SRico Sonntag    {
728add1155SRico Sonntag        return Sql::runSql($sql);
738add1155SRico Sonntag    }
748add1155SRico Sonntag
758add1155SRico Sonntag    /**
768add1155SRico Sonntag     * Find common given names.
778add1155SRico Sonntag     *
788add1155SRico Sonntag     * @param string $sex
798add1155SRico Sonntag     * @param string $type
808add1155SRico Sonntag     * @param bool   $show_tot
818add1155SRico Sonntag     * @param int    $threshold
828add1155SRico Sonntag     * @param int    $maxtoshow
838add1155SRico Sonntag     *
848add1155SRico Sonntag     * @return string|int[]
858add1155SRico Sonntag     */
868add1155SRico Sonntag    private function commonGivenQuery(string $sex, string $type, bool $show_tot, int $threshold, int $maxtoshow)
878add1155SRico Sonntag    {
888add1155SRico Sonntag        switch ($sex) {
898add1155SRico Sonntag            case 'M':
908add1155SRico Sonntag                $sex_sql = "i_sex='M'";
918add1155SRico Sonntag                break;
928add1155SRico Sonntag            case 'F':
938add1155SRico Sonntag                $sex_sql = "i_sex='F'";
948add1155SRico Sonntag                break;
958add1155SRico Sonntag            case 'U':
968add1155SRico Sonntag                $sex_sql = "i_sex='U'";
978add1155SRico Sonntag                break;
988add1155SRico Sonntag            case 'B':
998add1155SRico Sonntag            default:
1008add1155SRico Sonntag                $sex_sql = "i_sex<>'U'";
1018add1155SRico Sonntag                break;
1028add1155SRico Sonntag        }
1038add1155SRico Sonntag
1048add1155SRico Sonntag        $ged_id = $this->tree->id();
1058add1155SRico Sonntag
1068add1155SRico Sonntag        $rows     = Database::prepare("SELECT n_givn, COUNT(*) AS num FROM `##name` JOIN `##individuals` ON (n_id=i_id AND n_file=i_file) WHERE n_file={$ged_id} AND n_type<>'_MARNM' AND n_givn NOT IN ('@P.N.', '') AND LENGTH(n_givn)>1 AND {$sex_sql} GROUP BY n_id, n_givn")
1078add1155SRico Sonntag            ->fetchAll();
1088add1155SRico Sonntag
1098add1155SRico Sonntag        $nameList = [];
1108add1155SRico Sonntag        foreach ($rows as $row) {
1118add1155SRico Sonntag            $row->num = (int) $row->num;
1128add1155SRico Sonntag
1138add1155SRico Sonntag            // Split “John Thomas” into “John” and “Thomas” and count against both totals
1148add1155SRico Sonntag            foreach (explode(' ', $row->n_givn) as $given) {
1158add1155SRico Sonntag                // Exclude initials and particles.
1168add1155SRico Sonntag                if (!preg_match('/^([A-Z]|[a-z]{1,3})$/', $given)) {
1178add1155SRico Sonntag                    if (\array_key_exists($given, $nameList)) {
1188add1155SRico Sonntag                        $nameList[$given] += (int) $row->num;
1198add1155SRico Sonntag                    } else {
1208add1155SRico Sonntag                        $nameList[$given] = (int) $row->num;
1218add1155SRico Sonntag                    }
1228add1155SRico Sonntag                }
1238add1155SRico Sonntag            }
1248add1155SRico Sonntag        }
1258add1155SRico Sonntag        arsort($nameList);
1268add1155SRico Sonntag        $nameList = \array_slice($nameList, 0, $maxtoshow);
1278add1155SRico Sonntag
1288add1155SRico Sonntag        foreach ($nameList as $given => $total) {
1298add1155SRico Sonntag            if ($total < $threshold) {
1308add1155SRico Sonntag                unset($nameList[$given]);
1318add1155SRico Sonntag            }
1328add1155SRico Sonntag        }
1338add1155SRico Sonntag
1348add1155SRico Sonntag        switch ($type) {
1358add1155SRico Sonntag            case 'chart':
1368add1155SRico Sonntag                return $nameList;
1378add1155SRico Sonntag
1388add1155SRico Sonntag            case 'table':
1398add1155SRico Sonntag                return view('lists/given-names-table', [
1408add1155SRico Sonntag                    'given_names' => $nameList,
1418add1155SRico Sonntag                ]);
1428add1155SRico Sonntag
1438add1155SRico Sonntag            case 'list':
1448add1155SRico Sonntag                return view('lists/given-names-list', [
1458add1155SRico Sonntag                    'given_names' => $nameList,
1468add1155SRico Sonntag                    'show_totals' => $show_tot,
1478add1155SRico Sonntag                ]);
1488add1155SRico Sonntag
1498add1155SRico Sonntag            case 'nolist':
1508add1155SRico Sonntag            default:
1518add1155SRico Sonntag                array_walk($nameList, function (int &$value, string $key) use ($show_tot): void {
1528add1155SRico Sonntag                    if ($show_tot) {
1538add1155SRico Sonntag                        $value = '<span dir="auto">' . e($key);
1548add1155SRico Sonntag                    } else {
1558add1155SRico Sonntag                        $value = '<span dir="auto">' . e($key) . ' (' . I18N::number($value) . ')';
1568add1155SRico Sonntag                    }
1578add1155SRico Sonntag                });
1588add1155SRico Sonntag
1598add1155SRico Sonntag                return implode(I18N::$list_separator, $nameList);
1608add1155SRico Sonntag        }
1618add1155SRico Sonntag    }
1628add1155SRico Sonntag
1638add1155SRico Sonntag    /**
1648add1155SRico Sonntag     * Find common give names.
1658add1155SRico Sonntag     *
1668add1155SRico Sonntag     * @param int $threshold
1678add1155SRico Sonntag     * @param int $maxtoshow
1688add1155SRico Sonntag     *
1698add1155SRico Sonntag     * @return string
1708add1155SRico Sonntag     */
1718add1155SRico Sonntag    public function commonGiven(int $threshold = 1, int $maxtoshow = 10): string
1728add1155SRico Sonntag    {
1738add1155SRico Sonntag        return $this->commonGivenQuery('B', 'nolist', false, $threshold, $maxtoshow);
1748add1155SRico Sonntag    }
1758add1155SRico Sonntag
1768add1155SRico Sonntag    /**
1778add1155SRico Sonntag     * Find common give names.
1788add1155SRico Sonntag     *
1798add1155SRico Sonntag     * @param int $threshold
1808add1155SRico Sonntag     * @param int $maxtoshow
1818add1155SRico Sonntag     *
1828add1155SRico Sonntag     * @return string
1838add1155SRico Sonntag     */
1848add1155SRico Sonntag    public function commonGivenTotals(int $threshold = 1, int $maxtoshow = 10): string
1858add1155SRico Sonntag    {
1868add1155SRico Sonntag        return $this->commonGivenQuery('B', 'nolist', true, $threshold, $maxtoshow);
1878add1155SRico Sonntag    }
1888add1155SRico Sonntag
1898add1155SRico Sonntag    /**
1908add1155SRico Sonntag     * Find common give names.
1918add1155SRico Sonntag     *
1928add1155SRico Sonntag     * @param int $threshold
1938add1155SRico Sonntag     * @param int $maxtoshow
1948add1155SRico Sonntag     *
1958add1155SRico Sonntag     * @return string
1968add1155SRico Sonntag     */
1978add1155SRico Sonntag    public function commonGivenList(int $threshold = 1, int $maxtoshow = 10): string
1988add1155SRico Sonntag    {
1998add1155SRico Sonntag        return $this->commonGivenQuery('B', 'list', false, $threshold, $maxtoshow);
2008add1155SRico Sonntag    }
2018add1155SRico Sonntag
2028add1155SRico Sonntag    /**
2038add1155SRico Sonntag     * Find common give names.
2048add1155SRico Sonntag     *
2058add1155SRico Sonntag     * @param int $threshold
2068add1155SRico Sonntag     * @param int $maxtoshow
2078add1155SRico Sonntag     *
2088add1155SRico Sonntag     * @return string
2098add1155SRico Sonntag     */
2108add1155SRico Sonntag    public function commonGivenListTotals(int $threshold = 1, int $maxtoshow = 10): string
2118add1155SRico Sonntag    {
2128add1155SRico Sonntag        return $this->commonGivenQuery('B', 'list', true, $threshold, $maxtoshow);
2138add1155SRico Sonntag    }
2148add1155SRico Sonntag
2158add1155SRico Sonntag    /**
2168add1155SRico Sonntag     * Find common give names.
2178add1155SRico Sonntag     *
2188add1155SRico Sonntag     * @param int $threshold
2198add1155SRico Sonntag     * @param int $maxtoshow
2208add1155SRico Sonntag     *
2218add1155SRico Sonntag     * @return string
2228add1155SRico Sonntag     */
2238add1155SRico Sonntag    public function commonGivenTable(int $threshold = 1, int $maxtoshow = 10): string
2248add1155SRico Sonntag    {
2258add1155SRico Sonntag        return $this->commonGivenQuery('B', 'table', false, $threshold, $maxtoshow);
2268add1155SRico Sonntag    }
2278add1155SRico Sonntag
2288add1155SRico Sonntag    /**
2298add1155SRico Sonntag     * Find common give names of females.
2308add1155SRico Sonntag     *
2318add1155SRico Sonntag     * @param int $threshold
2328add1155SRico Sonntag     * @param int $maxtoshow
2338add1155SRico Sonntag     *
2348add1155SRico Sonntag     * @return string
2358add1155SRico Sonntag     */
2368add1155SRico Sonntag    public function commonGivenFemale(int $threshold = 1, int $maxtoshow = 10): string
2378add1155SRico Sonntag    {
2388add1155SRico Sonntag        return $this->commonGivenQuery('F', 'nolist', false, $threshold, $maxtoshow);
2398add1155SRico Sonntag    }
2408add1155SRico Sonntag
2418add1155SRico Sonntag    /**
2428add1155SRico Sonntag     * Find common give names of females.
2438add1155SRico Sonntag     *
2448add1155SRico Sonntag     * @param int $threshold
2458add1155SRico Sonntag     * @param int $maxtoshow
2468add1155SRico Sonntag     *
2478add1155SRico Sonntag     * @return string
2488add1155SRico Sonntag     */
2498add1155SRico Sonntag    public function commonGivenFemaleTotals(int $threshold = 1, int $maxtoshow = 10): string
2508add1155SRico Sonntag    {
2518add1155SRico Sonntag        return $this->commonGivenQuery('F', 'nolist', true, $threshold, $maxtoshow);
2528add1155SRico Sonntag    }
2538add1155SRico Sonntag
2548add1155SRico Sonntag    /**
2558add1155SRico Sonntag     * Find common give names of females.
2568add1155SRico Sonntag     *
2578add1155SRico Sonntag     * @param int $threshold
2588add1155SRico Sonntag     * @param int $maxtoshow
2598add1155SRico Sonntag     *
2608add1155SRico Sonntag     * @return string
2618add1155SRico Sonntag     */
2628add1155SRico Sonntag    public function commonGivenFemaleList(int $threshold = 1, int $maxtoshow = 10): string
2638add1155SRico Sonntag    {
2648add1155SRico Sonntag        return $this->commonGivenQuery('F', 'list', false, $threshold, $maxtoshow);
2658add1155SRico Sonntag    }
2668add1155SRico Sonntag
2678add1155SRico Sonntag    /**
2688add1155SRico Sonntag     * Find common give names of females.
2698add1155SRico Sonntag     *
2708add1155SRico Sonntag     * @param int $threshold
2718add1155SRico Sonntag     * @param int $maxtoshow
2728add1155SRico Sonntag     *
2738add1155SRico Sonntag     * @return string
2748add1155SRico Sonntag     */
2758add1155SRico Sonntag    public function commonGivenFemaleListTotals(int $threshold = 1, int $maxtoshow = 10): string
2768add1155SRico Sonntag    {
2778add1155SRico Sonntag        return $this->commonGivenQuery('F', 'list', true, $threshold, $maxtoshow);
2788add1155SRico Sonntag    }
2798add1155SRico Sonntag
2808add1155SRico Sonntag    /**
2818add1155SRico Sonntag     * Find common give names of females.
2828add1155SRico Sonntag     *
2838add1155SRico Sonntag     * @param int $threshold
2848add1155SRico Sonntag     * @param int $maxtoshow
2858add1155SRico Sonntag     *
2868add1155SRico Sonntag     * @return string
2878add1155SRico Sonntag     */
2888add1155SRico Sonntag    public function commonGivenFemaleTable(int $threshold = 1, int $maxtoshow = 10): string
2898add1155SRico Sonntag    {
2908add1155SRico Sonntag        return $this->commonGivenQuery('F', 'table', false, $threshold, $maxtoshow);
2918add1155SRico Sonntag    }
2928add1155SRico Sonntag
2938add1155SRico Sonntag    /**
2948add1155SRico Sonntag     * Find common give names of males.
2958add1155SRico Sonntag     *
2968add1155SRico Sonntag     * @param int $threshold
2978add1155SRico Sonntag     * @param int $maxtoshow
2988add1155SRico Sonntag     *
2998add1155SRico Sonntag     * @return string
3008add1155SRico Sonntag     */
3018add1155SRico Sonntag    public function commonGivenMale(int $threshold = 1, int $maxtoshow = 10): string
3028add1155SRico Sonntag    {
3038add1155SRico Sonntag        return $this->commonGivenQuery('M', 'nolist', false, $threshold, $maxtoshow);
3048add1155SRico Sonntag    }
3058add1155SRico Sonntag
3068add1155SRico Sonntag    /**
3078add1155SRico Sonntag     * Find common give names of males.
3088add1155SRico Sonntag     *
3098add1155SRico Sonntag     * @param int $threshold
3108add1155SRico Sonntag     * @param int $maxtoshow
3118add1155SRico Sonntag     *
3128add1155SRico Sonntag     * @return string
3138add1155SRico Sonntag     */
3148add1155SRico Sonntag    public function commonGivenMaleTotals(int $threshold = 1, int $maxtoshow = 10): string
3158add1155SRico Sonntag    {
3168add1155SRico Sonntag        return $this->commonGivenQuery('M', 'nolist', true, $threshold, $maxtoshow);
3178add1155SRico Sonntag    }
3188add1155SRico Sonntag
3198add1155SRico Sonntag    /**
3208add1155SRico Sonntag     * Find common give names of males.
3218add1155SRico Sonntag     *
3228add1155SRico Sonntag     * @param int $threshold
3238add1155SRico Sonntag     * @param int $maxtoshow
3248add1155SRico Sonntag     *
3258add1155SRico Sonntag     * @return string
3268add1155SRico Sonntag     */
3278add1155SRico Sonntag    public function commonGivenMaleList(int $threshold = 1, int $maxtoshow = 10): string
3288add1155SRico Sonntag    {
3298add1155SRico Sonntag        return $this->commonGivenQuery('M', 'list', false, $threshold, $maxtoshow);
3308add1155SRico Sonntag    }
3318add1155SRico Sonntag
3328add1155SRico Sonntag    /**
3338add1155SRico Sonntag     * Find common give names of males.
3348add1155SRico Sonntag     *
3358add1155SRico Sonntag     * @param int $threshold
3368add1155SRico Sonntag     * @param int $maxtoshow
3378add1155SRico Sonntag     *
3388add1155SRico Sonntag     * @return string
3398add1155SRico Sonntag     */
3408add1155SRico Sonntag    public function commonGivenMaleListTotals(int $threshold = 1, int $maxtoshow = 10): string
3418add1155SRico Sonntag    {
3428add1155SRico Sonntag        return $this->commonGivenQuery('M', 'list', true, $threshold, $maxtoshow);
3438add1155SRico Sonntag    }
3448add1155SRico Sonntag
3458add1155SRico Sonntag    /**
3468add1155SRico Sonntag     * Find common give names of males.
3478add1155SRico Sonntag     *
3488add1155SRico Sonntag     * @param int $threshold
3498add1155SRico Sonntag     * @param int $maxtoshow
3508add1155SRico Sonntag     *
3518add1155SRico Sonntag     * @return string
3528add1155SRico Sonntag     */
3538add1155SRico Sonntag    public function commonGivenMaleTable(int $threshold = 1, int $maxtoshow = 10): string
3548add1155SRico Sonntag    {
3558add1155SRico Sonntag        return $this->commonGivenQuery('M', 'table', false, $threshold, $maxtoshow);
3568add1155SRico Sonntag    }
3578add1155SRico Sonntag
3588add1155SRico Sonntag    /**
3598add1155SRico Sonntag     * Find common give names of unknown sexes.
3608add1155SRico Sonntag     *
3618add1155SRico Sonntag     * @param int $threshold
3628add1155SRico Sonntag     * @param int $maxtoshow
3638add1155SRico Sonntag     *
3648add1155SRico Sonntag     * @return string
3658add1155SRico Sonntag     */
3668add1155SRico Sonntag    public function commonGivenUnknown(int $threshold = 1, int $maxtoshow = 10): string
3678add1155SRico Sonntag    {
3688add1155SRico Sonntag        return $this->commonGivenQuery('U', 'nolist', false, $threshold, $maxtoshow);
3698add1155SRico Sonntag    }
3708add1155SRico Sonntag
3718add1155SRico Sonntag    /**
3728add1155SRico Sonntag     * Find common give names of unknown sexes.
3738add1155SRico Sonntag     *
3748add1155SRico Sonntag     * @param int $threshold
3758add1155SRico Sonntag     * @param int $maxtoshow
3768add1155SRico Sonntag     *
3778add1155SRico Sonntag     * @return string
3788add1155SRico Sonntag     */
3798add1155SRico Sonntag    public function commonGivenUnknownTotals(int $threshold = 1, int $maxtoshow = 10): string
3808add1155SRico Sonntag    {
3818add1155SRico Sonntag        return $this->commonGivenQuery('U', 'nolist', true, $threshold, $maxtoshow);
3828add1155SRico Sonntag    }
3838add1155SRico Sonntag
3848add1155SRico Sonntag    /**
3858add1155SRico Sonntag     * Find common give names of unknown sexes.
3868add1155SRico Sonntag     *
3878add1155SRico Sonntag     * @param int $threshold
3888add1155SRico Sonntag     * @param int $maxtoshow
3898add1155SRico Sonntag     *
3908add1155SRico Sonntag     * @return string
3918add1155SRico Sonntag     */
3928add1155SRico Sonntag    public function commonGivenUnknownList(int $threshold = 1, int $maxtoshow = 10): string
3938add1155SRico Sonntag    {
3948add1155SRico Sonntag        return $this->commonGivenQuery('U', 'list', false, $threshold, $maxtoshow);
3958add1155SRico Sonntag    }
3968add1155SRico Sonntag
3978add1155SRico Sonntag    /**
3988add1155SRico Sonntag     * Find common give names of unknown sexes.
3998add1155SRico Sonntag     *
4008add1155SRico Sonntag     * @param int $threshold
4018add1155SRico Sonntag     * @param int $maxtoshow
4028add1155SRico Sonntag     *
4038add1155SRico Sonntag     * @return string
4048add1155SRico Sonntag     */
4058add1155SRico Sonntag    public function commonGivenUnknownListTotals(int $threshold = 1, int $maxtoshow = 10): string
4068add1155SRico Sonntag    {
4078add1155SRico Sonntag        return $this->commonGivenQuery('U', 'list', true, $threshold, $maxtoshow);
4088add1155SRico Sonntag    }
4098add1155SRico Sonntag
4108add1155SRico Sonntag    /**
4118add1155SRico Sonntag     * Find common give names of unknown sexes.
4128add1155SRico Sonntag     *
4138add1155SRico Sonntag     * @param int $threshold
4148add1155SRico Sonntag     * @param int $maxtoshow
4158add1155SRico Sonntag     *
4168add1155SRico Sonntag     * @return string
4178add1155SRico Sonntag     */
4188add1155SRico Sonntag    public function commonGivenUnknownTable(int $threshold = 1, int $maxtoshow = 10): string
4198add1155SRico Sonntag    {
4208add1155SRico Sonntag        return $this->commonGivenQuery('U', 'table', false, $threshold, $maxtoshow);
4218add1155SRico Sonntag    }
4228add1155SRico Sonntag
4238add1155SRico Sonntag    /**
4243dc8167dSGreg Roach     * Count the number of distinct given names (or the number of occurences of specific given names).
4258add1155SRico Sonntag     *
4263dc8167dSGreg Roach     * @param string[] ...$params
4278add1155SRico Sonntag     *
4288add1155SRico Sonntag     * @return string
4298add1155SRico Sonntag     */
4308add1155SRico Sonntag    public function totalGivennames(...$params): string
4318add1155SRico Sonntag    {
4323dc8167dSGreg Roach        $query = DB::table('name')
4333dc8167dSGreg Roach            ->where('n_file', '=', $this->tree->id());
4343dc8167dSGreg Roach
4353dc8167dSGreg Roach        if (empty($params)) {
4363dc8167dSGreg Roach            // Count number of distinct surnames
4373dc8167dSGreg Roach            $query
438*44cdc21eSGreg Roach                ->whereNotIn('n_givn', ['', '@N.N.'])
439*44cdc21eSGreg Roach                ->groupBy('n_givn');
4408add1155SRico Sonntag        } else {
4413dc8167dSGreg Roach            // Count number of occurences of specific surnames.
442*44cdc21eSGreg Roach            $query->whereIn('n_givn', $params);
4438add1155SRico Sonntag        }
4448add1155SRico Sonntag
4453dc8167dSGreg Roach        $count = $query->count();
4463dc8167dSGreg Roach
4473dc8167dSGreg Roach        return I18N::number($count);
4488add1155SRico Sonntag    }
4498add1155SRico Sonntag
4508add1155SRico Sonntag    /**
4513dc8167dSGreg Roach     * Count the number of distinct surnames (or the number of occurences of specific surnmaes).
4528add1155SRico Sonntag     *
4533dc8167dSGreg Roach     * @param string[] ...$params
4548add1155SRico Sonntag     *
4558add1155SRico Sonntag     * @return string
4568add1155SRico Sonntag     */
4578add1155SRico Sonntag    public function totalSurnames(...$params): string
4588add1155SRico Sonntag    {
4593dc8167dSGreg Roach        $query = DB::table('name')
4603dc8167dSGreg Roach            ->where('n_file', '=', $this->tree->id());
4613dc8167dSGreg Roach
4623dc8167dSGreg Roach        if (empty($params)) {
4633dc8167dSGreg Roach            // Count number of distinct surnames
4643dc8167dSGreg Roach            $query
4653dc8167dSGreg Roach                ->whereNotIn('n_surn', ['', '@N.N.'])
4663dc8167dSGreg Roach                ->groupBy('n_surn');
4678add1155SRico Sonntag        } else {
4683dc8167dSGreg Roach            // Count number of occurences of specific surnames.
4693dc8167dSGreg Roach            $query->whereIn('n_surn', $params);
4708add1155SRico Sonntag        }
4718add1155SRico Sonntag
4723dc8167dSGreg Roach        $count = $query->count();
4738add1155SRico Sonntag
4743dc8167dSGreg Roach        return I18N::number($count);
4758add1155SRico Sonntag    }
4768add1155SRico Sonntag
4778add1155SRico Sonntag    /**
4788add1155SRico Sonntag     * @param int $number_of_surnames
4798add1155SRico Sonntag     * @param int $threshold
4808add1155SRico Sonntag     *
4818add1155SRico Sonntag     * @return \stdClass[]
4828add1155SRico Sonntag     */
4838add1155SRico Sonntag    private function topSurnames(int $number_of_surnames, int $threshold): array
4848add1155SRico Sonntag    {
4858add1155SRico Sonntag        // Use the count of base surnames.
4868add1155SRico Sonntag        $top_surnames = Database::prepare(
4878add1155SRico Sonntag            "SELECT n_surn FROM `##name`" .
4888add1155SRico Sonntag            " WHERE n_file = :tree_id AND n_type != '_MARNM' AND n_surn NOT IN ('@N.N.', '')" .
4898add1155SRico Sonntag            " GROUP BY n_surn" .
4908add1155SRico Sonntag            " ORDER BY COUNT(n_surn) DESC" .
4918add1155SRico Sonntag            " LIMIT :limit"
4928add1155SRico Sonntag        )->execute([
4938add1155SRico Sonntag            'tree_id' => $this->tree->id(),
4948add1155SRico Sonntag            'limit'   => $number_of_surnames,
4958add1155SRico Sonntag        ])->fetchOneColumn();
4968add1155SRico Sonntag
4978add1155SRico Sonntag        $surnames = [];
4988add1155SRico Sonntag        foreach ($top_surnames as $top_surname) {
4998add1155SRico Sonntag            $variants = Database::prepare(
5008add1155SRico Sonntag                "SELECT n_surname COLLATE utf8_bin, COUNT(*) FROM `##name` WHERE n_file = :tree_id AND n_surn COLLATE :collate = :surname GROUP BY 1"
5018add1155SRico Sonntag            )->execute([
5028add1155SRico Sonntag                'collate' => I18N::collation(),
5038add1155SRico Sonntag                'surname' => $top_surname,
5048add1155SRico Sonntag                'tree_id' => $this->tree->id(),
5058add1155SRico Sonntag            ])->fetchAssoc();
5068add1155SRico Sonntag
5078add1155SRico Sonntag            if (array_sum($variants) > $threshold) {
5088add1155SRico Sonntag                $surnames[$top_surname] = $variants;
5098add1155SRico Sonntag            }
5108add1155SRico Sonntag        }
5118add1155SRico Sonntag
5128add1155SRico Sonntag        return $surnames;
5138add1155SRico Sonntag    }
5148add1155SRico Sonntag
5158add1155SRico Sonntag    /**
5168add1155SRico Sonntag     * Find common surnames.
5178add1155SRico Sonntag     *
5188add1155SRico Sonntag     * @return string
5198add1155SRico Sonntag     */
5208add1155SRico Sonntag    public function getCommonSurname(): string
5218add1155SRico Sonntag    {
5228add1155SRico Sonntag        $top_surname = $this->topSurnames(1, 0);
5238add1155SRico Sonntag        return implode(', ', array_keys(array_shift($top_surname)) ?? []);
5248add1155SRico Sonntag    }
5258add1155SRico Sonntag
5268add1155SRico Sonntag    /**
5278add1155SRico Sonntag     * Find common surnames.
5288add1155SRico Sonntag     *
5298add1155SRico Sonntag     * @param string $type
5308add1155SRico Sonntag     * @param bool   $show_tot
5318add1155SRico Sonntag     * @param int    $threshold
5328add1155SRico Sonntag     * @param int    $number_of_surnames
5338add1155SRico Sonntag     * @param string $sorting
5348add1155SRico Sonntag     *
5358add1155SRico Sonntag     * @return string
5368add1155SRico Sonntag     */
5378add1155SRico Sonntag    private function commonSurnamesQuery(
5388add1155SRico Sonntag        string $type,
5398add1155SRico Sonntag        bool $show_tot,
5408add1155SRico Sonntag        int $threshold,
5418add1155SRico Sonntag        int $number_of_surnames,
5428add1155SRico Sonntag        string $sorting
5438add1155SRico Sonntag    ): string {
5448add1155SRico Sonntag        $surnames = $this->topSurnames($number_of_surnames, $threshold);
5458add1155SRico Sonntag
5468add1155SRico Sonntag        switch ($sorting) {
5478add1155SRico Sonntag            default:
5488add1155SRico Sonntag            case 'alpha':
5498add1155SRico Sonntag                uksort($surnames, [I18N::class, 'strcasecmp']);
5508add1155SRico Sonntag                break;
5518add1155SRico Sonntag            case 'count':
5528add1155SRico Sonntag                break;
5538add1155SRico Sonntag            case 'rcount':
5548add1155SRico Sonntag                $surnames = array_reverse($surnames, true);
5558add1155SRico Sonntag                break;
5568add1155SRico Sonntag        }
5578add1155SRico Sonntag
5588add1155SRico Sonntag        return FunctionsPrintLists::surnameList(
5598add1155SRico Sonntag            $surnames,
5608add1155SRico Sonntag            ($type === 'list' ? 1 : 2),
5618add1155SRico Sonntag            $show_tot,
5628add1155SRico Sonntag            'individual-list',
5638add1155SRico Sonntag            $this->tree
5648add1155SRico Sonntag        );
5658add1155SRico Sonntag    }
5668add1155SRico Sonntag
5678add1155SRico Sonntag    /**
5688add1155SRico Sonntag     * Find common surnames.
5698add1155SRico Sonntag     *
5708add1155SRico Sonntag     * @param int    $threshold
5718add1155SRico Sonntag     * @param int    $number_of_surnames
5728add1155SRico Sonntag     * @param string $sorting
5738add1155SRico Sonntag     *
5748add1155SRico Sonntag     * @return string
5758add1155SRico Sonntag     */
5768add1155SRico Sonntag    public function commonSurnames(
5778add1155SRico Sonntag        int $threshold = 1,
5788add1155SRico Sonntag        int $number_of_surnames = 10,
5798add1155SRico Sonntag        string $sorting = 'alpha'
5808add1155SRico Sonntag    ): string {
5818add1155SRico Sonntag        return $this->commonSurnamesQuery('nolist', false, $threshold, $number_of_surnames, $sorting);
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 commonSurnamesTotals(
5948add1155SRico Sonntag        int $threshold = 1,
5958add1155SRico Sonntag        int $number_of_surnames = 10,
5968add1155SRico Sonntag        string $sorting = 'rcount'
5978add1155SRico Sonntag    ): string {
5988add1155SRico Sonntag        return $this->commonSurnamesQuery('nolist', true, $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 commonSurnamesList(
6118add1155SRico Sonntag        int $threshold = 1,
6128add1155SRico Sonntag        int $number_of_surnames = 10,
6138add1155SRico Sonntag        string $sorting = 'alpha'
6148add1155SRico Sonntag    ): string {
6158add1155SRico Sonntag        return $this->commonSurnamesQuery('list', false, $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 commonSurnamesListTotals(
6288add1155SRico Sonntag        int $threshold = 1,
6298add1155SRico Sonntag        int $number_of_surnames = 10,
6308add1155SRico Sonntag        string $sorting = 'rcount'
6318add1155SRico Sonntag    ): string {
6328add1155SRico Sonntag        return $this->commonSurnamesQuery('list', true, $threshold, $number_of_surnames, $sorting);
6338add1155SRico Sonntag    }
6348add1155SRico Sonntag
6358add1155SRico Sonntag    /**
6368add1155SRico Sonntag     * Get a list of birth dates.
6378add1155SRico Sonntag     *
6388add1155SRico Sonntag     * @param bool $sex
6398add1155SRico Sonntag     * @param int  $year1
6408add1155SRico Sonntag     * @param int  $year2
6418add1155SRico Sonntag     *
6428add1155SRico Sonntag     * @return array
6438add1155SRico Sonntag     */
6448add1155SRico Sonntag    public function statsBirthQuery(bool $sex = false, int $year1 = -1, int $year2 = -1): array
6458add1155SRico Sonntag    {
6468add1155SRico Sonntag        if ($sex) {
6478add1155SRico Sonntag            $sql =
6488add1155SRico Sonntag                "SELECT d_month, i_sex, COUNT(*) AS total FROM `##dates` " .
6498add1155SRico Sonntag                "JOIN `##individuals` ON d_file = i_file AND d_gid = i_id " .
6508add1155SRico Sonntag                "WHERE " .
6518add1155SRico Sonntag                "d_file={$this->tree->id()} AND " .
6528add1155SRico Sonntag                "d_fact='BIRT' AND " .
6538add1155SRico Sonntag                "d_type IN ('@#DGREGORIAN@', '@#DJULIAN@')";
6548add1155SRico Sonntag        } else {
6558add1155SRico Sonntag            $sql =
6568add1155SRico Sonntag                "SELECT d_month, COUNT(*) AS total FROM `##dates` " .
6578add1155SRico Sonntag                "WHERE " .
6588add1155SRico Sonntag                "d_file={$this->tree->id()} AND " .
6598add1155SRico Sonntag                "d_fact='BIRT' AND " .
6608add1155SRico Sonntag                "d_type IN ('@#DGREGORIAN@', '@#DJULIAN@')";
6618add1155SRico Sonntag        }
6628add1155SRico Sonntag
6638add1155SRico Sonntag        if ($year1 >= 0 && $year2 >= 0) {
6648add1155SRico Sonntag            $sql .= " AND d_year BETWEEN '{$year1}' AND '{$year2}'";
6658add1155SRico Sonntag        }
6668add1155SRico Sonntag
6678add1155SRico Sonntag        $sql .= " GROUP BY d_month";
6688add1155SRico Sonntag
6698add1155SRico Sonntag        if ($sex) {
6708add1155SRico Sonntag            $sql .= ", i_sex";
6718add1155SRico Sonntag        }
6728add1155SRico Sonntag
6738add1155SRico Sonntag        return $this->runSql($sql);
6748add1155SRico Sonntag    }
6758add1155SRico Sonntag
6768add1155SRico Sonntag    /**
6778add1155SRico Sonntag     * General query on births.
6788add1155SRico Sonntag     *
6798add1155SRico Sonntag     * @param string|null $size
6808add1155SRico Sonntag     * @param string|null $color_from
6818add1155SRico Sonntag     * @param string|null $color_to
6828add1155SRico Sonntag     *
6838add1155SRico Sonntag     * @return string
6848add1155SRico Sonntag     */
6858add1155SRico Sonntag    public function statsBirth(string $size = null, string $color_from = null, string $color_to = null): string
6868add1155SRico Sonntag    {
6878add1155SRico Sonntag        return (new ChartBirth($this->tree))
6888add1155SRico Sonntag            ->chartBirth($size, $color_from, $color_to);
6898add1155SRico Sonntag    }
6908add1155SRico Sonntag
6918add1155SRico Sonntag    /**
6928add1155SRico Sonntag     * Get a list of death dates.
6938add1155SRico Sonntag     *
6948add1155SRico Sonntag     * @param bool $sex
6958add1155SRico Sonntag     * @param int  $year1
6968add1155SRico Sonntag     * @param int  $year2
6978add1155SRico Sonntag     *
6988add1155SRico Sonntag     * @return array
6998add1155SRico Sonntag     */
7008add1155SRico Sonntag    public function statsDeathQuery(bool $sex = false, int $year1 = -1, int $year2 = -1): array
7018add1155SRico Sonntag    {
7028add1155SRico Sonntag        if ($sex) {
7038add1155SRico Sonntag            $sql =
7048add1155SRico Sonntag                "SELECT d_month, i_sex, COUNT(*) AS total FROM `##dates` " .
7058add1155SRico Sonntag                "JOIN `##individuals` ON d_file = i_file AND d_gid = i_id " .
7068add1155SRico Sonntag                "WHERE " .
7078add1155SRico Sonntag                "d_file={$this->tree->id()} AND " .
7088add1155SRico Sonntag                "d_fact='DEAT' AND " .
7098add1155SRico Sonntag                "d_type IN ('@#DGREGORIAN@', '@#DJULIAN@')";
7108add1155SRico Sonntag        } else {
7118add1155SRico Sonntag            $sql =
7128add1155SRico Sonntag                "SELECT d_month, COUNT(*) AS total FROM `##dates` " .
7138add1155SRico Sonntag                "WHERE " .
7148add1155SRico Sonntag                "d_file={$this->tree->id()} AND " .
7158add1155SRico Sonntag                "d_fact='DEAT' AND " .
7168add1155SRico Sonntag                "d_type IN ('@#DGREGORIAN@', '@#DJULIAN@')";
7178add1155SRico Sonntag        }
7188add1155SRico Sonntag
7198add1155SRico Sonntag        if ($year1 >= 0 && $year2 >= 0) {
7208add1155SRico Sonntag            $sql .= " AND d_year BETWEEN '{$year1}' AND '{$year2}'";
7218add1155SRico Sonntag        }
7228add1155SRico Sonntag
7238add1155SRico Sonntag        $sql .= " GROUP BY d_month";
7248add1155SRico Sonntag
7258add1155SRico Sonntag        if ($sex) {
7268add1155SRico Sonntag            $sql .= ", i_sex";
7278add1155SRico Sonntag        }
7288add1155SRico Sonntag
7298add1155SRico Sonntag        return $this->runSql($sql);
7308add1155SRico Sonntag    }
7318add1155SRico Sonntag
7328add1155SRico Sonntag    /**
7338add1155SRico Sonntag     * General query on deaths.
7348add1155SRico Sonntag     *
7358add1155SRico Sonntag     * @param string|null $size
7368add1155SRico Sonntag     * @param string|null $color_from
7378add1155SRico Sonntag     * @param string|null $color_to
7388add1155SRico Sonntag     *
7398add1155SRico Sonntag     * @return string
7408add1155SRico Sonntag     */
7418add1155SRico Sonntag    public function statsDeath(string $size = null, string $color_from = null, string $color_to = null): string
7428add1155SRico Sonntag    {
7438add1155SRico Sonntag        return (new ChartDeath($this->tree))
7448add1155SRico Sonntag            ->chartDeath($size, $color_from, $color_to);
7458add1155SRico Sonntag    }
7468add1155SRico Sonntag
7478add1155SRico Sonntag    /**
7488add1155SRico Sonntag     * General query on ages.
7498add1155SRico Sonntag     *
7508add1155SRico Sonntag     * @param string $related
7518add1155SRico Sonntag     * @param string $sex
7528add1155SRico Sonntag     * @param int    $year1
7538add1155SRico Sonntag     * @param int    $year2
7548add1155SRico Sonntag     *
7558add1155SRico Sonntag     * @return array|string
7568add1155SRico Sonntag     */
7578add1155SRico Sonntag    public function statsAgeQuery(string $related = 'BIRT', string $sex = 'BOTH', int $year1 = -1, int $year2 = -1)
7588add1155SRico Sonntag    {
759*44cdc21eSGreg Roach        $prefix = DB::connection()->getTablePrefix();
7608add1155SRico Sonntag
761*44cdc21eSGreg Roach        $query = $this->birthAndDeathQuery($sex);
7628add1155SRico Sonntag
7638add1155SRico Sonntag        if ($year1 >= 0 && $year2 >= 0) {
764*44cdc21eSGreg Roach            $query
765*44cdc21eSGreg Roach                ->whereIn('birth.d_type', ['@#DGREGORIAN@', '@#DJULIAN@'])
766*44cdc21eSGreg Roach                ->whereIn('death.d_type', ['@#DGREGORIAN@', '@#DJULIAN@']);
767*44cdc21eSGreg Roach
7688add1155SRico Sonntag            if ($related === 'BIRT') {
769*44cdc21eSGreg Roach                $query->whereBetween('birth.d_year', [$year1, $year2]);
7708add1155SRico Sonntag            } elseif ($related === 'DEAT') {
771*44cdc21eSGreg Roach                $query->whereBetween('death.d_year', [$year1, $year2]);
7728add1155SRico Sonntag            }
7738add1155SRico Sonntag        }
7748add1155SRico Sonntag
775*44cdc21eSGreg Roach        return $query
776*44cdc21eSGreg Roach            ->select(DB::raw($prefix . 'death.d_julianday2 - ' . $prefix . 'birth.d_julianday1 AS days'))
777*44cdc21eSGreg Roach            ->orderby('days', 'desc')
778*44cdc21eSGreg Roach            ->get()
779*44cdc21eSGreg Roach            ->all();
7808add1155SRico Sonntag    }
7818add1155SRico Sonntag
7828add1155SRico Sonntag    /**
7838add1155SRico Sonntag     * General query on ages.
7848add1155SRico Sonntag     *
7858add1155SRico Sonntag     * @param string $size
7868add1155SRico Sonntag     *
7878add1155SRico Sonntag     * @return string
7888add1155SRico Sonntag     */
7898add1155SRico Sonntag    public function statsAge(string $size = '230x250'): string
7908add1155SRico Sonntag    {
7918add1155SRico Sonntag        return (new ChartAge($this->tree))->chartAge($size);
7928add1155SRico Sonntag    }
7938add1155SRico Sonntag
7948add1155SRico Sonntag    /**
7958add1155SRico Sonntag     * Lifespan
7968add1155SRico Sonntag     *
7978add1155SRico Sonntag     * @param string $type
7988add1155SRico Sonntag     * @param string $sex
7998add1155SRico Sonntag     *
8008add1155SRico Sonntag     * @return string
8018add1155SRico Sonntag     */
8028add1155SRico Sonntag    private function longlifeQuery(string $type, string $sex): string
8038add1155SRico Sonntag    {
804*44cdc21eSGreg Roach        $prefix = DB::connection()->getTablePrefix();
8058add1155SRico Sonntag
806*44cdc21eSGreg Roach        $row = $this->birthAndDeathQuery($sex)
807*44cdc21eSGreg Roach            ->orderBy('days', 'desc')
808*44cdc21eSGreg Roach            ->select(['individuals.*', DB::raw($prefix . 'death.d_julianday2 - ' . $prefix . 'birth.d_julianday1 AS days')])
809*44cdc21eSGreg Roach            ->first();
810*44cdc21eSGreg Roach
811*44cdc21eSGreg Roach        if ($row === null) {
8128add1155SRico Sonntag            return '';
8138add1155SRico Sonntag        }
814*44cdc21eSGreg Roach
815*44cdc21eSGreg Roach        /** @var Individual $individual */
816*44cdc21eSGreg Roach        $individual = Individual::rowMapper()($row);
817*44cdc21eSGreg Roach
818*44cdc21eSGreg Roach        if (!$individual->canShow()) {
819*44cdc21eSGreg Roach            return I18N::translate('This information is private and cannot be shown.');
820*44cdc21eSGreg Roach        }
821*44cdc21eSGreg Roach
8228add1155SRico Sonntag        switch ($type) {
8238add1155SRico Sonntag            default:
8248add1155SRico Sonntag            case 'full':
825*44cdc21eSGreg Roach                return $individual->formatList();
8268add1155SRico Sonntag
827*44cdc21eSGreg Roach            case 'age':
828*44cdc21eSGreg Roach                return I18N::number((int) ($row->days / 365.25));
829*44cdc21eSGreg Roach
830*44cdc21eSGreg Roach            case 'name':
831*44cdc21eSGreg Roach                return '<a href="' . e($individual->url()) . '">' . $individual->getFullName() . '</a>';
832*44cdc21eSGreg Roach        }
8338add1155SRico Sonntag    }
8348add1155SRico Sonntag
8358add1155SRico Sonntag    /**
8368add1155SRico Sonntag     * Find the longest lived individual.
8378add1155SRico Sonntag     *
8388add1155SRico Sonntag     * @return string
8398add1155SRico Sonntag     */
8408add1155SRico Sonntag    public function longestLife(): string
8418add1155SRico Sonntag    {
8428add1155SRico Sonntag        return $this->longlifeQuery('full', 'BOTH');
8438add1155SRico Sonntag    }
8448add1155SRico Sonntag
8458add1155SRico Sonntag    /**
8468add1155SRico Sonntag     * Find the age of the longest lived individual.
8478add1155SRico Sonntag     *
8488add1155SRico Sonntag     * @return string
8498add1155SRico Sonntag     */
8508add1155SRico Sonntag    public function longestLifeAge(): string
8518add1155SRico Sonntag    {
8528add1155SRico Sonntag        return $this->longlifeQuery('age', 'BOTH');
8538add1155SRico Sonntag    }
8548add1155SRico Sonntag
8558add1155SRico Sonntag    /**
8568add1155SRico Sonntag     * Find the name of the longest lived individual.
8578add1155SRico Sonntag     *
8588add1155SRico Sonntag     * @return string
8598add1155SRico Sonntag     */
8608add1155SRico Sonntag    public function longestLifeName(): string
8618add1155SRico Sonntag    {
8628add1155SRico Sonntag        return $this->longlifeQuery('name', 'BOTH');
8638add1155SRico Sonntag    }
8648add1155SRico Sonntag
8658add1155SRico Sonntag    /**
8668add1155SRico Sonntag     * Find the longest lived female.
8678add1155SRico Sonntag     *
8688add1155SRico Sonntag     * @return string
8698add1155SRico Sonntag     */
8708add1155SRico Sonntag    public function longestLifeFemale(): string
8718add1155SRico Sonntag    {
8728add1155SRico Sonntag        return $this->longlifeQuery('full', 'F');
8738add1155SRico Sonntag    }
8748add1155SRico Sonntag
8758add1155SRico Sonntag    /**
8768add1155SRico Sonntag     * Find the age of the longest lived female.
8778add1155SRico Sonntag     *
8788add1155SRico Sonntag     * @return string
8798add1155SRico Sonntag     */
8808add1155SRico Sonntag    public function longestLifeFemaleAge(): string
8818add1155SRico Sonntag    {
8828add1155SRico Sonntag        return $this->longlifeQuery('age', 'F');
8838add1155SRico Sonntag    }
8848add1155SRico Sonntag
8858add1155SRico Sonntag    /**
8868add1155SRico Sonntag     * Find the name of the longest lived female.
8878add1155SRico Sonntag     *
8888add1155SRico Sonntag     * @return string
8898add1155SRico Sonntag     */
8908add1155SRico Sonntag    public function longestLifeFemaleName(): string
8918add1155SRico Sonntag    {
8928add1155SRico Sonntag        return $this->longlifeQuery('name', 'F');
8938add1155SRico Sonntag    }
8948add1155SRico Sonntag
8958add1155SRico Sonntag    /**
8968add1155SRico Sonntag     * Find the longest lived male.
8978add1155SRico Sonntag     *
8988add1155SRico Sonntag     * @return string
8998add1155SRico Sonntag     */
9008add1155SRico Sonntag    public function longestLifeMale(): string
9018add1155SRico Sonntag    {
9028add1155SRico Sonntag        return $this->longlifeQuery('full', 'M');
9038add1155SRico Sonntag    }
9048add1155SRico Sonntag
9058add1155SRico Sonntag    /**
9068add1155SRico Sonntag     * Find the age of the longest lived male.
9078add1155SRico Sonntag     *
9088add1155SRico Sonntag     * @return string
9098add1155SRico Sonntag     */
9108add1155SRico Sonntag    public function longestLifeMaleAge(): string
9118add1155SRico Sonntag    {
9128add1155SRico Sonntag        return $this->longlifeQuery('age', 'M');
9138add1155SRico Sonntag    }
9148add1155SRico Sonntag
9158add1155SRico Sonntag    /**
9168add1155SRico Sonntag     * Find the name of the longest lived male.
9178add1155SRico Sonntag     *
9188add1155SRico Sonntag     * @return string
9198add1155SRico Sonntag     */
9208add1155SRico Sonntag    public function longestLifeMaleName(): string
9218add1155SRico Sonntag    {
9228add1155SRico Sonntag        return $this->longlifeQuery('name', 'M');
9238add1155SRico Sonntag    }
9248add1155SRico Sonntag
9258add1155SRico Sonntag    /**
9268add1155SRico Sonntag     * Returns the calculated age the time of event.
9278add1155SRico Sonntag     *
9288add1155SRico Sonntag     * @param int $age The age from the database record
9298add1155SRico Sonntag     *
9308add1155SRico Sonntag     * @return string
9318add1155SRico Sonntag     */
9328add1155SRico Sonntag    private function calculateAge(int $age): string
9338add1155SRico Sonntag    {
9348add1155SRico Sonntag        if ((int) ($age / 365.25) > 0) {
9358add1155SRico Sonntag            $result = (int) ($age / 365.25) . 'y';
9368add1155SRico Sonntag        } elseif ((int) ($age / 30.4375) > 0) {
9378add1155SRico Sonntag            $result = (int) ($age / 30.4375) . 'm';
9388add1155SRico Sonntag        } else {
9398add1155SRico Sonntag            $result = $age . 'd';
9408add1155SRico Sonntag        }
9418add1155SRico Sonntag
9428add1155SRico Sonntag        return FunctionsDate::getAgeAtEvent($result);
9438add1155SRico Sonntag    }
9448add1155SRico Sonntag
9458add1155SRico Sonntag    /**
9468add1155SRico Sonntag     * Find the oldest individuals.
9478add1155SRico Sonntag     *
9488add1155SRico Sonntag     * @param string $sex
9498add1155SRico Sonntag     * @param int    $total
9508add1155SRico Sonntag     *
9518add1155SRico Sonntag     * @return array
9528add1155SRico Sonntag     */
9538add1155SRico Sonntag    private function topTenOldestQuery(string $sex, int $total): array
9548add1155SRico Sonntag    {
955*44cdc21eSGreg Roach        $prefix = DB::connection()->getTablePrefix();
9568add1155SRico Sonntag
957*44cdc21eSGreg Roach        $rows = $this->birthAndDeathQuery($sex)
958*44cdc21eSGreg Roach            ->groupBy(['i_id', 'i_file'])
959*44cdc21eSGreg Roach            ->orderBy('days', 'desc')
960*44cdc21eSGreg Roach            ->select(['individuals.*', DB::raw('MAX(' . $prefix . 'death.d_julianday2 - ' . $prefix . 'birth.d_julianday1) AS days')])
961*44cdc21eSGreg Roach            ->take($total)
962*44cdc21eSGreg Roach            ->get();
9638add1155SRico Sonntag
9648add1155SRico Sonntag        $top10 = [];
9658add1155SRico Sonntag        foreach ($rows as $row) {
966*44cdc21eSGreg Roach            /** @var Individual $individual */
967*44cdc21eSGreg Roach            $individual = Individual::rowMapper()($row);
9688add1155SRico Sonntag
969*44cdc21eSGreg Roach            if ($individual->canShow()) {
9708add1155SRico Sonntag                $top10[] = [
971*44cdc21eSGreg Roach                    'person' => $individual,
972*44cdc21eSGreg Roach                    'age'    => $this->calculateAge((int) $row->days),
9738add1155SRico Sonntag                ];
9748add1155SRico Sonntag            }
9758add1155SRico Sonntag        }
9768add1155SRico Sonntag
9778add1155SRico Sonntag        return $top10;
9788add1155SRico Sonntag    }
9798add1155SRico Sonntag
9808add1155SRico Sonntag    /**
9818add1155SRico Sonntag     * Find the oldest individuals.
9828add1155SRico Sonntag     *
9838add1155SRico Sonntag     * @param int $total
9848add1155SRico Sonntag     *
9858add1155SRico Sonntag     * @return string
9868add1155SRico Sonntag     */
9878add1155SRico Sonntag    public function topTenOldest(int $total = 10): string
9888add1155SRico Sonntag    {
9898add1155SRico Sonntag        $records = $this->topTenOldestQuery('BOTH', $total);
9908add1155SRico Sonntag
9918add1155SRico Sonntag        return view(
9928add1155SRico Sonntag            'statistics/individuals/top10-nolist',
9938add1155SRico Sonntag            [
9948add1155SRico Sonntag                'records' => $records,
9958add1155SRico Sonntag            ]
9968add1155SRico Sonntag        );
9978add1155SRico Sonntag    }
9988add1155SRico Sonntag
9998add1155SRico Sonntag    /**
10008add1155SRico Sonntag     * Find the oldest living individuals.
10018add1155SRico Sonntag     *
10028add1155SRico Sonntag     * @param int $total
10038add1155SRico Sonntag     *
10048add1155SRico Sonntag     * @return string
10058add1155SRico Sonntag     */
10068add1155SRico Sonntag    public function topTenOldestList(int $total = 10): string
10078add1155SRico Sonntag    {
10088add1155SRico Sonntag        $records = $this->topTenOldestQuery('BOTH', $total);
10098add1155SRico Sonntag
10108add1155SRico Sonntag        return view(
10118add1155SRico Sonntag            'statistics/individuals/top10-list',
10128add1155SRico Sonntag            [
10138add1155SRico Sonntag                'records' => $records,
10148add1155SRico Sonntag            ]
10158add1155SRico Sonntag        );
10168add1155SRico Sonntag    }
10178add1155SRico Sonntag
10188add1155SRico Sonntag    /**
10198add1155SRico Sonntag     * Find the oldest females.
10208add1155SRico Sonntag     *
10218add1155SRico Sonntag     * @param int $total
10228add1155SRico Sonntag     *
10238add1155SRico Sonntag     * @return string
10248add1155SRico Sonntag     */
10258add1155SRico Sonntag    public function topTenOldestFemale(int $total = 10): string
10268add1155SRico Sonntag    {
10278add1155SRico Sonntag        $records = $this->topTenOldestQuery('F', $total);
10288add1155SRico Sonntag
10298add1155SRico Sonntag        return view(
10308add1155SRico Sonntag            'statistics/individuals/top10-nolist',
10318add1155SRico Sonntag            [
10328add1155SRico Sonntag                'records' => $records,
10338add1155SRico Sonntag            ]
10348add1155SRico Sonntag        );
10358add1155SRico Sonntag    }
10368add1155SRico Sonntag
10378add1155SRico Sonntag    /**
10388add1155SRico Sonntag     * Find the oldest living females.
10398add1155SRico Sonntag     *
10408add1155SRico Sonntag     * @param int $total
10418add1155SRico Sonntag     *
10428add1155SRico Sonntag     * @return string
10438add1155SRico Sonntag     */
10448add1155SRico Sonntag    public function topTenOldestFemaleList(int $total = 10): string
10458add1155SRico Sonntag    {
10468add1155SRico Sonntag        $records = $this->topTenOldestQuery('F', $total);
10478add1155SRico Sonntag
10488add1155SRico Sonntag        return view(
10498add1155SRico Sonntag            'statistics/individuals/top10-list',
10508add1155SRico Sonntag            [
10518add1155SRico Sonntag                'records' => $records,
10528add1155SRico Sonntag            ]
10538add1155SRico Sonntag        );
10548add1155SRico Sonntag    }
10558add1155SRico Sonntag
10568add1155SRico Sonntag    /**
10578add1155SRico Sonntag     * Find the longest lived males.
10588add1155SRico Sonntag     *
10598add1155SRico Sonntag     * @param int $total
10608add1155SRico Sonntag     *
10618add1155SRico Sonntag     * @return string
10628add1155SRico Sonntag     */
10638add1155SRico Sonntag    public function topTenOldestMale(int $total = 10): string
10648add1155SRico Sonntag    {
10658add1155SRico Sonntag        $records = $this->topTenOldestQuery('M', $total);
10668add1155SRico Sonntag
10678add1155SRico Sonntag        return view(
10688add1155SRico Sonntag            'statistics/individuals/top10-nolist',
10698add1155SRico Sonntag            [
10708add1155SRico Sonntag                'records' => $records,
10718add1155SRico Sonntag            ]
10728add1155SRico Sonntag        );
10738add1155SRico Sonntag    }
10748add1155SRico Sonntag
10758add1155SRico Sonntag    /**
10768add1155SRico Sonntag     * Find the longest lived males.
10778add1155SRico Sonntag     *
10788add1155SRico Sonntag     * @param int $total
10798add1155SRico Sonntag     *
10808add1155SRico Sonntag     * @return string
10818add1155SRico Sonntag     */
10828add1155SRico Sonntag    public function topTenOldestMaleList(int $total = 10): string
10838add1155SRico Sonntag    {
10848add1155SRico Sonntag        $records = $this->topTenOldestQuery('M', $total);
10858add1155SRico Sonntag
10868add1155SRico Sonntag        return view(
10878add1155SRico Sonntag            'statistics/individuals/top10-list',
10888add1155SRico Sonntag            [
10898add1155SRico Sonntag                'records' => $records,
10908add1155SRico Sonntag            ]
10918add1155SRico Sonntag        );
10928add1155SRico Sonntag    }
10938add1155SRico Sonntag
10948add1155SRico Sonntag    /**
10958add1155SRico Sonntag     * Find the oldest living individuals.
10968add1155SRico Sonntag     *
10978add1155SRico Sonntag     * @param string $sex
10988add1155SRico Sonntag     * @param int    $total
10998add1155SRico Sonntag     *
11008add1155SRico Sonntag     * @return array
11018add1155SRico Sonntag     */
11028add1155SRico Sonntag    private function topTenOldestAliveQuery(string $sex = 'BOTH', int $total = 10): array
11038add1155SRico Sonntag    {
11048add1155SRico Sonntag        if ($sex === 'F') {
11058add1155SRico Sonntag            $sex_search = " AND i_sex='F'";
11068add1155SRico Sonntag        } elseif ($sex === 'M') {
11078add1155SRico Sonntag            $sex_search = " AND i_sex='M'";
11088add1155SRico Sonntag        } else {
11098add1155SRico Sonntag            $sex_search = '';
11108add1155SRico Sonntag        }
11118add1155SRico Sonntag
11128add1155SRico Sonntag        $rows = $this->runSql(
11138add1155SRico Sonntag            "SELECT" .
11148add1155SRico Sonntag            " birth.d_gid AS id," .
11158add1155SRico Sonntag            " MIN(birth.d_julianday1) AS age" .
11168add1155SRico Sonntag            " FROM" .
11178add1155SRico Sonntag            " `##dates` AS birth," .
11188add1155SRico Sonntag            " `##individuals` AS indi" .
11198add1155SRico Sonntag            " WHERE" .
11208add1155SRico Sonntag            " indi.i_id=birth.d_gid AND" .
11218add1155SRico Sonntag            " indi.i_gedcom NOT REGEXP '\\n1 (" . implode('|', Gedcom::DEATH_EVENTS) . ")' AND" .
11228add1155SRico Sonntag            " birth.d_file={$this->tree->id()} AND" .
11238add1155SRico Sonntag            " birth.d_fact='BIRT' AND" .
11248add1155SRico Sonntag            " birth.d_file=indi.i_file AND" .
11258add1155SRico Sonntag            " birth.d_julianday1<>0" .
11268add1155SRico Sonntag            $sex_search .
11278add1155SRico Sonntag            " GROUP BY id" .
11288add1155SRico Sonntag            " ORDER BY age" .
11298add1155SRico Sonntag            " ASC LIMIT " . $total
11308add1155SRico Sonntag        );
11318add1155SRico Sonntag
11328add1155SRico Sonntag        $top10 = [];
11338add1155SRico Sonntag
11348add1155SRico Sonntag        foreach ($rows as $row) {
11358add1155SRico Sonntag            $person = Individual::getInstance($row->id, $this->tree);
11368add1155SRico Sonntag
11378add1155SRico Sonntag            $top10[] = [
11388add1155SRico Sonntag                'person' => $person,
11398add1155SRico Sonntag                'age'    => $this->calculateAge(WT_CLIENT_JD - ((int) $row->age)),
11408add1155SRico Sonntag            ];
11418add1155SRico Sonntag        }
11428add1155SRico Sonntag
11438add1155SRico Sonntag        return $top10;
11448add1155SRico Sonntag    }
11458add1155SRico Sonntag
11468add1155SRico Sonntag    /**
11478add1155SRico Sonntag     * Find the oldest living individuals.
11488add1155SRico Sonntag     *
11498add1155SRico Sonntag     * @param int $total
11508add1155SRico Sonntag     *
11518add1155SRico Sonntag     * @return string
11528add1155SRico Sonntag     */
11538add1155SRico Sonntag    public function topTenOldestAlive(int $total = 10): string
11548add1155SRico Sonntag    {
11558add1155SRico Sonntag        if (!Auth::isMember($this->tree)) {
11568add1155SRico Sonntag            return I18N::translate('This information is private and cannot be shown.');
11578add1155SRico Sonntag        }
11588add1155SRico Sonntag
11598add1155SRico Sonntag        $records = $this->topTenOldestAliveQuery('BOTH', $total);
11608add1155SRico Sonntag
11618add1155SRico Sonntag        return view(
11628add1155SRico Sonntag            'statistics/individuals/top10-nolist',
11638add1155SRico Sonntag            [
11648add1155SRico Sonntag                'records' => $records,
11658add1155SRico Sonntag            ]
11668add1155SRico Sonntag        );
11678add1155SRico Sonntag    }
11688add1155SRico Sonntag
11698add1155SRico Sonntag    /**
11708add1155SRico Sonntag     * Find the oldest living individuals.
11718add1155SRico Sonntag     *
11728add1155SRico Sonntag     * @param int $total
11738add1155SRico Sonntag     *
11748add1155SRico Sonntag     * @return string
11758add1155SRico Sonntag     */
11768add1155SRico Sonntag    public function topTenOldestListAlive(int $total = 10): string
11778add1155SRico Sonntag    {
11788add1155SRico Sonntag        if (!Auth::isMember($this->tree)) {
11798add1155SRico Sonntag            return I18N::translate('This information is private and cannot be shown.');
11808add1155SRico Sonntag        }
11818add1155SRico Sonntag
11828add1155SRico Sonntag        $records = $this->topTenOldestAliveQuery('BOTH', $total);
11838add1155SRico Sonntag
11848add1155SRico Sonntag        return view(
11858add1155SRico Sonntag            'statistics/individuals/top10-list',
11868add1155SRico Sonntag            [
11878add1155SRico Sonntag                'records' => $records,
11888add1155SRico Sonntag            ]
11898add1155SRico Sonntag        );
11908add1155SRico Sonntag    }
11918add1155SRico Sonntag
11928add1155SRico Sonntag    /**
11938add1155SRico Sonntag     * Find the oldest living females.
11948add1155SRico Sonntag     *
11958add1155SRico Sonntag     * @param int $total
11968add1155SRico Sonntag     *
11978add1155SRico Sonntag     * @return string
11988add1155SRico Sonntag     */
11998add1155SRico Sonntag    public function topTenOldestFemaleAlive(int $total = 10): string
12008add1155SRico Sonntag    {
12018add1155SRico Sonntag        if (!Auth::isMember($this->tree)) {
12028add1155SRico Sonntag            return I18N::translate('This information is private and cannot be shown.');
12038add1155SRico Sonntag        }
12048add1155SRico Sonntag
12058add1155SRico Sonntag        $records = $this->topTenOldestAliveQuery('F', $total);
12068add1155SRico Sonntag
12078add1155SRico Sonntag        return view(
12088add1155SRico Sonntag            'statistics/individuals/top10-nolist',
12098add1155SRico Sonntag            [
12108add1155SRico Sonntag                'records' => $records,
12118add1155SRico Sonntag            ]
12128add1155SRico Sonntag        );
12138add1155SRico Sonntag    }
12148add1155SRico Sonntag
12158add1155SRico Sonntag    /**
12168add1155SRico Sonntag     * Find the oldest living females.
12178add1155SRico Sonntag     *
12188add1155SRico Sonntag     * @param int $total
12198add1155SRico Sonntag     *
12208add1155SRico Sonntag     * @return string
12218add1155SRico Sonntag     */
12228add1155SRico Sonntag    public function topTenOldestFemaleListAlive(int $total = 10): string
12238add1155SRico Sonntag    {
12248add1155SRico Sonntag        if (!Auth::isMember($this->tree)) {
12258add1155SRico Sonntag            return I18N::translate('This information is private and cannot be shown.');
12268add1155SRico Sonntag        }
12278add1155SRico Sonntag
12288add1155SRico Sonntag        $records = $this->topTenOldestAliveQuery('F', $total);
12298add1155SRico Sonntag
12308add1155SRico Sonntag        return view(
12318add1155SRico Sonntag            'statistics/individuals/top10-list',
12328add1155SRico Sonntag            [
12338add1155SRico Sonntag                'records' => $records,
12348add1155SRico Sonntag            ]
12358add1155SRico Sonntag        );
12368add1155SRico Sonntag    }
12378add1155SRico Sonntag
12388add1155SRico Sonntag    /**
12398add1155SRico Sonntag     * Find the longest lived living males.
12408add1155SRico Sonntag     *
12418add1155SRico Sonntag     * @param int $total
12428add1155SRico Sonntag     *
12438add1155SRico Sonntag     * @return string
12448add1155SRico Sonntag     */
12458add1155SRico Sonntag    public function topTenOldestMaleAlive(int $total = 10): string
12468add1155SRico Sonntag    {
12478add1155SRico Sonntag        if (!Auth::isMember($this->tree)) {
12488add1155SRico Sonntag            return I18N::translate('This information is private and cannot be shown.');
12498add1155SRico Sonntag        }
12508add1155SRico Sonntag
12518add1155SRico Sonntag        $records = $this->topTenOldestAliveQuery('M', $total);
12528add1155SRico Sonntag
12538add1155SRico Sonntag        return view(
12548add1155SRico Sonntag            'statistics/individuals/top10-nolist',
12558add1155SRico Sonntag            [
12568add1155SRico Sonntag                'records' => $records,
12578add1155SRico Sonntag            ]
12588add1155SRico Sonntag        );
12598add1155SRico Sonntag    }
12608add1155SRico Sonntag
12618add1155SRico Sonntag    /**
12628add1155SRico Sonntag     * Find the longest lived living males.
12638add1155SRico Sonntag     *
12648add1155SRico Sonntag     * @param int $total
12658add1155SRico Sonntag     *
12668add1155SRico Sonntag     * @return string
12678add1155SRico Sonntag     */
12688add1155SRico Sonntag    public function topTenOldestMaleListAlive(int $total = 10): string
12698add1155SRico Sonntag    {
12708add1155SRico Sonntag        if (!Auth::isMember($this->tree)) {
12718add1155SRico Sonntag            return I18N::translate('This information is private and cannot be shown.');
12728add1155SRico Sonntag        }
12738add1155SRico Sonntag
12748add1155SRico Sonntag        $records = $this->topTenOldestAliveQuery('M', $total);
12758add1155SRico Sonntag
12768add1155SRico Sonntag        return view(
12778add1155SRico Sonntag            'statistics/individuals/top10-list',
12788add1155SRico Sonntag            [
12798add1155SRico Sonntag                'records' => $records,
12808add1155SRico Sonntag            ]
12818add1155SRico Sonntag        );
12828add1155SRico Sonntag    }
12838add1155SRico Sonntag
12848add1155SRico Sonntag    /**
12858add1155SRico Sonntag     * Find the average lifespan.
12868add1155SRico Sonntag     *
12878add1155SRico Sonntag     * @param string $sex
12888add1155SRico Sonntag     * @param bool   $show_years
12898add1155SRico Sonntag     *
12908add1155SRico Sonntag     * @return string
12918add1155SRico Sonntag     */
12928add1155SRico Sonntag    private function averageLifespanQuery(string $sex = 'BOTH', bool $show_years = false): string
12938add1155SRico Sonntag    {
1294*44cdc21eSGreg Roach        $prefix = DB::connection()->getTablePrefix();
12958add1155SRico Sonntag
1296*44cdc21eSGreg Roach        $days = (int) $this->birthAndDeathQuery($sex)
1297*44cdc21eSGreg Roach            ->groupBy('i_id')
1298*44cdc21eSGreg Roach            ->select(DB::raw('AVG(' . $prefix . 'death.d_julianday1 - ' . $prefix . 'birth.d_julianday2) AS days'))
1299*44cdc21eSGreg Roach            ->value('days');
13008add1155SRico Sonntag
13018add1155SRico Sonntag        if ($show_years) {
1302*44cdc21eSGreg Roach            return $this->calculateAge($days);
13038add1155SRico Sonntag        }
13048add1155SRico Sonntag
1305*44cdc21eSGreg Roach        return I18N::number((int) ($days / 365.25));
13068add1155SRico Sonntag    }
13078add1155SRico Sonntag
13088add1155SRico Sonntag    /**
13098add1155SRico Sonntag     * Find the average lifespan.
13108add1155SRico Sonntag     *
13118add1155SRico Sonntag     * @param bool $show_years
13128add1155SRico Sonntag     *
13138add1155SRico Sonntag     * @return string
13148add1155SRico Sonntag     */
13158add1155SRico Sonntag    public function averageLifespan($show_years = false): string
13168add1155SRico Sonntag    {
13178add1155SRico Sonntag        return $this->averageLifespanQuery('BOTH', $show_years);
13188add1155SRico Sonntag    }
13198add1155SRico Sonntag
13208add1155SRico Sonntag    /**
13218add1155SRico Sonntag     * Find the average lifespan of females.
13228add1155SRico Sonntag     *
13238add1155SRico Sonntag     * @param bool $show_years
13248add1155SRico Sonntag     *
13258add1155SRico Sonntag     * @return string
13268add1155SRico Sonntag     */
13278add1155SRico Sonntag    public function averageLifespanFemale($show_years = false): string
13288add1155SRico Sonntag    {
13298add1155SRico Sonntag        return $this->averageLifespanQuery('F', $show_years);
13308add1155SRico Sonntag    }
13318add1155SRico Sonntag
13328add1155SRico Sonntag    /**
13338add1155SRico Sonntag     * Find the average male lifespan.
13348add1155SRico Sonntag     *
13358add1155SRico Sonntag     * @param bool $show_years
13368add1155SRico Sonntag     *
13378add1155SRico Sonntag     * @return string
13388add1155SRico Sonntag     */
13398add1155SRico Sonntag    public function averageLifespanMale($show_years = false): string
13408add1155SRico Sonntag    {
13418add1155SRico Sonntag        return $this->averageLifespanQuery('M', $show_years);
13428add1155SRico Sonntag    }
13438add1155SRico Sonntag
13448add1155SRico Sonntag    /**
13458add1155SRico Sonntag     * Convert totals into percentages.
13468add1155SRico Sonntag     *
13478add1155SRico Sonntag     * @param int $count
13488add1155SRico Sonntag     * @param int $total
13498add1155SRico Sonntag     *
13508add1155SRico Sonntag     * @return string
13518add1155SRico Sonntag     */
13528add1155SRico Sonntag    private function getPercentage(int $count, int $total): string
13538add1155SRico Sonntag    {
13548add1155SRico Sonntag        return I18N::percentage($count / $total, 1);
13558add1155SRico Sonntag    }
13568add1155SRico Sonntag
13578add1155SRico Sonntag    /**
13588add1155SRico Sonntag     * Returns how many individuals exist in the tree.
13598add1155SRico Sonntag     *
13608add1155SRico Sonntag     * @return int
13618add1155SRico Sonntag     */
13628add1155SRico Sonntag    private function totalIndividualsQuery(): int
13638add1155SRico Sonntag    {
13648add1155SRico Sonntag        return DB::table('individuals')
13658add1155SRico Sonntag            ->where('i_file', '=', $this->tree->id())
13668add1155SRico Sonntag            ->count();
13678add1155SRico Sonntag    }
13688add1155SRico Sonntag
13698add1155SRico Sonntag    /**
13708add1155SRico Sonntag     * Count the number of living individuals.
13718add1155SRico Sonntag     *
13728add1155SRico Sonntag     * The totalLiving/totalDeceased queries assume that every dead person will
13738add1155SRico Sonntag     * have a DEAT record. It will not include individuals who were born more
13748add1155SRico Sonntag     * than MAX_ALIVE_AGE years ago, and who have no DEAT record.
13758add1155SRico Sonntag     * A good reason to run the “Add missing DEAT records” batch-update!
13768add1155SRico Sonntag     *
13778add1155SRico Sonntag     * @return int
13788add1155SRico Sonntag     */
13798add1155SRico Sonntag    private function totalLivingQuery(): int
13808add1155SRico Sonntag    {
13813dc8167dSGreg Roach        $query = DB::table('individuals')
13823dc8167dSGreg Roach            ->where('i_file', '=', $this->tree->id());
13833dc8167dSGreg Roach
13843dc8167dSGreg Roach        foreach (Gedcom::DEATH_EVENTS as $death_event) {
13853dc8167dSGreg Roach            $query->where('i_gedcom', 'NOT LIKE', '%\n1 ' . $death_event);
13863dc8167dSGreg Roach        }
13873dc8167dSGreg Roach
13883dc8167dSGreg Roach        return $query->count();
13898add1155SRico Sonntag    }
13908add1155SRico Sonntag
13918add1155SRico Sonntag    /**
13928add1155SRico Sonntag     * Count the number of dead individuals.
13938add1155SRico Sonntag     *
13948add1155SRico Sonntag     * @return int
13958add1155SRico Sonntag     */
13968add1155SRico Sonntag    private function totalDeceasedQuery(): int
13978add1155SRico Sonntag    {
13988add1155SRico Sonntag        return DB::table('individuals')
13998add1155SRico Sonntag            ->where('i_file', '=', $this->tree->id())
14003dc8167dSGreg Roach            ->where(function (Builder $query): void {
14013dc8167dSGreg Roach                foreach (Gedcom::DEATH_EVENTS as $death_event) {
14023dc8167dSGreg Roach                    $query->orWhere('i_gedcom', 'NOT LIKE', '%\n1 ' . $death_event);
14033dc8167dSGreg Roach                }
14043dc8167dSGreg Roach            })
14058add1155SRico Sonntag            ->count();
14068add1155SRico Sonntag    }
14078add1155SRico Sonntag
14088add1155SRico Sonntag    /**
14098add1155SRico Sonntag     * Returns the total count of a specific sex.
14108add1155SRico Sonntag     *
14118add1155SRico Sonntag     * @param string $sex The sex to query
14128add1155SRico Sonntag     *
14138add1155SRico Sonntag     * @return int
14148add1155SRico Sonntag     */
14158add1155SRico Sonntag    private function getTotalSexQuery(string $sex): int
14168add1155SRico Sonntag    {
14178add1155SRico Sonntag        return DB::table('individuals')
14188add1155SRico Sonntag            ->where('i_file', '=', $this->tree->id())
14198add1155SRico Sonntag            ->where('i_sex', '=', $sex)
14208add1155SRico Sonntag            ->count();
14218add1155SRico Sonntag    }
14228add1155SRico Sonntag
14238add1155SRico Sonntag    /**
14248add1155SRico Sonntag     * Returns the total number of males.
14258add1155SRico Sonntag     *
14268add1155SRico Sonntag     * @return int
14278add1155SRico Sonntag     */
14288add1155SRico Sonntag    private function totalSexMalesQuery(): int
14298add1155SRico Sonntag    {
14308add1155SRico Sonntag        return $this->getTotalSexQuery('M');
14318add1155SRico Sonntag    }
14328add1155SRico Sonntag
14338add1155SRico Sonntag    /**
14348add1155SRico Sonntag     * Returns the total number of females.
14358add1155SRico Sonntag     *
14368add1155SRico Sonntag     * @return int
14378add1155SRico Sonntag     */
14388add1155SRico Sonntag    private function totalSexFemalesQuery(): int
14398add1155SRico Sonntag    {
14408add1155SRico Sonntag        return $this->getTotalSexQuery('F');
14418add1155SRico Sonntag    }
14428add1155SRico Sonntag
14438add1155SRico Sonntag    /**
14448add1155SRico Sonntag     * Returns the total number of individuals with unknown sex.
14458add1155SRico Sonntag     *
14468add1155SRico Sonntag     * @return int
14478add1155SRico Sonntag     */
14488add1155SRico Sonntag    private function totalSexUnknownQuery(): int
14498add1155SRico Sonntag    {
14508add1155SRico Sonntag        return $this->getTotalSexQuery('U');
14518add1155SRico Sonntag    }
14528add1155SRico Sonntag
14538add1155SRico Sonntag    /**
14548add1155SRico Sonntag     * Count the total families.
14558add1155SRico Sonntag     *
14568add1155SRico Sonntag     * @return int
14578add1155SRico Sonntag     */
14588add1155SRico Sonntag    private function totalFamiliesQuery(): int
14598add1155SRico Sonntag    {
14608add1155SRico Sonntag        return DB::table('families')
14618add1155SRico Sonntag            ->where('f_file', '=', $this->tree->id())
14628add1155SRico Sonntag            ->count();
14638add1155SRico Sonntag    }
14648add1155SRico Sonntag
14658add1155SRico Sonntag    /**
14668add1155SRico Sonntag     * How many individuals have one or more sources.
14678add1155SRico Sonntag     *
14688add1155SRico Sonntag     * @return int
14698add1155SRico Sonntag     */
14708add1155SRico Sonntag    private function totalIndisWithSourcesQuery(): int
14718add1155SRico Sonntag    {
14728add1155SRico Sonntag        return DB::table('individuals')
14738add1155SRico Sonntag            ->select(['i_id'])
14748add1155SRico Sonntag            ->distinct()
14758add1155SRico Sonntag            ->join('link', function (JoinClause $join) {
14768add1155SRico Sonntag                $join->on('i_id', '=', 'l_from')
14778add1155SRico Sonntag                    ->on('i_file', '=', 'l_file');
14788add1155SRico Sonntag            })
14798add1155SRico Sonntag            ->where('l_file', '=', $this->tree->id())
14808add1155SRico Sonntag            ->where('l_type', '=', 'SOUR')
14818add1155SRico Sonntag            ->count('i_id');
14828add1155SRico Sonntag    }
14838add1155SRico Sonntag
14848add1155SRico Sonntag    /**
14858add1155SRico Sonntag     * Count the families with source records.
14868add1155SRico Sonntag     *
14878add1155SRico Sonntag     * @return int
14888add1155SRico Sonntag     */
14898add1155SRico Sonntag    private function totalFamsWithSourcesQuery(): int
14908add1155SRico Sonntag    {
14918add1155SRico Sonntag        return DB::table('families')
14928add1155SRico Sonntag            ->select(['f_id'])
14938add1155SRico Sonntag            ->distinct()
14948add1155SRico Sonntag            ->join('link', function (JoinClause $join) {
14958add1155SRico Sonntag                $join->on('f_id', '=', 'l_from')
14968add1155SRico Sonntag                    ->on('f_file', '=', 'l_file');
14978add1155SRico Sonntag            })
14988add1155SRico Sonntag            ->where('l_file', '=', $this->tree->id())
14998add1155SRico Sonntag            ->where('l_type', '=', 'SOUR')
15008add1155SRico Sonntag            ->count('f_id');
15018add1155SRico Sonntag    }
15028add1155SRico Sonntag
15038add1155SRico Sonntag    /**
15048add1155SRico Sonntag     * Count the number of repositories.
15058add1155SRico Sonntag     *
15068add1155SRico Sonntag     * @return int
15078add1155SRico Sonntag     */
15088add1155SRico Sonntag    private function totalRepositoriesQuery(): int
15098add1155SRico Sonntag    {
15108add1155SRico Sonntag        return DB::table('other')
15118add1155SRico Sonntag            ->where('o_file', '=', $this->tree->id())
15128add1155SRico Sonntag            ->where('o_type', '=', 'REPO')
15138add1155SRico Sonntag            ->count();
15148add1155SRico Sonntag    }
15158add1155SRico Sonntag
15168add1155SRico Sonntag    /**
15178add1155SRico Sonntag     * Count the total number of sources.
15188add1155SRico Sonntag     *
15198add1155SRico Sonntag     * @return int
15208add1155SRico Sonntag     */
15218add1155SRico Sonntag    private function totalSourcesQuery(): int
15228add1155SRico Sonntag    {
15238add1155SRico Sonntag        return DB::table('sources')
15248add1155SRico Sonntag            ->where('s_file', '=', $this->tree->id())
15258add1155SRico Sonntag            ->count();
15268add1155SRico Sonntag    }
15278add1155SRico Sonntag
15288add1155SRico Sonntag    /**
15298add1155SRico Sonntag     * Count the number of notes.
15308add1155SRico Sonntag     *
15318add1155SRico Sonntag     * @return int
15328add1155SRico Sonntag     */
15338add1155SRico Sonntag    private function totalNotesQuery(): int
15348add1155SRico Sonntag    {
15358add1155SRico Sonntag        return DB::table('other')
15368add1155SRico Sonntag            ->where('o_file', '=', $this->tree->id())
15378add1155SRico Sonntag            ->where('o_type', '=', 'NOTE')
15388add1155SRico Sonntag            ->count();
15398add1155SRico Sonntag    }
15408add1155SRico Sonntag
15418add1155SRico Sonntag    /**
15428add1155SRico Sonntag     * Returns the total number of records.
15438add1155SRico Sonntag     *
15448add1155SRico Sonntag     * @return int
15458add1155SRico Sonntag     */
15468add1155SRico Sonntag    private function totalRecordsQuery(): int
15478add1155SRico Sonntag    {
15488add1155SRico Sonntag        return $this->totalIndividualsQuery()
15498add1155SRico Sonntag            + $this->totalFamiliesQuery()
15508add1155SRico Sonntag            + $this->totalNotesQuery()
15518add1155SRico Sonntag            + $this->totalRepositoriesQuery()
15528add1155SRico Sonntag            + $this->totalSourcesQuery();
15538add1155SRico Sonntag    }
15548add1155SRico Sonntag
15558add1155SRico Sonntag    /**
15568add1155SRico Sonntag     * @inheritDoc
15578add1155SRico Sonntag     */
15588add1155SRico Sonntag    public function totalRecords(): string
15598add1155SRico Sonntag    {
15608add1155SRico Sonntag        return I18N::number($this->totalRecordsQuery());
15618add1155SRico Sonntag    }
15628add1155SRico Sonntag
15638add1155SRico Sonntag    /**
15648add1155SRico Sonntag     * @inheritDoc
15658add1155SRico Sonntag     */
15668add1155SRico Sonntag    public function totalIndividuals(): string
15678add1155SRico Sonntag    {
15688add1155SRico Sonntag        return I18N::number($this->totalIndividualsQuery());
15698add1155SRico Sonntag    }
15708add1155SRico Sonntag
15718add1155SRico Sonntag    /**
15728add1155SRico Sonntag     * Count the number of living individuals.
15738add1155SRico Sonntag     *
15748add1155SRico Sonntag     * @return string
15758add1155SRico Sonntag     */
15768add1155SRico Sonntag    public function totalLiving(): string
15778add1155SRico Sonntag    {
15788add1155SRico Sonntag        return I18N::number($this->totalLivingQuery());
15798add1155SRico Sonntag    }
15808add1155SRico Sonntag
15818add1155SRico Sonntag    /**
15828add1155SRico Sonntag     * Count the number of dead individuals.
15838add1155SRico Sonntag     *
15848add1155SRico Sonntag     * @return string
15858add1155SRico Sonntag     */
15868add1155SRico Sonntag    public function totalDeceased(): string
15878add1155SRico Sonntag    {
15888add1155SRico Sonntag        return I18N::number($this->totalDeceasedQuery());
15898add1155SRico Sonntag    }
15908add1155SRico Sonntag
15918add1155SRico Sonntag    /**
15928add1155SRico Sonntag     * @inheritDoc
15938add1155SRico Sonntag     */
15948add1155SRico Sonntag    public function totalSexMales(): string
15958add1155SRico Sonntag    {
15968add1155SRico Sonntag        return I18N::number($this->totalSexMalesQuery());
15978add1155SRico Sonntag    }
15988add1155SRico Sonntag
15998add1155SRico Sonntag    /**
16008add1155SRico Sonntag     * @inheritDoc
16018add1155SRico Sonntag     */
16028add1155SRico Sonntag    public function totalSexFemales(): string
16038add1155SRico Sonntag    {
16048add1155SRico Sonntag        return I18N::number($this->totalSexFemalesQuery());
16058add1155SRico Sonntag    }
16068add1155SRico Sonntag
16078add1155SRico Sonntag    /**
16088add1155SRico Sonntag     * @inheritDoc
16098add1155SRico Sonntag     */
16108add1155SRico Sonntag    public function totalSexUnknown(): string
16118add1155SRico Sonntag    {
16128add1155SRico Sonntag        return I18N::number($this->totalSexUnknownQuery());
16138add1155SRico Sonntag    }
16148add1155SRico Sonntag
16158add1155SRico Sonntag    /**
16168add1155SRico Sonntag     * @inheritDoc
16178add1155SRico Sonntag     */
16188add1155SRico Sonntag    public function totalFamilies(): string
16198add1155SRico Sonntag    {
16208add1155SRico Sonntag        return I18N::number($this->totalFamiliesQuery());
16218add1155SRico Sonntag    }
16228add1155SRico Sonntag
16238add1155SRico Sonntag    /**
16248add1155SRico Sonntag     * How many individuals have one or more sources.
16258add1155SRico Sonntag     *
16268add1155SRico Sonntag     * @return string
16278add1155SRico Sonntag     */
16288add1155SRico Sonntag    public function totalIndisWithSources(): string
16298add1155SRico Sonntag    {
16308add1155SRico Sonntag        return I18N::number($this->totalIndisWithSourcesQuery());
16318add1155SRico Sonntag    }
16328add1155SRico Sonntag
16338add1155SRico Sonntag    /**
16348add1155SRico Sonntag     * Count the families with with source records.
16358add1155SRico Sonntag     *
16368add1155SRico Sonntag     * @return string
16378add1155SRico Sonntag     */
16388add1155SRico Sonntag    public function totalFamsWithSources(): string
16398add1155SRico Sonntag    {
16408add1155SRico Sonntag        return I18N::number($this->totalFamsWithSourcesQuery());
16418add1155SRico Sonntag    }
16428add1155SRico Sonntag
16438add1155SRico Sonntag    /**
16448add1155SRico Sonntag     * @inheritDoc
16458add1155SRico Sonntag     */
16468add1155SRico Sonntag    public function totalRepositories(): string
16478add1155SRico Sonntag    {
16488add1155SRico Sonntag        return I18N::number($this->totalRepositoriesQuery());
16498add1155SRico Sonntag    }
16508add1155SRico Sonntag
16518add1155SRico Sonntag    /**
16528add1155SRico Sonntag     * @inheritDoc
16538add1155SRico Sonntag     */
16548add1155SRico Sonntag    public function totalSources(): string
16558add1155SRico Sonntag    {
16568add1155SRico Sonntag        return I18N::number($this->totalSourcesQuery());
16578add1155SRico Sonntag    }
16588add1155SRico Sonntag
16598add1155SRico Sonntag    /**
16608add1155SRico Sonntag     * @inheritDoc
16618add1155SRico Sonntag     */
16628add1155SRico Sonntag    public function totalNotes(): string
16638add1155SRico Sonntag    {
16648add1155SRico Sonntag        return I18N::number($this->totalNotesQuery());
16658add1155SRico Sonntag    }
16668add1155SRico Sonntag
16678add1155SRico Sonntag    /**
16688add1155SRico Sonntag     * @inheritDoc
16698add1155SRico Sonntag     */
16708add1155SRico Sonntag    public function totalIndividualsPercentage(): string
16718add1155SRico Sonntag    {
16728add1155SRico Sonntag        return $this->getPercentage(
16738add1155SRico Sonntag            $this->totalIndividualsQuery(),
16748add1155SRico Sonntag            $this->totalRecordsQuery()
16758add1155SRico Sonntag        );
16768add1155SRico Sonntag    }
16778add1155SRico Sonntag
16788add1155SRico Sonntag    /**
16798add1155SRico Sonntag     * @inheritDoc
16808add1155SRico Sonntag     */
16818add1155SRico Sonntag    public function totalFamiliesPercentage(): string
16828add1155SRico Sonntag    {
16838add1155SRico Sonntag        return $this->getPercentage(
16848add1155SRico Sonntag            $this->totalFamiliesQuery(),
16858add1155SRico Sonntag            $this->totalRecordsQuery()
16868add1155SRico Sonntag        );
16878add1155SRico Sonntag    }
16888add1155SRico Sonntag
16898add1155SRico Sonntag    /**
16908add1155SRico Sonntag     * @inheritDoc
16918add1155SRico Sonntag     */
16928add1155SRico Sonntag    public function totalRepositoriesPercentage(): string
16938add1155SRico Sonntag    {
16948add1155SRico Sonntag        return $this->getPercentage(
16958add1155SRico Sonntag            $this->totalRepositoriesQuery(),
16968add1155SRico Sonntag            $this->totalRecordsQuery()
16978add1155SRico Sonntag        );
16988add1155SRico Sonntag    }
16998add1155SRico Sonntag
17008add1155SRico Sonntag    /**
17018add1155SRico Sonntag     * @inheritDoc
17028add1155SRico Sonntag     */
17038add1155SRico Sonntag    public function totalSourcesPercentage(): string
17048add1155SRico Sonntag    {
17058add1155SRico Sonntag        return $this->getPercentage(
17068add1155SRico Sonntag            $this->totalSourcesQuery(),
17078add1155SRico Sonntag            $this->totalRecordsQuery()
17088add1155SRico Sonntag        );
17098add1155SRico Sonntag    }
17108add1155SRico Sonntag
17118add1155SRico Sonntag    /**
17128add1155SRico Sonntag     * @inheritDoc
17138add1155SRico Sonntag     */
17148add1155SRico Sonntag    public function totalNotesPercentage(): string
17158add1155SRico Sonntag    {
17168add1155SRico Sonntag        return $this->getPercentage(
17178add1155SRico Sonntag            $this->totalNotesQuery(),
17188add1155SRico Sonntag            $this->totalRecordsQuery()
17198add1155SRico Sonntag        );
17208add1155SRico Sonntag    }
17218add1155SRico Sonntag
17228add1155SRico Sonntag    /**
17238add1155SRico Sonntag     * @inheritDoc
17248add1155SRico Sonntag     */
17258add1155SRico Sonntag    public function totalLivingPercentage(): string
17268add1155SRico Sonntag    {
17278add1155SRico Sonntag        return $this->getPercentage(
17288add1155SRico Sonntag            $this->totalLivingQuery(),
17298add1155SRico Sonntag            $this->totalIndividualsQuery()
17308add1155SRico Sonntag        );
17318add1155SRico Sonntag    }
17328add1155SRico Sonntag
17338add1155SRico Sonntag    /**
17348add1155SRico Sonntag     * @inheritDoc
17358add1155SRico Sonntag     */
17368add1155SRico Sonntag    public function totalDeceasedPercentage(): string
17378add1155SRico Sonntag    {
17388add1155SRico Sonntag        return $this->getPercentage(
17398add1155SRico Sonntag            $this->totalDeceasedQuery(),
17408add1155SRico Sonntag            $this->totalIndividualsQuery()
17418add1155SRico Sonntag        );
17428add1155SRico Sonntag    }
17438add1155SRico Sonntag
17448add1155SRico Sonntag    /**
17458add1155SRico Sonntag     * @inheritDoc
17468add1155SRico Sonntag     */
17478add1155SRico Sonntag    public function totalSexMalesPercentage(): string
17488add1155SRico Sonntag    {
17498add1155SRico Sonntag        return $this->getPercentage(
17508add1155SRico Sonntag            $this->totalSexMalesQuery(),
17518add1155SRico Sonntag            $this->totalIndividualsQuery()
17528add1155SRico Sonntag        );
17538add1155SRico Sonntag    }
17548add1155SRico Sonntag
17558add1155SRico Sonntag    /**
17568add1155SRico Sonntag     * @inheritDoc
17578add1155SRico Sonntag     */
17588add1155SRico Sonntag    public function totalSexFemalesPercentage(): string
17598add1155SRico Sonntag    {
17608add1155SRico Sonntag        return $this->getPercentage(
17618add1155SRico Sonntag            $this->totalSexFemalesQuery(),
17628add1155SRico Sonntag            $this->totalIndividualsQuery()
17638add1155SRico Sonntag        );
17648add1155SRico Sonntag    }
17658add1155SRico Sonntag
17668add1155SRico Sonntag    /**
17678add1155SRico Sonntag     * @inheritDoc
17688add1155SRico Sonntag     */
17698add1155SRico Sonntag    public function totalSexUnknownPercentage(): string
17708add1155SRico Sonntag    {
17718add1155SRico Sonntag        return $this->getPercentage(
17728add1155SRico Sonntag            $this->totalSexUnknownQuery(),
17738add1155SRico Sonntag            $this->totalIndividualsQuery()
17748add1155SRico Sonntag        );
17758add1155SRico Sonntag    }
17768add1155SRico Sonntag
17778add1155SRico Sonntag    /**
17788add1155SRico Sonntag     * Create a chart of common given names.
17798add1155SRico Sonntag     *
17808add1155SRico Sonntag     * @param string|null $size
17818add1155SRico Sonntag     * @param string|null $color_from
17828add1155SRico Sonntag     * @param string|null $color_to
17838add1155SRico Sonntag     * @param int         $maxtoshow
17848add1155SRico Sonntag     *
17858add1155SRico Sonntag     * @return string
17868add1155SRico Sonntag     */
17878add1155SRico Sonntag    public function chartCommonGiven(
17888add1155SRico Sonntag        string $size = null,
17898add1155SRico Sonntag        string $color_from = null,
17908add1155SRico Sonntag        string $color_to = null,
17918add1155SRico Sonntag        int $maxtoshow = 7
17928add1155SRico Sonntag    ): string {
17938add1155SRico Sonntag        $tot_indi = $this->totalIndividualsQuery();
17948add1155SRico Sonntag        $given    = $this->commonGivenQuery('B', 'chart', false, 1, $maxtoshow);
17958add1155SRico Sonntag
17968add1155SRico Sonntag        return (new ChartCommonGiven())
17978add1155SRico Sonntag            ->chartCommonGiven($tot_indi, $given, $size, $color_from, $color_to);
17988add1155SRico Sonntag    }
17998add1155SRico Sonntag
18008add1155SRico Sonntag    /**
18018add1155SRico Sonntag     * Create a chart of common surnames.
18028add1155SRico Sonntag     *
18038add1155SRico Sonntag     * @param string|null $size
18048add1155SRico Sonntag     * @param string|null $color_from
18058add1155SRico Sonntag     * @param string|null $color_to
18068add1155SRico Sonntag     * @param int         $number_of_surnames
18078add1155SRico Sonntag     *
18088add1155SRico Sonntag     * @return string
18098add1155SRico Sonntag     */
18108add1155SRico Sonntag    public function chartCommonSurnames(
18118add1155SRico Sonntag        string $size = null,
18128add1155SRico Sonntag        string $color_from = null,
18138add1155SRico Sonntag        string $color_to = null,
18148add1155SRico Sonntag        int $number_of_surnames = 10
18158add1155SRico Sonntag    ): string {
18168add1155SRico Sonntag        $tot_indi     = $this->totalIndividualsQuery();
18178add1155SRico Sonntag        $all_surnames = $this->topSurnames($number_of_surnames, 0);
18188add1155SRico Sonntag
18198add1155SRico Sonntag        return (new ChartCommonSurname($this->tree))
18208add1155SRico Sonntag            ->chartCommonSurnames($tot_indi, $all_surnames, $size, $color_from, $color_to);
18218add1155SRico Sonntag    }
18228add1155SRico Sonntag
18238add1155SRico Sonntag    /**
18248add1155SRico Sonntag     * Create a chart showing mortality.
18258add1155SRico Sonntag     *
18268add1155SRico Sonntag     * @param string|null $size
18278add1155SRico Sonntag     * @param string|null $color_living
18288add1155SRico Sonntag     * @param string|null $color_dead
18298add1155SRico Sonntag     *
18308add1155SRico Sonntag     * @return string
18318add1155SRico Sonntag     */
18328add1155SRico Sonntag    public function chartMortality(string $size = null, string $color_living = null, string $color_dead = null): string
18338add1155SRico Sonntag    {
18348add1155SRico Sonntag        $tot_l = $this->totalLivingQuery();
18358add1155SRico Sonntag        $tot_d = $this->totalDeceasedQuery();
18368add1155SRico Sonntag
18378add1155SRico Sonntag        return (new ChartMortality($this->tree))
18388add1155SRico Sonntag            ->chartMortality($tot_l, $tot_d, $size, $color_living, $color_dead);
18398add1155SRico Sonntag    }
18408add1155SRico Sonntag
18418add1155SRico Sonntag    /**
18428add1155SRico Sonntag     * Create a chart showing individuals with/without sources.
18438add1155SRico Sonntag     *
18448add1155SRico Sonntag     * @param string|null $size
18458add1155SRico Sonntag     * @param string|null $color_from
18468add1155SRico Sonntag     * @param string|null $color_to
18478add1155SRico Sonntag     *
18488add1155SRico Sonntag     * @return string
18498add1155SRico Sonntag     */
18508add1155SRico Sonntag    public function chartIndisWithSources(
18518add1155SRico Sonntag        string $size       = null,
18528add1155SRico Sonntag        string $color_from = null,
18538add1155SRico Sonntag        string $color_to   = null
18548add1155SRico Sonntag    ): string {
18558add1155SRico Sonntag        $tot_indi        = $this->totalIndividualsQuery();
18568add1155SRico Sonntag        $tot_indi_source = $this->totalIndisWithSourcesQuery();
18578add1155SRico Sonntag
18588add1155SRico Sonntag        return (new ChartIndividual())
18598add1155SRico Sonntag            ->chartIndisWithSources($tot_indi, $tot_indi_source, $size, $color_from, $color_to);
18608add1155SRico Sonntag    }
18618add1155SRico Sonntag
18628add1155SRico Sonntag    /**
18638add1155SRico Sonntag     * Create a chart of individuals with/without sources.
18648add1155SRico Sonntag     *
18658add1155SRico Sonntag     * @param string|null $size
18668add1155SRico Sonntag     * @param string|null $color_from
18678add1155SRico Sonntag     * @param string|null $color_to
18688add1155SRico Sonntag     *
18698add1155SRico Sonntag     * @return string
18708add1155SRico Sonntag     */
18718add1155SRico Sonntag    public function chartFamsWithSources(
18728add1155SRico Sonntag        string $size       = null,
18738add1155SRico Sonntag        string $color_from = null,
18748add1155SRico Sonntag        string $color_to   = null
18758add1155SRico Sonntag    ): string {
18768add1155SRico Sonntag        $tot_fam        = $this->totalFamiliesQuery();
18778add1155SRico Sonntag        $tot_fam_source = $this->totalFamsWithSourcesQuery();
18788add1155SRico Sonntag
18798add1155SRico Sonntag        return (new ChartFamilyWithSources())
18808add1155SRico Sonntag            ->chartFamsWithSources($tot_fam, $tot_fam_source, $size, $color_from, $color_to);
18818add1155SRico Sonntag    }
18828add1155SRico Sonntag
18838add1155SRico Sonntag    /**
18848add1155SRico Sonntag     * @inheritDoc
18858add1155SRico Sonntag     */
18868add1155SRico Sonntag    public function chartSex(
18878add1155SRico Sonntag        string $size          = null,
18888add1155SRico Sonntag        string $color_female  = null,
18898add1155SRico Sonntag        string $color_male    = null,
18908add1155SRico Sonntag        string $color_unknown = null
18918add1155SRico Sonntag    ): string {
18928add1155SRico Sonntag        $tot_m = $this->totalSexMalesQuery();
18938add1155SRico Sonntag        $tot_f = $this->totalSexFemalesQuery();
18948add1155SRico Sonntag        $tot_u = $this->totalSexUnknownQuery();
18958add1155SRico Sonntag
18968add1155SRico Sonntag        return (new ChartSex($this->tree))
18978add1155SRico Sonntag            ->chartSex($tot_m, $tot_f, $tot_u, $size, $color_female, $color_male, $color_unknown);
18988add1155SRico Sonntag    }
1899*44cdc21eSGreg Roach
1900*44cdc21eSGreg Roach    /**
1901*44cdc21eSGreg Roach     * Query individuals, with their births and deaths.
1902*44cdc21eSGreg Roach     *
1903*44cdc21eSGreg Roach     * @param string $sex
1904*44cdc21eSGreg Roach     *
1905*44cdc21eSGreg Roach     * @return Builder
1906*44cdc21eSGreg Roach     */
1907*44cdc21eSGreg Roach    private function birthAndDeathQuery(string $sex): Builder {
1908*44cdc21eSGreg Roach        $query = DB::table('individuals')
1909*44cdc21eSGreg Roach            ->where('i_file', '=', $this->tree->id())
1910*44cdc21eSGreg Roach            ->join('dates AS birth', function (JoinClause $join): void {
1911*44cdc21eSGreg Roach                $join
1912*44cdc21eSGreg Roach                    ->on('birth.d_file', '=', 'i_file')
1913*44cdc21eSGreg Roach                    ->on('birth.d_gid', '=', 'i_id');
1914*44cdc21eSGreg Roach            })
1915*44cdc21eSGreg Roach            ->join('dates AS death', function (JoinClause $join): void {
1916*44cdc21eSGreg Roach                $join
1917*44cdc21eSGreg Roach                    ->on('death.d_file', '=', 'i_file')
1918*44cdc21eSGreg Roach                    ->on('death.d_gid', '=', 'i_id');
1919*44cdc21eSGreg Roach            })
1920*44cdc21eSGreg Roach            ->where('birth.d_fact', '=', 'BIRT')
1921*44cdc21eSGreg Roach            ->where('death.d_fact', '=', 'DEAT')
1922*44cdc21eSGreg Roach            ->whereColumn('death.d_julianday1', '>=', 'birth.d_julianday2')
1923*44cdc21eSGreg Roach            ->where('birth.d_julianday2', '<>', 0);
1924*44cdc21eSGreg Roach
1925*44cdc21eSGreg Roach        if ($sex === 'M' || $sex === 'F') {
1926*44cdc21eSGreg Roach            $query->where('i_sex', '=', $sex);
1927*44cdc21eSGreg Roach        }
1928*44cdc21eSGreg Roach
1929*44cdc21eSGreg Roach        return $query;
1930*44cdc21eSGreg Roach    }
19318add1155SRico Sonntag}
1932