xref: /webtrees/app/Statistics/Repository/FamilyRepository.php (revision 7f5c2944f84cdc914a20ed1e961f9b81e401f78b)
18add1155SRico Sonntag<?php
28add1155SRico Sonntag/**
38add1155SRico Sonntag * webtrees: online genealogy
4242a7862SGreg Roach * Copyright (C) 2019 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
206ccdf4f0SGreg Roachuse Exception;
218add1155SRico Sonntaguse Fisharebest\Webtrees\Family;
228add1155SRico Sonntaguse Fisharebest\Webtrees\Functions\FunctionsDate;
23d1a467e4SGreg Roachuse Fisharebest\Webtrees\GedcomRecord;
248add1155SRico Sonntaguse Fisharebest\Webtrees\I18N;
258add1155SRico Sonntaguse Fisharebest\Webtrees\Individual;
268add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartChildren;
278add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartDivorce;
288add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartFamilyLargest;
298add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartMarriage;
308add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartMarriageAge;
318add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartNoChildrenFamilies;
328add1155SRico Sonntaguse Fisharebest\Webtrees\Tree;
3344cdc21eSGreg Roachuse Illuminate\Database\Capsule\Manager as DB;
342d7289dcSGreg Roachuse Illuminate\Database\Query\Builder;
35a69f5655SGreg Roachuse Illuminate\Database\Query\Expression;
36d1a467e4SGreg Roachuse Illuminate\Database\Query\JoinClause;
376ccdf4f0SGreg Roachuse function in_array;
388add1155SRico Sonntaguse stdClass;
398add1155SRico Sonntag
408add1155SRico Sonntag/**
418add1155SRico Sonntag *
428add1155SRico Sonntag */
438add1155SRico Sonntagclass FamilyRepository
448add1155SRico Sonntag{
458add1155SRico Sonntag    /**
468add1155SRico Sonntag     * @var Tree
478add1155SRico Sonntag     */
488add1155SRico Sonntag    private $tree;
498add1155SRico Sonntag
508add1155SRico Sonntag    /**
518add1155SRico Sonntag     * Constructor.
528add1155SRico Sonntag     *
538add1155SRico Sonntag     * @param Tree $tree
548add1155SRico Sonntag     */
558add1155SRico Sonntag    public function __construct(Tree $tree)
568add1155SRico Sonntag    {
578add1155SRico Sonntag        $this->tree = $tree;
588add1155SRico Sonntag    }
598add1155SRico Sonntag
608add1155SRico Sonntag    /**
618add1155SRico Sonntag     * General query on family.
628add1155SRico Sonntag     *
638add1155SRico Sonntag     * @param string $type
648add1155SRico Sonntag     *
658add1155SRico Sonntag     * @return string
668add1155SRico Sonntag     */
678add1155SRico Sonntag    private function familyQuery(string $type): string
688add1155SRico Sonntag    {
6944cdc21eSGreg Roach        $row = DB::table('families')
7044cdc21eSGreg Roach            ->where('f_file', '=', $this->tree->id())
7144cdc21eSGreg Roach            ->orderBy('f_numchil', 'desc')
7244cdc21eSGreg Roach            ->first();
738add1155SRico Sonntag
7444cdc21eSGreg Roach        if ($row === null) {
758add1155SRico Sonntag            return '';
768add1155SRico Sonntag        }
778add1155SRico Sonntag
7844cdc21eSGreg Roach        /** @var Family $family */
7944cdc21eSGreg Roach        $family = Family::rowMapper()($row);
808add1155SRico Sonntag
8144cdc21eSGreg Roach        if (!$family->canShow()) {
8244cdc21eSGreg Roach            return I18N::translate('This information is private and cannot be shown.');
838add1155SRico Sonntag        }
848add1155SRico Sonntag
858add1155SRico Sonntag        switch ($type) {
868add1155SRico Sonntag            default:
878add1155SRico Sonntag            case 'full':
8844cdc21eSGreg Roach                return $family->formatList();
898add1155SRico Sonntag
9044cdc21eSGreg Roach            case 'size':
9144cdc21eSGreg Roach                return I18N::number((int) $row->f_numchil);
9244cdc21eSGreg Roach
9344cdc21eSGreg Roach            case 'name':
9439ca88baSGreg Roach                return '<a href="' . e($family->url()) . '">' . $family->fullName() . '</a>';
9544cdc21eSGreg Roach        }
968add1155SRico Sonntag    }
978add1155SRico Sonntag
988add1155SRico Sonntag    /**
998add1155SRico Sonntag     * Find the family with the most children.
1008add1155SRico Sonntag     *
1018add1155SRico Sonntag     * @return string
1028add1155SRico Sonntag     */
1038add1155SRico Sonntag    public function largestFamily(): string
1048add1155SRico Sonntag    {
1058add1155SRico Sonntag        return $this->familyQuery('full');
1068add1155SRico Sonntag    }
1078add1155SRico Sonntag
1088add1155SRico Sonntag    /**
1098add1155SRico Sonntag     * Find the number of children in the largest family.
1108add1155SRico Sonntag     *
1118add1155SRico Sonntag     * @return string
1128add1155SRico Sonntag     */
1138add1155SRico Sonntag    public function largestFamilySize(): string
1148add1155SRico Sonntag    {
1158add1155SRico Sonntag        return $this->familyQuery('size');
1168add1155SRico Sonntag    }
1178add1155SRico Sonntag
1188add1155SRico Sonntag    /**
1198add1155SRico Sonntag     * Find the family with the most children.
1208add1155SRico Sonntag     *
1218add1155SRico Sonntag     * @return string
1228add1155SRico Sonntag     */
1238add1155SRico Sonntag    public function largestFamilyName(): string
1248add1155SRico Sonntag    {
1258add1155SRico Sonntag        return $this->familyQuery('name');
1268add1155SRico Sonntag    }
1278add1155SRico Sonntag
1288add1155SRico Sonntag    /**
1298add1155SRico Sonntag     * Find the couple with the most grandchildren.
1308add1155SRico Sonntag     *
1318add1155SRico Sonntag     * @param int $total
1328add1155SRico Sonntag     *
1338add1155SRico Sonntag     * @return array
1348add1155SRico Sonntag     */
1358add1155SRico Sonntag    private function topTenGrandFamilyQuery(int $total): array
1368add1155SRico Sonntag    {
1372d7289dcSGreg Roach        return DB::table('families')
1380b5fd0a6SGreg Roach            ->join('link AS children', static function (JoinClause $join): void {
1392d7289dcSGreg Roach                $join
1402d7289dcSGreg Roach                    ->on('children.l_from', '=', 'f_id')
1412d7289dcSGreg Roach                    ->on('children.l_file', '=', 'f_file')
1422d7289dcSGreg Roach                    ->where('children.l_type', '=', 'CHIL');
1430b5fd0a6SGreg Roach            })->join('link AS mchildren', static function (JoinClause $join): void {
1442d7289dcSGreg Roach                $join
1452d7289dcSGreg Roach                    ->on('mchildren.l_file', '=', 'children.l_file')
1462d7289dcSGreg Roach                    ->on('mchildren.l_from', '=', 'children.l_to')
1472d7289dcSGreg Roach                    ->where('mchildren.l_type', '=', 'FAMS');
1480b5fd0a6SGreg Roach            })->join('link AS gchildren', static function (JoinClause $join): void {
1492d7289dcSGreg Roach                $join
1502d7289dcSGreg Roach                    ->on('gchildren.l_file', '=', 'mchildren.l_file')
1512d7289dcSGreg Roach                    ->on('gchildren.l_from', '=', 'mchildren.l_to')
1522d7289dcSGreg Roach                    ->where('gchildren.l_type', '=', 'CHIL');
1532d7289dcSGreg Roach            })
1542d7289dcSGreg Roach            ->where('f_file', '=', $this->tree->id())
1552d7289dcSGreg Roach            ->groupBy(['f_id', 'f_file'])
156a69f5655SGreg Roach            ->orderBy(new Expression('COUNT(*)'), 'DESC')
1572d7289dcSGreg Roach            ->select('families.*')
1582d7289dcSGreg Roach            ->limit($total)
1592d7289dcSGreg Roach            ->get()
1602d7289dcSGreg Roach            ->map(Family::rowMapper())
1612d7289dcSGreg Roach            ->filter(GedcomRecord::accessFilter())
1620b5fd0a6SGreg Roach            ->map(static function (Family $family): array {
1632d7289dcSGreg Roach                $count = 0;
1642d7289dcSGreg Roach                foreach ($family->children() as $child) {
1652d7289dcSGreg Roach                    foreach ($child->spouseFamilies() as $spouse_family) {
1662d7289dcSGreg Roach                        $count += $spouse_family->children()->count();
1672d7289dcSGreg Roach                    }
1688add1155SRico Sonntag                }
1698add1155SRico Sonntag
1702d7289dcSGreg Roach                return [
1718add1155SRico Sonntag                    'family' => $family,
1722d7289dcSGreg Roach                    'count'  => $count,
1738add1155SRico Sonntag                ];
1742d7289dcSGreg Roach            })
1752d7289dcSGreg Roach            ->all();
1768add1155SRico Sonntag    }
1778add1155SRico Sonntag
1788add1155SRico Sonntag    /**
1798add1155SRico Sonntag     * Find the couple with the most grandchildren.
1808add1155SRico Sonntag     *
1818add1155SRico Sonntag     * @param int $total
1828add1155SRico Sonntag     *
1838add1155SRico Sonntag     * @return string
1848add1155SRico Sonntag     */
1858add1155SRico Sonntag    public function topTenLargestGrandFamily(int $total = 10): string
1868add1155SRico Sonntag    {
1872d7289dcSGreg Roach        return view('statistics/families/top10-nolist-grand', [
1882d7289dcSGreg Roach            'records' => $this->topTenGrandFamilyQuery($total),
1892d7289dcSGreg Roach        ]);
1908add1155SRico Sonntag    }
1918add1155SRico Sonntag
1928add1155SRico Sonntag    /**
1938add1155SRico Sonntag     * Find the couple with the most grandchildren.
1948add1155SRico Sonntag     *
1958add1155SRico Sonntag     * @param int $total
1968add1155SRico Sonntag     *
1978add1155SRico Sonntag     * @return string
1988add1155SRico Sonntag     */
1998add1155SRico Sonntag    public function topTenLargestGrandFamilyList(int $total = 10): string
2008add1155SRico Sonntag    {
2012d7289dcSGreg Roach        return view('statistics/families/top10-list-grand', [
2022d7289dcSGreg Roach            'records' => $this->topTenGrandFamilyQuery($total),
2032d7289dcSGreg Roach        ]);
2048add1155SRico Sonntag    }
2058add1155SRico Sonntag
2068add1155SRico Sonntag    /**
2078add1155SRico Sonntag     * Find the families with no children.
2088add1155SRico Sonntag     *
2098add1155SRico Sonntag     * @return int
2108add1155SRico Sonntag     */
2118add1155SRico Sonntag    private function noChildrenFamiliesQuery(): int
2128add1155SRico Sonntag    {
2132d7289dcSGreg Roach        return DB::table('families')
2142d7289dcSGreg Roach            ->where('f_file', '=', $this->tree->id())
2152d7289dcSGreg Roach            ->where('f_numchil', '=', 0)
2162d7289dcSGreg Roach            ->count();
2178add1155SRico Sonntag    }
2188add1155SRico Sonntag
2198add1155SRico Sonntag    /**
2208add1155SRico Sonntag     * Find the families with no children.
2218add1155SRico Sonntag     *
2228add1155SRico Sonntag     * @return string
2238add1155SRico Sonntag     */
2248add1155SRico Sonntag    public function noChildrenFamilies(): string
2258add1155SRico Sonntag    {
2268add1155SRico Sonntag        return I18N::number($this->noChildrenFamiliesQuery());
2278add1155SRico Sonntag    }
2288add1155SRico Sonntag
2298add1155SRico Sonntag    /**
2308add1155SRico Sonntag     * Find the families with no children.
2318add1155SRico Sonntag     *
2328add1155SRico Sonntag     * @param string $type
2338add1155SRico Sonntag     *
2348add1155SRico Sonntag     * @return string
2358add1155SRico Sonntag     */
2368add1155SRico Sonntag    public function noChildrenFamiliesList($type = 'list'): string
2378add1155SRico Sonntag    {
2382c928961SGreg Roach        $families = DB::table('families')
2392c928961SGreg Roach            ->where('f_file', '=', $this->tree->id())
2402c928961SGreg Roach            ->where('f_numchil', '=', 0)
2412c928961SGreg Roach            ->get()
2422c928961SGreg Roach            ->map(Family::rowMapper())
2432c928961SGreg Roach            ->filter(GedcomRecord::accessFilter());
2448add1155SRico Sonntag
2458add1155SRico Sonntag        $top10 = [];
2462c928961SGreg Roach
2473413ec75SRico Sonntag        /** @var Family $family */
2482c928961SGreg Roach        foreach ($families as $family) {
2498add1155SRico Sonntag            if ($type === 'list') {
25039ca88baSGreg Roach                $top10[] = '<li><a href="' . e($family->url()) . '">' . $family->fullName() . '</a></li>';
2518add1155SRico Sonntag            } else {
25239ca88baSGreg Roach                $top10[] = '<a href="' . e($family->url()) . '">' . $family->fullName() . '</a>';
2538add1155SRico Sonntag            }
2548add1155SRico Sonntag        }
2558add1155SRico Sonntag
2568add1155SRico Sonntag        if ($type === 'list') {
2578add1155SRico Sonntag            $top10 = implode('', $top10);
2588add1155SRico Sonntag        } else {
2598add1155SRico Sonntag            $top10 = implode('; ', $top10);
2608add1155SRico Sonntag        }
2618add1155SRico Sonntag
2622c928961SGreg Roach
2638add1155SRico Sonntag        if ($type === 'list') {
2648add1155SRico Sonntag            return '<ul>' . $top10 . '</ul>';
2658add1155SRico Sonntag        }
2668add1155SRico Sonntag
2678add1155SRico Sonntag        return $top10;
2688add1155SRico Sonntag    }
2698add1155SRico Sonntag
2708add1155SRico Sonntag    /**
2718add1155SRico Sonntag     * Create a chart of children with no families.
2728add1155SRico Sonntag     *
2738add1155SRico Sonntag     * @param int $year1
2748add1155SRico Sonntag     * @param int $year2
2758add1155SRico Sonntag     *
2768add1155SRico Sonntag     * @return string
2778add1155SRico Sonntag     */
27888de55fdSRico Sonntag    public function chartNoChildrenFamilies(int $year1 = -1, int $year2 = -1): string
2798add1155SRico Sonntag    {
2808add1155SRico Sonntag        $no_child_fam = $this->noChildrenFamiliesQuery();
2818add1155SRico Sonntag
2828add1155SRico Sonntag        return (new ChartNoChildrenFamilies($this->tree))
28388de55fdSRico Sonntag            ->chartNoChildrenFamilies($no_child_fam, $year1, $year2);
2848add1155SRico Sonntag    }
2858add1155SRico Sonntag
2868add1155SRico Sonntag    /**
2878add1155SRico Sonntag     * Returns the ages between siblings.
2888add1155SRico Sonntag     *
2898add1155SRico Sonntag     * @param int $total The total number of records to query
2908add1155SRico Sonntag     *
2918add1155SRico Sonntag     * @return array
2928add1155SRico Sonntag     */
2938add1155SRico Sonntag    private function ageBetweenSiblingsQuery(int $total): array
2948add1155SRico Sonntag    {
295b1126ab4SGreg Roach        $prefix = DB::connection()->getTablePrefix();
2968add1155SRico Sonntag
297b1126ab4SGreg Roach        return DB::table('link AS link1')
2980b5fd0a6SGreg Roach            ->join('link AS link2', static function (JoinClause $join): void {
299b1126ab4SGreg Roach                $join
300b1126ab4SGreg Roach                    ->on('link2.l_from', '=', 'link1.l_from')
301b1126ab4SGreg Roach                    ->on('link2.l_type', '=', 'link1.l_type')
302b1126ab4SGreg Roach                    ->on('link2.l_file', '=', 'link1.l_file');
303b1126ab4SGreg Roach            })
3040b5fd0a6SGreg Roach            ->join('dates AS child1', static function (JoinClause $join): void {
305b1126ab4SGreg Roach                $join
306b1126ab4SGreg Roach                    ->on('child1.d_gid', '=', 'link1.l_to')
307b1126ab4SGreg Roach                    ->on('child1.d_file', '=', 'link1.l_file')
308b1126ab4SGreg Roach                    ->where('child1.d_fact', '=', 'BIRT')
309b1126ab4SGreg Roach                    ->where('child1.d_julianday1', '<>', 0);
310b1126ab4SGreg Roach            })
3110b5fd0a6SGreg Roach            ->join('dates AS child2', static function (JoinClause $join): void {
312b1126ab4SGreg Roach                $join
313b1126ab4SGreg Roach                    ->on('child2.d_gid', '=', 'link2.l_to')
314b1126ab4SGreg Roach                    ->on('child2.d_file', '=', 'link2.l_file')
315b1126ab4SGreg Roach                    ->where('child2.d_fact', '=', 'BIRT')
316b1126ab4SGreg Roach                    ->whereColumn('child2.d_julianday2', '>', 'child1.d_julianday1');
317b1126ab4SGreg Roach            })
318b1126ab4SGreg Roach            ->where('link1.l_type', '=', 'CHIL')
319b1126ab4SGreg Roach            ->where('link1.l_file', '=', $this->tree->id())
320b1126ab4SGreg Roach            ->distinct()
321a69f5655SGreg Roach            ->select(['link1.l_from AS family', 'link1.l_to AS ch1', 'link2.l_to AS ch2', new Expression($prefix . 'child2.d_julianday2 - ' . $prefix . 'child1.d_julianday1 AS age')])
322b1126ab4SGreg Roach            ->orderBy('age', 'DESC')
323b1126ab4SGreg Roach            ->take($total)
324b1126ab4SGreg Roach            ->get()
325b1126ab4SGreg Roach            ->all();
3268add1155SRico Sonntag    }
3278add1155SRico Sonntag
3288add1155SRico Sonntag    /**
3298add1155SRico Sonntag     * Returns the calculated age the time of event.
3308add1155SRico Sonntag     *
3318add1155SRico Sonntag     * @param int $age The age from the database record
3328add1155SRico Sonntag     *
3338add1155SRico Sonntag     * @return string
3348add1155SRico Sonntag     */
3358add1155SRico Sonntag    private function calculateAge(int $age): string
3368add1155SRico Sonntag    {
3378add1155SRico Sonntag        if ((int) ($age / 365.25) > 0) {
3388add1155SRico Sonntag            $result = (int) ($age / 365.25) . 'y';
3398add1155SRico Sonntag        } elseif ((int) ($age / 30.4375) > 0) {
3408add1155SRico Sonntag            $result = (int) ($age / 30.4375) . 'm';
3418add1155SRico Sonntag        } else {
3428add1155SRico Sonntag            $result = $age . 'd';
3438add1155SRico Sonntag        }
3448add1155SRico Sonntag
3458add1155SRico Sonntag        return FunctionsDate::getAgeAtEvent($result);
3468add1155SRico Sonntag    }
3478add1155SRico Sonntag
3488add1155SRico Sonntag    /**
3498add1155SRico Sonntag     * Find the ages between siblings.
3508add1155SRico Sonntag     *
3518add1155SRico Sonntag     * @param int $total The total number of records to query
3528add1155SRico Sonntag     *
3538add1155SRico Sonntag     * @return array
3546ccdf4f0SGreg Roach     * @throws Exception
3558add1155SRico Sonntag     */
3568add1155SRico Sonntag    private function ageBetweenSiblingsNoList(int $total): array
3578add1155SRico Sonntag    {
3588add1155SRico Sonntag        $rows = $this->ageBetweenSiblingsQuery($total);
3598add1155SRico Sonntag
3608add1155SRico Sonntag        foreach ($rows as $fam) {
3618add1155SRico Sonntag            $family = Family::getInstance($fam->family, $this->tree);
3628add1155SRico Sonntag            $child1 = Individual::getInstance($fam->ch1, $this->tree);
3638add1155SRico Sonntag            $child2 = Individual::getInstance($fam->ch2, $this->tree);
3648add1155SRico Sonntag
3658add1155SRico Sonntag            if ($child1->canShow() && $child2->canShow()) {
3668add1155SRico Sonntag                // ! Single array (no list)
3678add1155SRico Sonntag                return [
3688add1155SRico Sonntag                    'child1' => $child1,
3698add1155SRico Sonntag                    'child2' => $child2,
3708add1155SRico Sonntag                    'family' => $family,
3718add1155SRico Sonntag                    'age'    => $this->calculateAge((int) $fam->age),
3728add1155SRico Sonntag                ];
3738add1155SRico Sonntag            }
3748add1155SRico Sonntag        }
3758add1155SRico Sonntag
3768add1155SRico Sonntag        return [];
3778add1155SRico Sonntag    }
3788add1155SRico Sonntag
3798add1155SRico Sonntag    /**
3808add1155SRico Sonntag     * Find the ages between siblings.
3818add1155SRico Sonntag     *
3828add1155SRico Sonntag     * @param int  $total The total number of records to query
3838add1155SRico Sonntag     * @param bool $one   Include each family only once if true
3848add1155SRico Sonntag     *
3858add1155SRico Sonntag     * @return array
3866ccdf4f0SGreg Roach     * @throws Exception
3878add1155SRico Sonntag     */
3888add1155SRico Sonntag    private function ageBetweenSiblingsList(int $total, bool $one): array
3898add1155SRico Sonntag    {
3908add1155SRico Sonntag        $rows  = $this->ageBetweenSiblingsQuery($total);
3918add1155SRico Sonntag        $top10 = [];
3928add1155SRico Sonntag        $dist  = [];
3938add1155SRico Sonntag
3948add1155SRico Sonntag        foreach ($rows as $fam) {
3958add1155SRico Sonntag            $family = Family::getInstance($fam->family, $this->tree);
3968add1155SRico Sonntag            $child1 = Individual::getInstance($fam->ch1, $this->tree);
3978add1155SRico Sonntag            $child2 = Individual::getInstance($fam->ch2, $this->tree);
3988add1155SRico Sonntag
3998add1155SRico Sonntag            $age = $this->calculateAge((int) $fam->age);
4008add1155SRico Sonntag
4016ccdf4f0SGreg Roach            if ($one && !in_array($fam->family, $dist, true)) {
4028add1155SRico Sonntag                if ($child1->canShow() && $child2->canShow()) {
4038add1155SRico Sonntag                    $top10[] = [
4048add1155SRico Sonntag                        'child1' => $child1,
4058add1155SRico Sonntag                        'child2' => $child2,
4068add1155SRico Sonntag                        'family' => $family,
4078add1155SRico Sonntag                        'age'    => $age,
4088add1155SRico Sonntag                    ];
4098add1155SRico Sonntag
4108add1155SRico Sonntag                    $dist[] = $fam->family;
4118add1155SRico Sonntag                }
4128add1155SRico Sonntag            } elseif (!$one && $child1->canShow() && $child2->canShow()) {
4138add1155SRico Sonntag                $top10[] = [
4148add1155SRico Sonntag                    'child1' => $child1,
4158add1155SRico Sonntag                    'child2' => $child2,
4168add1155SRico Sonntag                    'family' => $family,
4178add1155SRico Sonntag                    'age'    => $age,
4188add1155SRico Sonntag                ];
4198add1155SRico Sonntag            }
4208add1155SRico Sonntag        }
4218add1155SRico Sonntag
4228add1155SRico Sonntag        return $top10;
4238add1155SRico Sonntag    }
4248add1155SRico Sonntag
4258add1155SRico Sonntag    /**
4268add1155SRico Sonntag     * Find the ages between siblings.
4278add1155SRico Sonntag     *
4288add1155SRico Sonntag     * @param int $total The total number of records to query
4298add1155SRico Sonntag     *
4308add1155SRico Sonntag     * @return string
4318add1155SRico Sonntag     */
4328add1155SRico Sonntag    private function ageBetweenSiblingsAge(int $total): string
4338add1155SRico Sonntag    {
4348add1155SRico Sonntag        $rows = $this->ageBetweenSiblingsQuery($total);
4358add1155SRico Sonntag
4368add1155SRico Sonntag        foreach ($rows as $fam) {
4378add1155SRico Sonntag            return $this->calculateAge((int) $fam->age);
4388add1155SRico Sonntag        }
4398add1155SRico Sonntag
4408add1155SRico Sonntag        return '';
4418add1155SRico Sonntag    }
4428add1155SRico Sonntag
4438add1155SRico Sonntag    /**
4448add1155SRico Sonntag     * Find the ages between siblings.
4458add1155SRico Sonntag     *
4468add1155SRico Sonntag     * @param int $total The total number of records to query
4478add1155SRico Sonntag     *
4488add1155SRico Sonntag     * @return string
4496ccdf4f0SGreg Roach     * @throws Exception
4508add1155SRico Sonntag     */
4518add1155SRico Sonntag    private function ageBetweenSiblingsName(int $total): string
4528add1155SRico Sonntag    {
4538add1155SRico Sonntag        $rows = $this->ageBetweenSiblingsQuery($total);
4548add1155SRico Sonntag
4558add1155SRico Sonntag        foreach ($rows as $fam) {
4568add1155SRico Sonntag            $family = Family::getInstance($fam->family, $this->tree);
4578add1155SRico Sonntag            $child1 = Individual::getInstance($fam->ch1, $this->tree);
4588add1155SRico Sonntag            $child2 = Individual::getInstance($fam->ch2, $this->tree);
4598add1155SRico Sonntag
4608add1155SRico Sonntag            if ($child1->canShow() && $child2->canShow()) {
46139ca88baSGreg Roach                $return = '<a href="' . e($child2->url()) . '">' . $child2->fullName() . '</a> ';
4628add1155SRico Sonntag                $return .= I18N::translate('and') . ' ';
46339ca88baSGreg Roach                $return .= '<a href="' . e($child1->url()) . '">' . $child1->fullName() . '</a>';
4648add1155SRico Sonntag                $return .= ' <a href="' . e($family->url()) . '">[' . I18N::translate('View this family') . ']</a>';
4658add1155SRico Sonntag            } else {
4668add1155SRico Sonntag                $return = I18N::translate('This information is private and cannot be shown.');
4678add1155SRico Sonntag            }
4688add1155SRico Sonntag
4698add1155SRico Sonntag            return $return;
4708add1155SRico Sonntag        }
4718add1155SRico Sonntag
4728add1155SRico Sonntag        return '';
4738add1155SRico Sonntag    }
4748add1155SRico Sonntag
4758add1155SRico Sonntag    /**
4768add1155SRico Sonntag     * Find the names of siblings with the widest age gap.
4778add1155SRico Sonntag     *
4788add1155SRico Sonntag     * @param int $total
4798add1155SRico Sonntag     *
4808add1155SRico Sonntag     * @return string
4818add1155SRico Sonntag     */
4828add1155SRico Sonntag    public function topAgeBetweenSiblingsName(int $total = 10): string
4838add1155SRico Sonntag    {
4848add1155SRico Sonntag        return $this->ageBetweenSiblingsName($total);
4858add1155SRico Sonntag    }
4868add1155SRico Sonntag
4878add1155SRico Sonntag    /**
4888add1155SRico Sonntag     * Find the widest age gap between siblings.
4898add1155SRico Sonntag     *
4908add1155SRico Sonntag     * @param int $total
4918add1155SRico Sonntag     *
4928add1155SRico Sonntag     * @return string
4938add1155SRico Sonntag     */
4948add1155SRico Sonntag    public function topAgeBetweenSiblings(int $total = 10): string
4958add1155SRico Sonntag    {
4968add1155SRico Sonntag        return $this->ageBetweenSiblingsAge($total);
4978add1155SRico Sonntag    }
4988add1155SRico Sonntag
4998add1155SRico Sonntag    /**
5008add1155SRico Sonntag     * Find the name of siblings with the widest age gap.
5018add1155SRico Sonntag     *
5028add1155SRico Sonntag     * @param int $total
5038add1155SRico Sonntag     *
5048add1155SRico Sonntag     * @return string
5058add1155SRico Sonntag     */
5068add1155SRico Sonntag    public function topAgeBetweenSiblingsFullName(int $total = 10): string
5078add1155SRico Sonntag    {
5088add1155SRico Sonntag        $record = $this->ageBetweenSiblingsNoList($total);
5098add1155SRico Sonntag
510cb2263dcSGreg Roach        if (empty($record)) {
511dd7dd2a1SRico Sonntag            return I18N::translate('This information is not available.');
512cb2263dcSGreg Roach        }
513cb2263dcSGreg Roach
514cb2263dcSGreg Roach        return view('statistics/families/top10-nolist-age', [
5158add1155SRico Sonntag            'record' => $record,
516cb2263dcSGreg Roach        ]);
5178add1155SRico Sonntag    }
5188add1155SRico Sonntag
5198add1155SRico Sonntag    /**
5208add1155SRico Sonntag     * Find the siblings with the widest age gaps.
5218add1155SRico Sonntag     *
5228add1155SRico Sonntag     * @param int    $total
5238add1155SRico Sonntag     * @param string $one
5248add1155SRico Sonntag     *
5258add1155SRico Sonntag     * @return string
5268add1155SRico Sonntag     */
5278add1155SRico Sonntag    public function topAgeBetweenSiblingsList(int $total = 10, string $one = ''): string
5288add1155SRico Sonntag    {
5298add1155SRico Sonntag        $records = $this->ageBetweenSiblingsList($total, (bool) $one);
5308add1155SRico Sonntag
531cb2263dcSGreg Roach        return view('statistics/families/top10-list-age', [
5328add1155SRico Sonntag            'records' => $records,
533cb2263dcSGreg Roach        ]);
5348add1155SRico Sonntag    }
5358add1155SRico Sonntag
5368add1155SRico Sonntag    /**
5378add1155SRico Sonntag     * General query on familes/children.
5388add1155SRico Sonntag     *
5398add1155SRico Sonntag     * @param int    $year1
5408add1155SRico Sonntag     * @param int    $year2
5418add1155SRico Sonntag     *
5428add1155SRico Sonntag     * @return stdClass[]
5438add1155SRico Sonntag     */
544b1126ab4SGreg Roach    public function statsChildrenQuery(int $year1 = -1, int $year2 = -1): array
5458add1155SRico Sonntag    {
546b1126ab4SGreg Roach        $query = DB::table('families')
547b1126ab4SGreg Roach            ->where('f_file', '=', $this->tree->id())
548*7f5c2944SGreg Roach            ->groupBy(['f_numchil'])
549a69f5655SGreg Roach            ->select(['f_numchil', new Expression('COUNT(*) AS total')]);
5508add1155SRico Sonntag
5518add1155SRico Sonntag        if ($year1 >= 0 && $year2 >= 0) {
552b1126ab4SGreg Roach            $query
5530b5fd0a6SGreg Roach                ->join('dates', static function (JoinClause $join): void {
554b1126ab4SGreg Roach                    $join
555b1126ab4SGreg Roach                        ->on('d_file', '=', 'f_file')
556b1126ab4SGreg Roach                        ->on('d_gid', '=', 'f_id');
557b1126ab4SGreg Roach                })
558b1126ab4SGreg Roach                ->where('d_fact', '=', 'MARR')
559b1126ab4SGreg Roach                ->whereIn('d_type', ['@#DGREGORIAN@', '@#DJULIAN@'])
560b1126ab4SGreg Roach                ->whereBetween('d_year', [$year1, $year2]);
5618add1155SRico Sonntag        }
5628add1155SRico Sonntag
563b1126ab4SGreg Roach        return $query->get()->all();
5648add1155SRico Sonntag    }
5658add1155SRico Sonntag
5668add1155SRico Sonntag    /**
5678add1155SRico Sonntag     * Genearl query on families/children.
5688add1155SRico Sonntag     *
5698add1155SRico Sonntag     * @return string
5708add1155SRico Sonntag     */
57188de55fdSRico Sonntag    public function statsChildren(): string
5728add1155SRico Sonntag    {
5738add1155SRico Sonntag        return (new ChartChildren($this->tree))
57488de55fdSRico Sonntag            ->chartChildren();
5758add1155SRico Sonntag    }
5768add1155SRico Sonntag
5778add1155SRico Sonntag    /**
5788add1155SRico Sonntag     * Count the total children.
5798add1155SRico Sonntag     *
5808add1155SRico Sonntag     * @return string
5818add1155SRico Sonntag     */
5828add1155SRico Sonntag    public function totalChildren(): string
5838add1155SRico Sonntag    {
584d1a467e4SGreg Roach        $total = (int) DB::table('families')
585d1a467e4SGreg Roach            ->where('f_file', '=', $this->tree->id())
586d1a467e4SGreg Roach            ->sum('f_numchil');
5878add1155SRico Sonntag
5888add1155SRico Sonntag        return I18N::number($total);
5898add1155SRico Sonntag    }
5908add1155SRico Sonntag
5918add1155SRico Sonntag    /**
5928add1155SRico Sonntag     * Find the average number of children in families.
5938add1155SRico Sonntag     *
5948add1155SRico Sonntag     * @return string
5958add1155SRico Sonntag     */
5968add1155SRico Sonntag    public function averageChildren(): string
5978add1155SRico Sonntag    {
598d1a467e4SGreg Roach        $average = (float) DB::table('families')
599d1a467e4SGreg Roach            ->where('f_file', '=', $this->tree->id())
600d1a467e4SGreg Roach            ->avg('f_numchil');
6018add1155SRico Sonntag
6028add1155SRico Sonntag        return I18N::number($average, 2);
6038add1155SRico Sonntag    }
6048add1155SRico Sonntag
6058add1155SRico Sonntag    /**
6068add1155SRico Sonntag     * General query on families.
6078add1155SRico Sonntag     *
6088add1155SRico Sonntag     * @param int $total
6098add1155SRico Sonntag     *
6108add1155SRico Sonntag     * @return array
6118add1155SRico Sonntag     */
6128add1155SRico Sonntag    private function topTenFamilyQuery(int $total): array
6138add1155SRico Sonntag    {
6142d7289dcSGreg Roach        return DB::table('families')
6152d7289dcSGreg Roach            ->where('f_file', '=', $this->tree->id())
6162d7289dcSGreg Roach            ->orderBy('f_numchil', 'DESC')
6172d7289dcSGreg Roach            ->limit($total)
6182d7289dcSGreg Roach            ->get()
6192d7289dcSGreg Roach            ->map(Family::rowMapper())
6202d7289dcSGreg Roach            ->filter(GedcomRecord::accessFilter())
6210b5fd0a6SGreg Roach            ->map(static function (Family $family): array {
6222d7289dcSGreg Roach                return [
6238add1155SRico Sonntag                    'family' => $family,
6242d7289dcSGreg Roach                    'count'  => $family->numberOfChildren(),
6258add1155SRico Sonntag                ];
6262d7289dcSGreg Roach            })
6272d7289dcSGreg Roach            ->all();
6288add1155SRico Sonntag    }
6298add1155SRico Sonntag
6308add1155SRico Sonntag    /**
6318add1155SRico Sonntag     * The the families with the most children.
6328add1155SRico Sonntag     *
6338add1155SRico Sonntag     * @param int $total
6348add1155SRico Sonntag     *
6358add1155SRico Sonntag     * @return string
6368add1155SRico Sonntag     */
6378add1155SRico Sonntag    public function topTenLargestFamily(int $total = 10): string
6388add1155SRico Sonntag    {
6398add1155SRico Sonntag        $records = $this->topTenFamilyQuery($total);
6408add1155SRico Sonntag
641c0112ce8SGreg Roach        return view('statistics/families/top10-nolist', [
6428add1155SRico Sonntag            'records' => $records,
643c0112ce8SGreg Roach        ]);
6448add1155SRico Sonntag    }
6458add1155SRico Sonntag
6468add1155SRico Sonntag    /**
6478add1155SRico Sonntag     * Find the families with the most children.
6488add1155SRico Sonntag     *
6498add1155SRico Sonntag     * @param int $total
6508add1155SRico Sonntag     *
6518add1155SRico Sonntag     * @return string
6528add1155SRico Sonntag     */
6538add1155SRico Sonntag    public function topTenLargestFamilyList(int $total = 10): string
6548add1155SRico Sonntag    {
6558add1155SRico Sonntag        $records = $this->topTenFamilyQuery($total);
6568add1155SRico Sonntag
657c0112ce8SGreg Roach        return view('statistics/families/top10-list', [
6588add1155SRico Sonntag            'records' => $records,
659c0112ce8SGreg Roach        ]);
6608add1155SRico Sonntag    }
6618add1155SRico Sonntag
6628add1155SRico Sonntag    /**
6638add1155SRico Sonntag     * Create a chart of the largest families.
6648add1155SRico Sonntag     *
6658add1155SRico Sonntag     * @param string|null $color_from
6668add1155SRico Sonntag     * @param string|null $color_to
6678add1155SRico Sonntag     * @param int         $total
6688add1155SRico Sonntag     *
6698add1155SRico Sonntag     * @return string
6708add1155SRico Sonntag     */
6718add1155SRico Sonntag    public function chartLargestFamilies(
6728add1155SRico Sonntag        string $color_from = null,
6738add1155SRico Sonntag        string $color_to = null,
6748add1155SRico Sonntag        int $total = 10
675e2cbf57aSGreg Roach    ): string {
6768add1155SRico Sonntag        return (new ChartFamilyLargest($this->tree))
67788de55fdSRico Sonntag            ->chartLargestFamilies($color_from, $color_to, $total);
6788add1155SRico Sonntag    }
6798add1155SRico Sonntag
6808add1155SRico Sonntag    /**
6818add1155SRico Sonntag     * Find the month in the year of the birth of the first child.
6828add1155SRico Sonntag     *
683999da590SGreg Roach     * @param int $year1
684999da590SGreg Roach     * @param int $year2
6858add1155SRico Sonntag     *
686999da590SGreg Roach     * @return Builder
6878add1155SRico Sonntag     */
688999da590SGreg Roach    public function monthFirstChildQuery(int $year1 = -1, int $year2 = -1): Builder
6898add1155SRico Sonntag    {
690b1126ab4SGreg Roach        $first_child_subquery = DB::table('link')
6910b5fd0a6SGreg Roach            ->join('dates', static function (JoinClause $join): void {
692b1126ab4SGreg Roach                $join
693b1126ab4SGreg Roach                    ->on('d_gid', '=', 'l_to')
694b1126ab4SGreg Roach                    ->on('d_file', '=', 'l_file')
695b1126ab4SGreg Roach                    ->where('d_julianday1', '<>', 0)
696b1126ab4SGreg Roach                    ->whereIn('d_month', ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC']);
697b1126ab4SGreg Roach            })
698b1126ab4SGreg Roach            ->where('l_file', '=', $this->tree->id())
699b1126ab4SGreg Roach            ->where('l_type', '=', 'CHIL')
700a69f5655SGreg Roach            ->select(['l_from AS family_id', new Expression('MIN(d_julianday1) AS min_birth_jd')])
701*7f5c2944SGreg Roach            ->groupBy(['family_id']);
702b1126ab4SGreg Roach
703b1126ab4SGreg Roach        $query = DB::table('link')
7040b5fd0a6SGreg Roach            ->join('dates', static function (JoinClause $join): void {
705b1126ab4SGreg Roach                $join
706b1126ab4SGreg Roach                    ->on('d_gid', '=', 'l_to')
707b1126ab4SGreg Roach                    ->on('d_file', '=', 'l_file');
708b1126ab4SGreg Roach            })
7090b5fd0a6SGreg Roach            ->joinSub($first_child_subquery, 'subquery', static function (JoinClause $join): void {
710b1126ab4SGreg Roach                $join
711b1126ab4SGreg Roach                    ->on('family_id', '=', 'l_from')
712b1126ab4SGreg Roach                    ->on('min_birth_jd', '=', 'd_julianday1');
713b1126ab4SGreg Roach            })
714b1126ab4SGreg Roach            ->where('link.l_file', '=', $this->tree->id())
715999da590SGreg Roach            ->where('link.l_type', '=', 'CHIL')
716a69f5655SGreg Roach            ->select(['d_month', new Expression('COUNT(*) AS total')])
717999da590SGreg Roach            ->groupBy(['d_month']);
718b1126ab4SGreg Roach
719999da590SGreg Roach        if ($year1 >= 0 && $year2 >= 0) {
720999da590SGreg Roach            $query->whereBetween('d_year', [$year1, $year2]);
721999da590SGreg Roach        }
722b1126ab4SGreg Roach
723999da590SGreg Roach        return $query;
724999da590SGreg Roach    }
725999da590SGreg Roach
726999da590SGreg Roach    /**
727999da590SGreg Roach     * Find the month in the year of the birth of the first child.
728999da590SGreg Roach     *
729999da590SGreg Roach     * @param int $year1
730999da590SGreg Roach     * @param int $year2
731999da590SGreg Roach     *
732999da590SGreg Roach     * @return Builder
733999da590SGreg Roach     */
734999da590SGreg Roach    public function monthFirstChildBySexQuery(int $year1 = -1, int $year2 = -1): Builder
735999da590SGreg Roach    {
736999da590SGreg Roach        return $this->monthFirstChildQuery($year1, $year2)
7370b5fd0a6SGreg Roach                ->join('individuals', static function (JoinClause $join): void {
738b1126ab4SGreg Roach                    $join
739b1126ab4SGreg Roach                        ->on('i_file', '=', 'l_file')
740b1126ab4SGreg Roach                        ->on('i_id', '=', 'l_to');
741b1126ab4SGreg Roach                })
742a69f5655SGreg Roach                ->select(['d_month', 'i_sex', new Expression('COUNT(*) AS total')])
743b1126ab4SGreg Roach                ->groupBy(['d_month', 'i_sex']);
7448add1155SRico Sonntag    }
7458add1155SRico Sonntag
7468add1155SRico Sonntag    /**
7478add1155SRico Sonntag     * Number of husbands.
7488add1155SRico Sonntag     *
7498add1155SRico Sonntag     * @return string
7508add1155SRico Sonntag     */
7518add1155SRico Sonntag    public function totalMarriedMales(): string
7528add1155SRico Sonntag    {
753d1a467e4SGreg Roach        $n = (int) DB::table('families')
754d1a467e4SGreg Roach            ->where('f_file', '=', $this->tree->id())
755d1a467e4SGreg Roach            ->where('f_gedcom', 'LIKE', "%\n1 MARR%")
756d1a467e4SGreg Roach            ->distinct()
757d1a467e4SGreg Roach            ->count('f_husb');
7588add1155SRico Sonntag
7598add1155SRico Sonntag        return I18N::number($n);
7608add1155SRico Sonntag    }
7618add1155SRico Sonntag
7628add1155SRico Sonntag    /**
7638add1155SRico Sonntag     * Number of wives.
7648add1155SRico Sonntag     *
7658add1155SRico Sonntag     * @return string
7668add1155SRico Sonntag     */
7678add1155SRico Sonntag    public function totalMarriedFemales(): string
7688add1155SRico Sonntag    {
769d1a467e4SGreg Roach        $n = (int) DB::table('families')
770d1a467e4SGreg Roach            ->where('f_file', '=', $this->tree->id())
771d1a467e4SGreg Roach            ->where('f_gedcom', 'LIKE', "%\n1 MARR%")
772d1a467e4SGreg Roach            ->distinct()
773d1a467e4SGreg Roach            ->count('f_wife');
7748add1155SRico Sonntag
7758add1155SRico Sonntag        return I18N::number($n);
7768add1155SRico Sonntag    }
7778add1155SRico Sonntag
7788add1155SRico Sonntag    /**
7798add1155SRico Sonntag     * General query on parents.
7808add1155SRico Sonntag     *
7818add1155SRico Sonntag     * @param string $type
7828add1155SRico Sonntag     * @param string $age_dir
7838add1155SRico Sonntag     * @param string $sex
7848add1155SRico Sonntag     * @param bool   $show_years
7858add1155SRico Sonntag     *
7868add1155SRico Sonntag     * @return string
7878add1155SRico Sonntag     */
7888add1155SRico Sonntag    private function parentsQuery(string $type, string $age_dir, string $sex, bool $show_years): string
7898add1155SRico Sonntag    {
7908add1155SRico Sonntag        if ($sex === 'F') {
7918add1155SRico Sonntag            $sex_field = 'WIFE';
7928add1155SRico Sonntag        } else {
7938add1155SRico Sonntag            $sex_field = 'HUSB';
7948add1155SRico Sonntag        }
7958add1155SRico Sonntag
7968add1155SRico Sonntag        if ($age_dir !== 'ASC') {
7978add1155SRico Sonntag            $age_dir = 'DESC';
7988add1155SRico Sonntag        }
7998add1155SRico Sonntag
8002c928961SGreg Roach        $prefix = DB::connection()->getTablePrefix();
8018add1155SRico Sonntag
8022c928961SGreg Roach        $row = DB::table('link AS parentfamily')
8030b5fd0a6SGreg Roach            ->join('link AS childfamily', static function (JoinClause $join): void {
8042c928961SGreg Roach                $join
8052c928961SGreg Roach                    ->on('childfamily.l_file', '=', 'parentfamily.l_file')
8062c928961SGreg Roach                    ->on('childfamily.l_from', '=', 'parentfamily.l_from')
8072c928961SGreg Roach                    ->where('childfamily.l_type', '=', 'CHIL');
8082c928961SGreg Roach            })
8090b5fd0a6SGreg Roach            ->join('dates AS birth', static function (JoinClause $join): void {
8102c928961SGreg Roach                $join
8112c928961SGreg Roach                    ->on('birth.d_file', '=', 'parentfamily.l_file')
8122c928961SGreg Roach                    ->on('birth.d_gid', '=', 'parentfamily.l_to')
8132c928961SGreg Roach                    ->where('birth.d_fact', '=', 'BIRT')
8142c928961SGreg Roach                    ->where('birth.d_julianday1', '<>', 0);
8152c928961SGreg Roach            })
8160b5fd0a6SGreg Roach            ->join('dates AS childbirth', static function (JoinClause $join): void {
8172c928961SGreg Roach                $join
8182c928961SGreg Roach                    ->on('childbirth.d_file', '=', 'parentfamily.l_file')
8193413ec75SRico Sonntag                    ->on('childbirth.d_gid', '=', 'childfamily.l_to')
8203413ec75SRico Sonntag                    ->where('childbirth.d_fact', '=', 'BIRT');
8212c928961SGreg Roach            })
8222c928961SGreg Roach            ->where('childfamily.l_file', '=', $this->tree->id())
8232c928961SGreg Roach            ->where('parentfamily.l_type', '=', $sex_field)
8242c928961SGreg Roach            ->where('childbirth.d_julianday2', '>', 'birth.d_julianday1')
825a69f5655SGreg Roach            ->select(['parentfamily.l_to AS id', new Expression($prefix . 'childbirth.d_julianday2 - ' . $prefix . 'birth.d_julianday1 AS age')])
8262c928961SGreg Roach            ->take(1)
8272c928961SGreg Roach            ->orderBy('age', $age_dir)
8282c928961SGreg Roach            ->get()
8292c928961SGreg Roach            ->first();
8302c928961SGreg Roach
8312c928961SGreg Roach        if ($row === null) {
8328add1155SRico Sonntag            return '';
8338add1155SRico Sonntag        }
8348add1155SRico Sonntag
8358add1155SRico Sonntag        $person = Individual::getInstance($row->id, $this->tree);
8368add1155SRico Sonntag
8378add1155SRico Sonntag        switch ($type) {
8388add1155SRico Sonntag            default:
8398add1155SRico Sonntag            case 'full':
8408add1155SRico Sonntag                if ($person && $person->canShow()) {
8418add1155SRico Sonntag                    $result = $person->formatList();
8428add1155SRico Sonntag                } else {
8438add1155SRico Sonntag                    $result = I18N::translate('This information is private and cannot be shown.');
8448add1155SRico Sonntag                }
8458add1155SRico Sonntag                break;
8468add1155SRico Sonntag
8478add1155SRico Sonntag            case 'name':
84839ca88baSGreg Roach                $result = '<a href="' . e($person->url()) . '">' . $person->fullName() . '</a>';
8498add1155SRico Sonntag                break;
8508add1155SRico Sonntag
8518add1155SRico Sonntag            case 'age':
8528add1155SRico Sonntag                $age = $row->age;
8538add1155SRico Sonntag
8548add1155SRico Sonntag                if ($show_years) {
8558add1155SRico Sonntag                    $result = $this->calculateAge((int) $row->age);
8568add1155SRico Sonntag                } else {
8578add1155SRico Sonntag                    $result = (string) floor($age / 365.25);
8588add1155SRico Sonntag                }
8598add1155SRico Sonntag
8608add1155SRico Sonntag                break;
8618add1155SRico Sonntag        }
8628add1155SRico Sonntag
8638add1155SRico Sonntag        return $result;
8648add1155SRico Sonntag    }
8658add1155SRico Sonntag
8668add1155SRico Sonntag    /**
8678add1155SRico Sonntag     * Find the youngest mother
8688add1155SRico Sonntag     *
8698add1155SRico Sonntag     * @return string
8708add1155SRico Sonntag     */
8718add1155SRico Sonntag    public function youngestMother(): string
8728add1155SRico Sonntag    {
8738add1155SRico Sonntag        return $this->parentsQuery('full', 'ASC', 'F', false);
8748add1155SRico Sonntag    }
8758add1155SRico Sonntag
8768add1155SRico Sonntag    /**
8778add1155SRico Sonntag     * Find the name of the youngest mother.
8788add1155SRico Sonntag     *
8798add1155SRico Sonntag     * @return string
8808add1155SRico Sonntag     */
8818add1155SRico Sonntag    public function youngestMotherName(): string
8828add1155SRico Sonntag    {
8838add1155SRico Sonntag        return $this->parentsQuery('name', 'ASC', 'F', false);
8848add1155SRico Sonntag    }
8858add1155SRico Sonntag
8868add1155SRico Sonntag    /**
8878add1155SRico Sonntag     * Find the age of the youngest mother.
8888add1155SRico Sonntag     *
8898add1155SRico Sonntag     * @param string $show_years
8908add1155SRico Sonntag     *
8918add1155SRico Sonntag     * @return string
8928add1155SRico Sonntag     */
8938add1155SRico Sonntag    public function youngestMotherAge(string $show_years = ''): string
8948add1155SRico Sonntag    {
8958add1155SRico Sonntag        return $this->parentsQuery('age', 'ASC', 'F', (bool) $show_years);
8968add1155SRico Sonntag    }
8978add1155SRico Sonntag
8988add1155SRico Sonntag    /**
8998add1155SRico Sonntag     * Find the oldest mother.
9008add1155SRico Sonntag     *
9018add1155SRico Sonntag     * @return string
9028add1155SRico Sonntag     */
9038add1155SRico Sonntag    public function oldestMother(): string
9048add1155SRico Sonntag    {
9058add1155SRico Sonntag        return $this->parentsQuery('full', 'DESC', 'F', false);
9068add1155SRico Sonntag    }
9078add1155SRico Sonntag
9088add1155SRico Sonntag    /**
9098add1155SRico Sonntag     * Find the name of the oldest mother.
9108add1155SRico Sonntag     *
9118add1155SRico Sonntag     * @return string
9128add1155SRico Sonntag     */
9138add1155SRico Sonntag    public function oldestMotherName(): string
9148add1155SRico Sonntag    {
9158add1155SRico Sonntag        return $this->parentsQuery('name', 'DESC', 'F', false);
9168add1155SRico Sonntag    }
9178add1155SRico Sonntag
9188add1155SRico Sonntag    /**
9198add1155SRico Sonntag     * Find the age of the oldest mother.
9208add1155SRico Sonntag     *
9218add1155SRico Sonntag     * @param string $show_years
9228add1155SRico Sonntag     *
9238add1155SRico Sonntag     * @return string
9248add1155SRico Sonntag     */
9258add1155SRico Sonntag    public function oldestMotherAge(string $show_years = ''): string
9268add1155SRico Sonntag    {
9278add1155SRico Sonntag        return $this->parentsQuery('age', 'DESC', 'F', (bool) $show_years);
9288add1155SRico Sonntag    }
9298add1155SRico Sonntag
9308add1155SRico Sonntag    /**
9318add1155SRico Sonntag     * Find the youngest father.
9328add1155SRico Sonntag     *
9338add1155SRico Sonntag     * @return string
9348add1155SRico Sonntag     */
9358add1155SRico Sonntag    public function youngestFather(): string
9368add1155SRico Sonntag    {
9378add1155SRico Sonntag        return $this->parentsQuery('full', 'ASC', 'M', false);
9388add1155SRico Sonntag    }
9398add1155SRico Sonntag
9408add1155SRico Sonntag    /**
9418add1155SRico Sonntag     * Find the name of the youngest father.
9428add1155SRico Sonntag     *
9438add1155SRico Sonntag     * @return string
9448add1155SRico Sonntag     */
9458add1155SRico Sonntag    public function youngestFatherName(): string
9468add1155SRico Sonntag    {
9478add1155SRico Sonntag        return $this->parentsQuery('name', 'ASC', 'M', false);
9488add1155SRico Sonntag    }
9498add1155SRico Sonntag
9508add1155SRico Sonntag    /**
9518add1155SRico Sonntag     * Find the age of the youngest father.
9528add1155SRico Sonntag     *
9538add1155SRico Sonntag     * @param string $show_years
9548add1155SRico Sonntag     *
9558add1155SRico Sonntag     * @return string
9568add1155SRico Sonntag     */
9578add1155SRico Sonntag    public function youngestFatherAge(string $show_years = ''): string
9588add1155SRico Sonntag    {
9598add1155SRico Sonntag        return $this->parentsQuery('age', 'ASC', 'M', (bool) $show_years);
9608add1155SRico Sonntag    }
9618add1155SRico Sonntag
9628add1155SRico Sonntag    /**
9638add1155SRico Sonntag     * Find the oldest father.
9648add1155SRico Sonntag     *
9658add1155SRico Sonntag     * @return string
9668add1155SRico Sonntag     */
9678add1155SRico Sonntag    public function oldestFather(): string
9688add1155SRico Sonntag    {
9698add1155SRico Sonntag        return $this->parentsQuery('full', 'DESC', 'M', false);
9708add1155SRico Sonntag    }
9718add1155SRico Sonntag
9728add1155SRico Sonntag    /**
9738add1155SRico Sonntag     * Find the name of the oldest father.
9748add1155SRico Sonntag     *
9758add1155SRico Sonntag     * @return string
9768add1155SRico Sonntag     */
9778add1155SRico Sonntag    public function oldestFatherName(): string
9788add1155SRico Sonntag    {
9798add1155SRico Sonntag        return $this->parentsQuery('name', 'DESC', 'M', false);
9808add1155SRico Sonntag    }
9818add1155SRico Sonntag
9828add1155SRico Sonntag    /**
9838add1155SRico Sonntag     * Find the age of the oldest father.
9848add1155SRico Sonntag     *
9858add1155SRico Sonntag     * @param string $show_years
9868add1155SRico Sonntag     *
9878add1155SRico Sonntag     * @return string
9888add1155SRico Sonntag     */
9898add1155SRico Sonntag    public function oldestFatherAge(string $show_years = ''): string
9908add1155SRico Sonntag    {
9918add1155SRico Sonntag        return $this->parentsQuery('age', 'DESC', 'M', (bool) $show_years);
9928add1155SRico Sonntag    }
9938add1155SRico Sonntag
9948add1155SRico Sonntag    /**
9958add1155SRico Sonntag     * General query on age at marriage.
9968add1155SRico Sonntag     *
9978add1155SRico Sonntag     * @param string $type
9985ad3f7b6SGreg Roach     * @param string $age_dir "ASC" or "DESC"
9998add1155SRico Sonntag     * @param int    $total
10008add1155SRico Sonntag     *
10018add1155SRico Sonntag     * @return string
10028add1155SRico Sonntag     */
10038add1155SRico Sonntag    private function ageOfMarriageQuery(string $type, string $age_dir, int $total): string
10048add1155SRico Sonntag    {
10055ad3f7b6SGreg Roach        $prefix = DB::connection()->getTablePrefix();
10068add1155SRico Sonntag
10075ad3f7b6SGreg Roach        $hrows = DB::table('families')
10085ad3f7b6SGreg Roach            ->where('f_file', '=', $this->tree->id())
10090b5fd0a6SGreg Roach            ->join('dates AS married', static function (JoinClause $join): void {
10105ad3f7b6SGreg Roach                $join
10115ad3f7b6SGreg Roach                    ->on('married.d_file', '=', 'f_file')
10125ad3f7b6SGreg Roach                    ->on('married.d_gid', '=', 'f_id')
10135ad3f7b6SGreg Roach                    ->where('married.d_fact', '=', 'MARR')
10145ad3f7b6SGreg Roach                    ->where('married.d_julianday1', '<>', 0);
10155ad3f7b6SGreg Roach            })
10160b5fd0a6SGreg Roach            ->join('dates AS husbdeath', static function (JoinClause $join): void {
10175ad3f7b6SGreg Roach                $join
10185ad3f7b6SGreg Roach                    ->on('husbdeath.d_gid', '=', 'f_husb')
10195ad3f7b6SGreg Roach                    ->on('husbdeath.d_file', '=', 'f_file')
10205ad3f7b6SGreg Roach                    ->where('husbdeath.d_fact', '=', 'DEAT');
10215ad3f7b6SGreg Roach            })
10225ad3f7b6SGreg Roach            ->whereColumn('married.d_julianday1', '<', 'husbdeath.d_julianday2')
1023*7f5c2944SGreg Roach            ->groupBy(['f_id'])
1024a69f5655SGreg Roach            ->select(['f_id AS family', new Expression('MIN(' . $prefix . 'husbdeath.d_julianday2 - ' . $prefix . 'married.d_julianday1) AS age')])
10255ad3f7b6SGreg Roach            ->get()
10265ad3f7b6SGreg Roach            ->all();
10278add1155SRico Sonntag
10285ad3f7b6SGreg Roach        $wrows = DB::table('families')
10295ad3f7b6SGreg Roach            ->where('f_file', '=', $this->tree->id())
10300b5fd0a6SGreg Roach            ->join('dates AS married', static function (JoinClause $join): void {
10315ad3f7b6SGreg Roach                $join
10325ad3f7b6SGreg Roach                    ->on('married.d_file', '=', 'f_file')
10335ad3f7b6SGreg Roach                    ->on('married.d_gid', '=', 'f_id')
10345ad3f7b6SGreg Roach                    ->where('married.d_fact', '=', 'MARR')
10355ad3f7b6SGreg Roach                    ->where('married.d_julianday1', '<>', 0);
10365ad3f7b6SGreg Roach            })
10370b5fd0a6SGreg Roach            ->join('dates AS wifedeath', static function (JoinClause $join): void {
10385ad3f7b6SGreg Roach                $join
10395ad3f7b6SGreg Roach                    ->on('wifedeath.d_gid', '=', 'f_wife')
10405ad3f7b6SGreg Roach                    ->on('wifedeath.d_file', '=', 'f_file')
10415ad3f7b6SGreg Roach                    ->where('wifedeath.d_fact', '=', 'DEAT');
10425ad3f7b6SGreg Roach            })
10435ad3f7b6SGreg Roach            ->whereColumn('married.d_julianday1', '<', 'wifedeath.d_julianday2')
1044*7f5c2944SGreg Roach            ->groupBy(['f_id'])
1045a69f5655SGreg Roach            ->select(['f_id AS family', new Expression('MIN(' . $prefix . 'wifedeath.d_julianday2 - ' . $prefix . 'married.d_julianday1) AS age')])
10465ad3f7b6SGreg Roach            ->get()
10475ad3f7b6SGreg Roach            ->all();
10488add1155SRico Sonntag
10495ad3f7b6SGreg Roach        $drows = DB::table('families')
10505ad3f7b6SGreg Roach            ->where('f_file', '=', $this->tree->id())
10510b5fd0a6SGreg Roach            ->join('dates AS married', static function (JoinClause $join): void {
10525ad3f7b6SGreg Roach                $join
10535ad3f7b6SGreg Roach                    ->on('married.d_file', '=', 'f_file')
10545ad3f7b6SGreg Roach                    ->on('married.d_gid', '=', 'f_id')
10555ad3f7b6SGreg Roach                    ->where('married.d_fact', '=', 'MARR')
10565ad3f7b6SGreg Roach                    ->where('married.d_julianday1', '<>', 0);
10575ad3f7b6SGreg Roach            })
10580b5fd0a6SGreg Roach            ->join('dates AS divorced', static function (JoinClause $join): void {
10595ad3f7b6SGreg Roach                $join
10605ad3f7b6SGreg Roach                    ->on('divorced.d_gid', '=', 'f_id')
10615ad3f7b6SGreg Roach                    ->on('divorced.d_file', '=', 'f_file')
10625ad3f7b6SGreg Roach                    ->whereIn('divorced.d_fact', ['DIV', 'ANUL', '_SEPR']);
10635ad3f7b6SGreg Roach            })
10645ad3f7b6SGreg Roach            ->whereColumn('married.d_julianday1', '<', 'divorced.d_julianday2')
1065*7f5c2944SGreg Roach            ->groupBy(['f_id'])
1066a69f5655SGreg Roach            ->select(['f_id AS family', new Expression('MIN(' . $prefix . 'divorced.d_julianday2 - ' . $prefix . 'married.d_julianday1) AS age')])
10675ad3f7b6SGreg Roach            ->get()
10685ad3f7b6SGreg Roach            ->all();
10698add1155SRico Sonntag
10708add1155SRico Sonntag        $rows = [];
10718add1155SRico Sonntag        foreach ($drows as $family) {
10728add1155SRico Sonntag            $rows[$family->family] = $family->age;
10738add1155SRico Sonntag        }
10748add1155SRico Sonntag
10758add1155SRico Sonntag        foreach ($hrows as $family) {
10768add1155SRico Sonntag            if (!isset($rows[$family->family])) {
10778add1155SRico Sonntag                $rows[$family->family] = $family->age;
10788add1155SRico Sonntag            }
10798add1155SRico Sonntag        }
10808add1155SRico Sonntag
10818add1155SRico Sonntag        foreach ($wrows as $family) {
10828add1155SRico Sonntag            if (!isset($rows[$family->family])) {
10838add1155SRico Sonntag                $rows[$family->family] = $family->age;
10848add1155SRico Sonntag            } elseif ($rows[$family->family] > $family->age) {
10858add1155SRico Sonntag                $rows[$family->family] = $family->age;
10868add1155SRico Sonntag            }
10878add1155SRico Sonntag        }
10888add1155SRico Sonntag
10898add1155SRico Sonntag        if ($age_dir === 'DESC') {
10908add1155SRico Sonntag            arsort($rows);
10918add1155SRico Sonntag        } else {
10928add1155SRico Sonntag            asort($rows);
10938add1155SRico Sonntag        }
10948add1155SRico Sonntag
10958add1155SRico Sonntag        $top10 = [];
10968add1155SRico Sonntag        $i     = 0;
1097b092a991SGreg Roach        foreach ($rows as $xref => $age) {
1098b092a991SGreg Roach            $family = Family::getInstance((string) $xref, $this->tree);
10998add1155SRico Sonntag            if ($type === 'name') {
11008add1155SRico Sonntag                return $family->formatList();
11018add1155SRico Sonntag            }
11028add1155SRico Sonntag
11038add1155SRico Sonntag            $age = $this->calculateAge((int) $age);
11048add1155SRico Sonntag
11058add1155SRico Sonntag            if ($type === 'age') {
11068add1155SRico Sonntag                return $age;
11078add1155SRico Sonntag            }
11088add1155SRico Sonntag
110939ca88baSGreg Roach            $husb = $family->husband();
111039ca88baSGreg Roach            $wife = $family->wife();
11118add1155SRico Sonntag
11128add1155SRico Sonntag            if (($husb && ($husb->getAllDeathDates() || !$husb->isDead()))
11138add1155SRico Sonntag                && ($wife && ($wife->getAllDeathDates() || !$wife->isDead()))
11148add1155SRico Sonntag            ) {
11158add1155SRico Sonntag                if ($family && $family->canShow()) {
11168add1155SRico Sonntag                    if ($type === 'list') {
111739ca88baSGreg Roach                        $top10[] = '<li><a href="' . e($family->url()) . '">' . $family->fullName() . '</a> (' . $age . ')' . '</li>';
11188add1155SRico Sonntag                    } else {
111939ca88baSGreg Roach                        $top10[] = '<a href="' . e($family->url()) . '">' . $family->fullName() . '</a> (' . $age . ')';
11208add1155SRico Sonntag                    }
11218add1155SRico Sonntag                }
11228add1155SRico Sonntag                if (++$i === $total) {
11238add1155SRico Sonntag                    break;
11248add1155SRico Sonntag                }
11258add1155SRico Sonntag            }
11268add1155SRico Sonntag        }
11278add1155SRico Sonntag
11288add1155SRico Sonntag        if ($type === 'list') {
11298add1155SRico Sonntag            $top10 = implode('', $top10);
11308add1155SRico Sonntag        } else {
11318add1155SRico Sonntag            $top10 = implode('; ', $top10);
11328add1155SRico Sonntag        }
11338add1155SRico Sonntag
11348add1155SRico Sonntag        if (I18N::direction() === 'rtl') {
11358add1155SRico Sonntag            $top10 = str_replace([
11368add1155SRico Sonntag                '[',
11378add1155SRico Sonntag                ']',
11388add1155SRico Sonntag                '(',
11398add1155SRico Sonntag                ')',
11408add1155SRico Sonntag                '+',
11418add1155SRico Sonntag            ], [
11428add1155SRico Sonntag                '&rlm;[',
11438add1155SRico Sonntag                '&rlm;]',
11448add1155SRico Sonntag                '&rlm;(',
11458add1155SRico Sonntag                '&rlm;)',
11468add1155SRico Sonntag                '&rlm;+',
11478add1155SRico Sonntag            ], $top10);
11488add1155SRico Sonntag        }
11498add1155SRico Sonntag
11508add1155SRico Sonntag        if ($type === 'list') {
11518add1155SRico Sonntag            return '<ul>' . $top10 . '</ul>';
11528add1155SRico Sonntag        }
11538add1155SRico Sonntag
11548add1155SRico Sonntag        return $top10;
11558add1155SRico Sonntag    }
11568add1155SRico Sonntag
11578add1155SRico Sonntag    /**
11588add1155SRico Sonntag     * General query on marriage ages.
11598add1155SRico Sonntag     *
11608add1155SRico Sonntag     * @return string
11618add1155SRico Sonntag     */
11628add1155SRico Sonntag    public function topAgeOfMarriageFamily(): string
11638add1155SRico Sonntag    {
11648add1155SRico Sonntag        return $this->ageOfMarriageQuery('name', 'DESC', 1);
11658add1155SRico Sonntag    }
11668add1155SRico Sonntag
11678add1155SRico Sonntag    /**
11688add1155SRico Sonntag     * General query on marriage ages.
11698add1155SRico Sonntag     *
11708add1155SRico Sonntag     * @return string
11718add1155SRico Sonntag     */
11728add1155SRico Sonntag    public function topAgeOfMarriage(): string
11738add1155SRico Sonntag    {
11748add1155SRico Sonntag        return $this->ageOfMarriageQuery('age', 'DESC', 1);
11758add1155SRico Sonntag    }
11768add1155SRico Sonntag
11778add1155SRico Sonntag    /**
11788add1155SRico Sonntag     * General query on marriage ages.
11798add1155SRico Sonntag     *
11808add1155SRico Sonntag     * @param int $total
11818add1155SRico Sonntag     *
11828add1155SRico Sonntag     * @return string
11838add1155SRico Sonntag     */
11848add1155SRico Sonntag    public function topAgeOfMarriageFamilies(int $total = 10): string
11858add1155SRico Sonntag    {
11868add1155SRico Sonntag        return $this->ageOfMarriageQuery('nolist', 'DESC', $total);
11878add1155SRico Sonntag    }
11888add1155SRico Sonntag
11898add1155SRico Sonntag    /**
11908add1155SRico Sonntag     * General query on marriage ages.
11918add1155SRico Sonntag     *
11928add1155SRico Sonntag     * @param int $total
11938add1155SRico Sonntag     *
11948add1155SRico Sonntag     * @return string
11958add1155SRico Sonntag     */
11968add1155SRico Sonntag    public function topAgeOfMarriageFamiliesList(int $total = 10): string
11978add1155SRico Sonntag    {
11988add1155SRico Sonntag        return $this->ageOfMarriageQuery('list', 'DESC', $total);
11998add1155SRico Sonntag    }
12008add1155SRico Sonntag
12018add1155SRico Sonntag    /**
12028add1155SRico Sonntag     * General query on marriage ages.
12038add1155SRico Sonntag     *
12048add1155SRico Sonntag     * @return string
12058add1155SRico Sonntag     */
12068add1155SRico Sonntag    public function minAgeOfMarriageFamily(): string
12078add1155SRico Sonntag    {
12088add1155SRico Sonntag        return $this->ageOfMarriageQuery('name', 'ASC', 1);
12098add1155SRico Sonntag    }
12108add1155SRico Sonntag
12118add1155SRico Sonntag    /**
12128add1155SRico Sonntag     * General query on marriage ages.
12138add1155SRico Sonntag     *
12148add1155SRico Sonntag     * @return string
12158add1155SRico Sonntag     */
12168add1155SRico Sonntag    public function minAgeOfMarriage(): string
12178add1155SRico Sonntag    {
12188add1155SRico Sonntag        return $this->ageOfMarriageQuery('age', 'ASC', 1);
12198add1155SRico Sonntag    }
12208add1155SRico Sonntag
12218add1155SRico Sonntag    /**
12228add1155SRico Sonntag     * General query on marriage ages.
12238add1155SRico Sonntag     *
12248add1155SRico Sonntag     * @param int $total
12258add1155SRico Sonntag     *
12268add1155SRico Sonntag     * @return string
12278add1155SRico Sonntag     */
12288add1155SRico Sonntag    public function minAgeOfMarriageFamilies(int $total = 10): string
12298add1155SRico Sonntag    {
12308add1155SRico Sonntag        return $this->ageOfMarriageQuery('nolist', 'ASC', $total);
12318add1155SRico Sonntag    }
12328add1155SRico Sonntag
12338add1155SRico Sonntag    /**
12348add1155SRico Sonntag     * General query on marriage ages.
12358add1155SRico Sonntag     *
12368add1155SRico Sonntag     * @param int $total
12378add1155SRico Sonntag     *
12388add1155SRico Sonntag     * @return string
12398add1155SRico Sonntag     */
12408add1155SRico Sonntag    public function minAgeOfMarriageFamiliesList(int $total = 10): string
12418add1155SRico Sonntag    {
12428add1155SRico Sonntag        return $this->ageOfMarriageQuery('list', 'ASC', $total);
12438add1155SRico Sonntag    }
12448add1155SRico Sonntag
12458add1155SRico Sonntag    /**
12468add1155SRico Sonntag     * Find the ages between spouses.
12478add1155SRico Sonntag     *
12488add1155SRico Sonntag     * @param string $age_dir
12498add1155SRico Sonntag     * @param int    $total
12508add1155SRico Sonntag     *
12518add1155SRico Sonntag     * @return array
12528add1155SRico Sonntag     */
12538add1155SRico Sonntag    private function ageBetweenSpousesQuery(string $age_dir, int $total): array
12548add1155SRico Sonntag    {
1255d1a467e4SGreg Roach        $prefix = DB::connection()->getTablePrefix();
1256d1a467e4SGreg Roach
1257d1a467e4SGreg Roach        $query = DB::table('families')
1258d1a467e4SGreg Roach            ->where('f_file', '=', $this->tree->id())
12590b5fd0a6SGreg Roach            ->join('dates AS wife', static function (JoinClause $join): void {
1260d1a467e4SGreg Roach                $join
1261d1a467e4SGreg Roach                    ->on('wife.d_gid', '=', 'f_wife')
1262d1a467e4SGreg Roach                    ->on('wife.d_file', '=', 'f_file')
1263d1a467e4SGreg Roach                    ->where('wife.d_fact', '=', 'BIRT')
1264d1a467e4SGreg Roach                    ->where('wife.d_julianday1', '<>', 0);
1265d1a467e4SGreg Roach            })
12660b5fd0a6SGreg Roach            ->join('dates AS husb', static function (JoinClause $join): void {
1267d1a467e4SGreg Roach                $join
1268d1a467e4SGreg Roach                    ->on('husb.d_gid', '=', 'f_husb')
1269d1a467e4SGreg Roach                    ->on('husb.d_file', '=', 'f_file')
1270d1a467e4SGreg Roach                    ->where('husb.d_fact', '=', 'BIRT')
1271d1a467e4SGreg Roach                    ->where('husb.d_julianday1', '<>', 0);
1272d1a467e4SGreg Roach            });
1273d1a467e4SGreg Roach
12748add1155SRico Sonntag        if ($age_dir === 'DESC') {
1275d1a467e4SGreg Roach            $query
1276d1a467e4SGreg Roach                ->whereColumn('wife.d_julianday1', '>=', 'husb.d_julianday1')
1277a69f5655SGreg Roach                ->orderBy(new Expression('MIN(' . $prefix . 'wife.d_julianday1) - MIN(' . $prefix . 'husb.d_julianday1)'), 'DESC');
12788add1155SRico Sonntag        } else {
1279d1a467e4SGreg Roach            $query
1280d1a467e4SGreg Roach                ->whereColumn('husb.d_julianday1', '>=', 'wife.d_julianday1')
1281a69f5655SGreg Roach                ->orderBy(new Expression('MIN(' . $prefix . 'husb.d_julianday1) - MIN(' . $prefix . 'wife.d_julianday1)'), 'DESC');
12828add1155SRico Sonntag        }
12838add1155SRico Sonntag
1284c0112ce8SGreg Roach        return $query
1285d1a467e4SGreg Roach            ->groupBy(['f_id', 'f_file'])
1286d1a467e4SGreg Roach            ->select('families.*')
1287d1a467e4SGreg Roach            ->take($total)
1288d1a467e4SGreg Roach            ->get()
1289d1a467e4SGreg Roach            ->map(Family::rowMapper())
1290c0112ce8SGreg Roach            ->filter(GedcomRecord::accessFilter())
1291c0112ce8SGreg Roach            ->map(function (Family $family) use ($age_dir): array {
1292d1a467e4SGreg Roach                $husb_birt_jd = $family->husband()->getBirthDate()->minimumJulianDay();
1293d1a467e4SGreg Roach                $wife_birt_jd = $family->wife()->getBirthDate()->minimumJulianDay();
12948add1155SRico Sonntag
1295d1a467e4SGreg Roach                if ($age_dir === 'DESC') {
1296d1a467e4SGreg Roach                    $diff = $wife_birt_jd - $husb_birt_jd;
1297d1a467e4SGreg Roach                } else {
1298d1a467e4SGreg Roach                    $diff = $husb_birt_jd - $wife_birt_jd;
12998add1155SRico Sonntag                }
13008add1155SRico Sonntag
1301c0112ce8SGreg Roach                return [
13028add1155SRico Sonntag                    'family' => $family,
130393ccd686SRico Sonntag                    'age'    => $this->calculateAge($diff),
13048add1155SRico Sonntag                ];
1305c0112ce8SGreg Roach            })
1306c0112ce8SGreg Roach            ->all();
13078add1155SRico Sonntag    }
13088add1155SRico Sonntag
13098add1155SRico Sonntag    /**
13108add1155SRico Sonntag     * Find the age between husband and wife.
13118add1155SRico Sonntag     *
13128add1155SRico Sonntag     * @param int $total
13138add1155SRico Sonntag     *
13148add1155SRico Sonntag     * @return string
13158add1155SRico Sonntag     */
13168add1155SRico Sonntag    public function ageBetweenSpousesMF(int $total = 10): string
13178add1155SRico Sonntag    {
13188add1155SRico Sonntag        $records = $this->ageBetweenSpousesQuery('DESC', $total);
13198add1155SRico Sonntag
1320c0112ce8SGreg Roach        return view('statistics/families/top10-nolist-spouses', [
13218add1155SRico Sonntag            'records' => $records,
1322c0112ce8SGreg Roach        ]);
13238add1155SRico Sonntag    }
13248add1155SRico Sonntag
13258add1155SRico Sonntag    /**
13268add1155SRico Sonntag     * Find the age between husband and wife.
13278add1155SRico Sonntag     *
13288add1155SRico Sonntag     * @param int $total
13298add1155SRico Sonntag     *
13308add1155SRico Sonntag     * @return string
13318add1155SRico Sonntag     */
13328add1155SRico Sonntag    public function ageBetweenSpousesMFList(int $total = 10): string
13338add1155SRico Sonntag    {
13348add1155SRico Sonntag        $records = $this->ageBetweenSpousesQuery('DESC', $total);
13358add1155SRico Sonntag
1336c0112ce8SGreg Roach        return view('statistics/families/top10-list-spouses', [
13378add1155SRico Sonntag            'records' => $records,
1338c0112ce8SGreg Roach        ]);
13398add1155SRico Sonntag    }
13408add1155SRico Sonntag
13418add1155SRico Sonntag    /**
13428add1155SRico Sonntag     * Find the age between wife and husband..
13438add1155SRico Sonntag     *
13448add1155SRico Sonntag     * @param int $total
13458add1155SRico Sonntag     *
13468add1155SRico Sonntag     * @return string
13478add1155SRico Sonntag     */
13488add1155SRico Sonntag    public function ageBetweenSpousesFM(int $total = 10): string
13498add1155SRico Sonntag    {
13508add1155SRico Sonntag        $records = $this->ageBetweenSpousesQuery('ASC', $total);
13518add1155SRico Sonntag
1352c0112ce8SGreg Roach        return view('statistics/families/top10-nolist-spouses', [
13538add1155SRico Sonntag            'records' => $records,
1354c0112ce8SGreg Roach        ]);
13558add1155SRico Sonntag    }
13568add1155SRico Sonntag
13578add1155SRico Sonntag    /**
13588add1155SRico Sonntag     * Find the age between wife and husband..
13598add1155SRico Sonntag     *
13608add1155SRico Sonntag     * @param int $total
13618add1155SRico Sonntag     *
13628add1155SRico Sonntag     * @return string
13638add1155SRico Sonntag     */
13648add1155SRico Sonntag    public function ageBetweenSpousesFMList(int $total = 10): string
13658add1155SRico Sonntag    {
13668add1155SRico Sonntag        $records = $this->ageBetweenSpousesQuery('ASC', $total);
13678add1155SRico Sonntag
1368c0112ce8SGreg Roach        return view('statistics/families/top10-list-spouses', [
13698add1155SRico Sonntag            'records' => $records,
1370c0112ce8SGreg Roach        ]);
13718add1155SRico Sonntag    }
13728add1155SRico Sonntag
13738add1155SRico Sonntag    /**
13748add1155SRico Sonntag     * General query on ages at marriage.
13758add1155SRico Sonntag     *
1376afa8d404SGreg Roach     * @param string $sex   "M" or "F"
13778add1155SRico Sonntag     * @param int    $year1
13788add1155SRico Sonntag     * @param int    $year2
13798add1155SRico Sonntag     *
13808add1155SRico Sonntag     * @return array
13818add1155SRico Sonntag     */
1382afa8d404SGreg Roach    public function statsMarrAgeQuery($sex, $year1 = -1, $year2 = -1): array
13838add1155SRico Sonntag    {
1384afa8d404SGreg Roach        $prefix = DB::connection()->getTablePrefix();
1385afa8d404SGreg Roach
1386afa8d404SGreg Roach        $query = DB::table('dates AS married')
13870b5fd0a6SGreg Roach            ->join('families', static function (JoinClause $join): void {
1388afa8d404SGreg Roach                $join
1389afa8d404SGreg Roach                    ->on('f_file', '=', 'married.d_file')
1390afa8d404SGreg Roach                    ->on('f_id', '=', 'married.d_gid');
1391afa8d404SGreg Roach            })
13920b5fd0a6SGreg Roach            ->join('dates AS birth', static function (JoinClause $join) use ($sex): void {
1393afa8d404SGreg Roach                $join
1394afa8d404SGreg Roach                    ->on('birth.d_file', '=', 'married.d_file')
1395afa8d404SGreg Roach                    ->on('birth.d_gid', '=', $sex === 'M' ? 'f_husb' : 'f_wife')
1396afa8d404SGreg Roach                    ->where('birth.d_julianday1', '<>', 0)
1397afa8d404SGreg Roach                    ->where('birth.d_fact', '=', 'BIRT')
1398afa8d404SGreg Roach                    ->whereIn('birth.d_type', ['@#DGREGORIAN@', '@#DJULIAN@']);
1399afa8d404SGreg Roach            })
1400afa8d404SGreg Roach            ->where('married.d_file', '=', $this->tree->id())
1401afa8d404SGreg Roach            ->where('married.d_fact', '=', 'MARR')
1402afa8d404SGreg Roach            ->whereIn('married.d_type', ['@#DGREGORIAN@', '@#DJULIAN@'])
1403afa8d404SGreg Roach            ->whereColumn('married.d_julianday1', '>', 'birth.d_julianday1')
1404a69f5655SGreg Roach            ->select(['f_id', 'birth.d_gid', new Expression($prefix . 'married.d_julianday2 - ' . $prefix . 'birth.d_julianday1 AS age')]);
1405afa8d404SGreg Roach
14068add1155SRico Sonntag        if ($year1 >= 0 && $year2 >= 0) {
1407afa8d404SGreg Roach            $query->whereBetween('married.d_year', [$year1, $year2]);
14088add1155SRico Sonntag        }
14098add1155SRico Sonntag
1410afa8d404SGreg Roach        return $query
1411afa8d404SGreg Roach            ->get()
14120b5fd0a6SGreg Roach            ->map(static function (stdClass $row): stdClass {
14138add1155SRico Sonntag                $row->age = (int) $row->age;
14148add1155SRico Sonntag
1415afa8d404SGreg Roach                return $row;
1416afa8d404SGreg Roach            })
1417afa8d404SGreg Roach            ->all();
14188add1155SRico Sonntag    }
14198add1155SRico Sonntag
14208add1155SRico Sonntag    /**
14218add1155SRico Sonntag     * General query on marriage ages.
14228add1155SRico Sonntag     *
14238add1155SRico Sonntag     * @return string
14248add1155SRico Sonntag     */
142588de55fdSRico Sonntag    public function statsMarrAge(): string
14268add1155SRico Sonntag    {
14278add1155SRico Sonntag        return (new ChartMarriageAge($this->tree))
142888de55fdSRico Sonntag            ->chartMarriageAge();
14298add1155SRico Sonntag    }
14308add1155SRico Sonntag
14318add1155SRico Sonntag    /**
14328add1155SRico Sonntag     * Query the database for marriage tags.
14338add1155SRico Sonntag     *
14342c928961SGreg Roach     * @param string $type       "full", "name" or "age"
14352c928961SGreg Roach     * @param string $age_dir    "ASC" or "DESC"
14362c928961SGreg Roach     * @param string $sex        "F" or "M"
14378add1155SRico Sonntag     * @param bool   $show_years
14388add1155SRico Sonntag     *
14398add1155SRico Sonntag     * @return string
14408add1155SRico Sonntag     */
14418add1155SRico Sonntag    private function marriageQuery(string $type, string $age_dir, string $sex, bool $show_years): string
14428add1155SRico Sonntag    {
14438add1155SRico Sonntag        if ($sex === 'F') {
14448add1155SRico Sonntag            $sex_field = 'f_wife';
14458add1155SRico Sonntag        } else {
14468add1155SRico Sonntag            $sex_field = 'f_husb';
14478add1155SRico Sonntag        }
14488add1155SRico Sonntag
14498add1155SRico Sonntag        if ($age_dir !== 'ASC') {
14508add1155SRico Sonntag            $age_dir = 'DESC';
14518add1155SRico Sonntag        }
14528add1155SRico Sonntag
14532c928961SGreg Roach        $prefix = DB::connection()->getTablePrefix();
14548add1155SRico Sonntag
14552c928961SGreg Roach        $row = DB::table('families')
14560b5fd0a6SGreg Roach            ->join('dates AS married', static function (JoinClause $join): void {
14572c928961SGreg Roach                $join
14582c928961SGreg Roach                    ->on('married.d_file', '=', 'f_file')
14592c928961SGreg Roach                    ->on('married.d_gid', '=', 'f_id')
14602c928961SGreg Roach                    ->where('married.d_fact', '=', 'MARR');
14612c928961SGreg Roach            })
14620b5fd0a6SGreg Roach            ->join('individuals', static function (JoinClause $join) use ($sex, $sex_field): void {
14632c928961SGreg Roach                $join
14642c928961SGreg Roach                    ->on('i_file', '=', 'f_file')
14652c928961SGreg Roach                    ->on('i_id', '=', $sex_field)
14662c928961SGreg Roach                    ->where('i_sex', '=', $sex);
14672c928961SGreg Roach            })
14680b5fd0a6SGreg Roach            ->join('dates AS birth', static function (JoinClause $join): void {
14692c928961SGreg Roach                $join
14702c928961SGreg Roach                    ->on('birth.d_file', '=', 'i_file')
14712c928961SGreg Roach                    ->on('birth.d_gid', '=', 'i_id')
14722c928961SGreg Roach                    ->where('birth.d_fact', '=', 'BIRT')
14732c928961SGreg Roach                    ->where('birth.d_julianday1', '<>', 0);
14742c928961SGreg Roach            })
14752c928961SGreg Roach            ->where('f_file', '=', $this->tree->id())
14762c928961SGreg Roach            ->where('married.d_julianday2', '>', 'birth.d_julianday1')
1477a69f5655SGreg Roach            ->orderBy(new Expression($prefix . 'married.d_julianday2 - ' . $prefix . 'birth.d_julianday1'), $age_dir)
1478a69f5655SGreg Roach            ->select(['f_id AS famid', $sex_field, new Expression($prefix . 'married.d_julianday2 - ' . $prefix . 'birth.d_julianday1 AS age'), 'i_id'])
14792c928961SGreg Roach            ->take(1)
14802c928961SGreg Roach            ->get()
14812c928961SGreg Roach            ->first();
14822c928961SGreg Roach
14832c928961SGreg Roach        if ($row === null) {
14848add1155SRico Sonntag            return '';
14858add1155SRico Sonntag        }
14868add1155SRico Sonntag
14878add1155SRico Sonntag        $family = Family::getInstance($row->famid, $this->tree);
14888add1155SRico Sonntag        $person = Individual::getInstance($row->i_id, $this->tree);
14898add1155SRico Sonntag
14908add1155SRico Sonntag        switch ($type) {
14918add1155SRico Sonntag            default:
14928add1155SRico Sonntag            case 'full':
14938add1155SRico Sonntag                if ($family && $family->canShow()) {
14948add1155SRico Sonntag                    $result = $family->formatList();
14958add1155SRico Sonntag                } else {
14968add1155SRico Sonntag                    $result = I18N::translate('This information is private and cannot be shown.');
14978add1155SRico Sonntag                }
14988add1155SRico Sonntag                break;
14998add1155SRico Sonntag
15008add1155SRico Sonntag            case 'name':
150139ca88baSGreg Roach                $result = '<a href="' . e($family->url()) . '">' . $person->fullName() . '</a>';
15028add1155SRico Sonntag                break;
15038add1155SRico Sonntag
15048add1155SRico Sonntag            case 'age':
15058add1155SRico Sonntag                $age = $row->age;
15068add1155SRico Sonntag
15078add1155SRico Sonntag                if ($show_years) {
15088add1155SRico Sonntag                    $result = $this->calculateAge((int) $row->age);
15098add1155SRico Sonntag                } else {
15108add1155SRico Sonntag                    $result = I18N::number((int) ($age / 365.25));
15118add1155SRico Sonntag                }
15128add1155SRico Sonntag
15138add1155SRico Sonntag                break;
15148add1155SRico Sonntag        }
15158add1155SRico Sonntag
15168add1155SRico Sonntag        return $result;
15178add1155SRico Sonntag    }
15188add1155SRico Sonntag
15198add1155SRico Sonntag    /**
15208add1155SRico Sonntag     * Find the youngest wife.
15218add1155SRico Sonntag     *
15228add1155SRico Sonntag     * @return string
15238add1155SRico Sonntag     */
15248add1155SRico Sonntag    public function youngestMarriageFemale(): string
15258add1155SRico Sonntag    {
15268add1155SRico Sonntag        return $this->marriageQuery('full', 'ASC', 'F', false);
15278add1155SRico Sonntag    }
15288add1155SRico Sonntag
15298add1155SRico Sonntag    /**
15308add1155SRico Sonntag     * Find the name of the youngest wife.
15318add1155SRico Sonntag     *
15328add1155SRico Sonntag     * @return string
15338add1155SRico Sonntag     */
15348add1155SRico Sonntag    public function youngestMarriageFemaleName(): string
15358add1155SRico Sonntag    {
15368add1155SRico Sonntag        return $this->marriageQuery('name', 'ASC', 'F', false);
15378add1155SRico Sonntag    }
15388add1155SRico Sonntag
15398add1155SRico Sonntag    /**
15408add1155SRico Sonntag     * Find the age of the youngest wife.
15418add1155SRico Sonntag     *
15428add1155SRico Sonntag     * @param string $show_years
15438add1155SRico Sonntag     *
15448add1155SRico Sonntag     * @return string
15458add1155SRico Sonntag     */
15468add1155SRico Sonntag    public function youngestMarriageFemaleAge(string $show_years = ''): string
15478add1155SRico Sonntag    {
15488add1155SRico Sonntag        return $this->marriageQuery('age', 'ASC', 'F', (bool) $show_years);
15498add1155SRico Sonntag    }
15508add1155SRico Sonntag
15518add1155SRico Sonntag    /**
15528add1155SRico Sonntag     * Find the oldest wife.
15538add1155SRico Sonntag     *
15548add1155SRico Sonntag     * @return string
15558add1155SRico Sonntag     */
15568add1155SRico Sonntag    public function oldestMarriageFemale(): string
15578add1155SRico Sonntag    {
15588add1155SRico Sonntag        return $this->marriageQuery('full', 'DESC', 'F', false);
15598add1155SRico Sonntag    }
15608add1155SRico Sonntag
15618add1155SRico Sonntag    /**
15628add1155SRico Sonntag     * Find the name of the oldest wife.
15638add1155SRico Sonntag     *
15648add1155SRico Sonntag     * @return string
15658add1155SRico Sonntag     */
15668add1155SRico Sonntag    public function oldestMarriageFemaleName(): string
15678add1155SRico Sonntag    {
15688add1155SRico Sonntag        return $this->marriageQuery('name', 'DESC', 'F', false);
15698add1155SRico Sonntag    }
15708add1155SRico Sonntag
15718add1155SRico Sonntag    /**
15728add1155SRico Sonntag     * Find the age of the oldest wife.
15738add1155SRico Sonntag     *
15748add1155SRico Sonntag     * @param string $show_years
15758add1155SRico Sonntag     *
15768add1155SRico Sonntag     * @return string
15778add1155SRico Sonntag     */
15788add1155SRico Sonntag    public function oldestMarriageFemaleAge(string $show_years = ''): string
15798add1155SRico Sonntag    {
15808add1155SRico Sonntag        return $this->marriageQuery('age', 'DESC', 'F', (bool) $show_years);
15818add1155SRico Sonntag    }
15828add1155SRico Sonntag
15838add1155SRico Sonntag    /**
15848add1155SRico Sonntag     * Find the youngest husband.
15858add1155SRico Sonntag     *
15868add1155SRico Sonntag     * @return string
15878add1155SRico Sonntag     */
15888add1155SRico Sonntag    public function youngestMarriageMale(): string
15898add1155SRico Sonntag    {
15908add1155SRico Sonntag        return $this->marriageQuery('full', 'ASC', 'M', false);
15918add1155SRico Sonntag    }
15928add1155SRico Sonntag
15938add1155SRico Sonntag    /**
15948add1155SRico Sonntag     * Find the name of the youngest husband.
15958add1155SRico Sonntag     *
15968add1155SRico Sonntag     * @return string
15978add1155SRico Sonntag     */
15988add1155SRico Sonntag    public function youngestMarriageMaleName(): string
15998add1155SRico Sonntag    {
16008add1155SRico Sonntag        return $this->marriageQuery('name', 'ASC', 'M', false);
16018add1155SRico Sonntag    }
16028add1155SRico Sonntag
16038add1155SRico Sonntag    /**
16048add1155SRico Sonntag     * Find the age of the youngest husband.
16058add1155SRico Sonntag     *
16068add1155SRico Sonntag     * @param string $show_years
16078add1155SRico Sonntag     *
16088add1155SRico Sonntag     * @return string
16098add1155SRico Sonntag     */
16108add1155SRico Sonntag    public function youngestMarriageMaleAge(string $show_years = ''): string
16118add1155SRico Sonntag    {
16128add1155SRico Sonntag        return $this->marriageQuery('age', 'ASC', 'M', (bool) $show_years);
16138add1155SRico Sonntag    }
16148add1155SRico Sonntag
16158add1155SRico Sonntag    /**
16168add1155SRico Sonntag     * Find the oldest husband.
16178add1155SRico Sonntag     *
16188add1155SRico Sonntag     * @return string
16198add1155SRico Sonntag     */
16208add1155SRico Sonntag    public function oldestMarriageMale(): string
16218add1155SRico Sonntag    {
16228add1155SRico Sonntag        return $this->marriageQuery('full', 'DESC', 'M', false);
16238add1155SRico Sonntag    }
16248add1155SRico Sonntag
16258add1155SRico Sonntag    /**
16268add1155SRico Sonntag     * Find the name of the oldest husband.
16278add1155SRico Sonntag     *
16288add1155SRico Sonntag     * @return string
16298add1155SRico Sonntag     */
16308add1155SRico Sonntag    public function oldestMarriageMaleName(): string
16318add1155SRico Sonntag    {
16328add1155SRico Sonntag        return $this->marriageQuery('name', 'DESC', 'M', false);
16338add1155SRico Sonntag    }
16348add1155SRico Sonntag
16358add1155SRico Sonntag    /**
16368add1155SRico Sonntag     * Find the age of the oldest husband.
16378add1155SRico Sonntag     *
16388add1155SRico Sonntag     * @param string $show_years
16398add1155SRico Sonntag     *
16408add1155SRico Sonntag     * @return string
16418add1155SRico Sonntag     */
16428add1155SRico Sonntag    public function oldestMarriageMaleAge(string $show_years = ''): string
16438add1155SRico Sonntag    {
16448add1155SRico Sonntag        return $this->marriageQuery('age', 'DESC', 'M', (bool) $show_years);
16458add1155SRico Sonntag    }
16468add1155SRico Sonntag
16478add1155SRico Sonntag    /**
16488add1155SRico Sonntag     * General query on marriages.
16498add1155SRico Sonntag     *
16508add1155SRico Sonntag     * @param int  $year1
16518add1155SRico Sonntag     * @param int  $year2
16528add1155SRico Sonntag     *
1653e6f3d5e2SGreg Roach     * @return Builder
16548add1155SRico Sonntag     */
1655e6f3d5e2SGreg Roach    public function statsMarriageQuery(int $year1 = -1, int $year2 = -1): Builder
16568add1155SRico Sonntag    {
1657e6f3d5e2SGreg Roach        $query = DB::table('dates')
1658e6f3d5e2SGreg Roach            ->where('d_file', '=', $this->tree->id())
1659e6f3d5e2SGreg Roach            ->where('d_fact', '=', 'MARR')
1660a69f5655SGreg Roach            ->select(['d_month', new Expression('COUNT(*) AS total')])
1661*7f5c2944SGreg Roach            ->groupBy(['d_month']);
1662e6f3d5e2SGreg Roach
1663e6f3d5e2SGreg Roach        if ($year1 >= 0 && $year2 >= 0) {
1664e6f3d5e2SGreg Roach            $query->whereBetween('d_year', [$year1, $year2]);
1665e6f3d5e2SGreg Roach        }
1666e6f3d5e2SGreg Roach
1667e6f3d5e2SGreg Roach        return $query;
1668e6f3d5e2SGreg Roach    }
1669e6f3d5e2SGreg Roach
1670e6f3d5e2SGreg Roach    /**
1671e6f3d5e2SGreg Roach     * General query on marriages.
1672e6f3d5e2SGreg Roach     *
1673e6f3d5e2SGreg Roach     * @param int  $year1
1674e6f3d5e2SGreg Roach     * @param int  $year2
1675e6f3d5e2SGreg Roach     *
1676e6f3d5e2SGreg Roach     * @return Builder
1677e6f3d5e2SGreg Roach     */
1678e6f3d5e2SGreg Roach    public function statsFirstMarriageQuery(int $year1 = -1, int $year2 = -1): Builder
1679e6f3d5e2SGreg Roach    {
16802d7289dcSGreg Roach        $query = DB::table('families')
16810b5fd0a6SGreg Roach            ->join('dates', static function (JoinClause $join): void {
16822d7289dcSGreg Roach                $join
16832d7289dcSGreg Roach                    ->on('d_gid', '=', 'f_id')
16842d7289dcSGreg Roach                    ->on('d_file', '=', 'f_file')
16852d7289dcSGreg Roach                    ->where('d_fact', '=', 'MARR')
168618e9654eSGreg Roach                    ->whereIn('d_month', ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'])
16872d7289dcSGreg Roach                    ->where('d_julianday2', '<>', 0);
16880b5fd0a6SGreg Roach            })->join('individuals', static function (JoinClause $join): void {
16892d7289dcSGreg Roach                $join
16902d7289dcSGreg Roach                    ->on('i_file', '=', 'f_file');
16912d7289dcSGreg Roach            })
16922d7289dcSGreg Roach            ->where('f_file', '=', $this->tree->id())
16930b5fd0a6SGreg Roach            ->where(static function (Builder $query): void {
16942d7289dcSGreg Roach                $query
16952d7289dcSGreg Roach                    ->whereColumn('i_id', '=', 'f_husb')
16962d7289dcSGreg Roach                    ->orWhereColumn('i_id', '=', 'f_wife');
16972d7289dcSGreg Roach            });
16988add1155SRico Sonntag
16998add1155SRico Sonntag        if ($year1 >= 0 && $year2 >= 0) {
17002d7289dcSGreg Roach            $query->whereBetween('d_year', [$year1, $year2]);
17018add1155SRico Sonntag        }
17028add1155SRico Sonntag
17032d7289dcSGreg Roach        return $query
17048305ac84SGreg Roach            ->select(['f_id AS fams', 'f_husb', 'f_wife', 'd_julianday2 AS age', 'd_month AS month', 'i_id AS indi'])
17058305ac84SGreg Roach            ->orderBy('f_id')
17068305ac84SGreg Roach            ->orderBy('i_id')
17078305ac84SGreg Roach            ->orderBy('d_julianday2');
17088add1155SRico Sonntag    }
17098add1155SRico Sonntag
17108add1155SRico Sonntag    /**
17118add1155SRico Sonntag     * General query on marriages.
17128add1155SRico Sonntag     *
17138add1155SRico Sonntag     * @param string|null $color_from
17148add1155SRico Sonntag     * @param string|null $color_to
17158add1155SRico Sonntag     *
17168add1155SRico Sonntag     * @return string
17178add1155SRico Sonntag     */
171888de55fdSRico Sonntag    public function statsMarr(string $color_from = null, string $color_to = null): string
17198add1155SRico Sonntag    {
17208add1155SRico Sonntag        return (new ChartMarriage($this->tree))
172188de55fdSRico Sonntag            ->chartMarriage($color_from, $color_to);
17228add1155SRico Sonntag    }
17238add1155SRico Sonntag
17248add1155SRico Sonntag    /**
17258add1155SRico Sonntag     * General divorce query.
17268add1155SRico Sonntag     *
17278add1155SRico Sonntag     * @param string|null $color_from
17288add1155SRico Sonntag     * @param string|null $color_to
17298add1155SRico Sonntag     *
17308add1155SRico Sonntag     * @return string
17318add1155SRico Sonntag     */
173288de55fdSRico Sonntag    public function statsDiv(string $color_from = null, string $color_to = null): string
17338add1155SRico Sonntag    {
17348add1155SRico Sonntag        return (new ChartDivorce($this->tree))
173588de55fdSRico Sonntag            ->chartDivorce($color_from, $color_to);
17368add1155SRico Sonntag    }
17378add1155SRico Sonntag}
1738