xref: /webtrees/app/Statistics/Repository/FamilyRepository.php (revision 52f124b09c3bc79f2e314c64c3ad971d9a17c818)
18add1155SRico Sonntag<?php
23976b470SGreg Roach
38add1155SRico Sonntag/**
48add1155SRico Sonntag * webtrees: online genealogy
5d11be702SGreg Roach * Copyright (C) 2023 webtrees development team
68add1155SRico Sonntag * This program is free software: you can redistribute it and/or modify
78add1155SRico Sonntag * it under the terms of the GNU General Public License as published by
88add1155SRico Sonntag * the Free Software Foundation, either version 3 of the License, or
98add1155SRico Sonntag * (at your option) any later version.
108add1155SRico Sonntag * This program is distributed in the hope that it will be useful,
118add1155SRico Sonntag * but WITHOUT ANY WARRANTY; without even the implied warranty of
128add1155SRico Sonntag * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
138add1155SRico Sonntag * GNU General Public License for more details.
148add1155SRico Sonntag * You should have received a copy of the GNU General Public License
1589f7189bSGreg Roach * along with this program. If not, see <https://www.gnu.org/licenses/>.
168add1155SRico Sonntag */
17fcfa147eSGreg Roach
188add1155SRico Sonntagdeclare(strict_types=1);
198add1155SRico Sonntag
208add1155SRico Sonntagnamespace Fisharebest\Webtrees\Statistics\Repository;
218add1155SRico Sonntag
226ccdf4f0SGreg Roachuse Exception;
238add1155SRico Sonntaguse Fisharebest\Webtrees\Family;
24d1a467e4SGreg Roachuse Fisharebest\Webtrees\GedcomRecord;
258add1155SRico Sonntaguse Fisharebest\Webtrees\I18N;
26f78da678SGreg Roachuse Fisharebest\Webtrees\Individual;
27f78da678SGreg Roachuse Fisharebest\Webtrees\Registry;
288add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartChildren;
298add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartDivorce;
308add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartFamilyLargest;
318add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartMarriage;
328add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartMarriageAge;
338add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartNoChildrenFamilies;
34f78da678SGreg Roachuse Fisharebest\Webtrees\Statistics\Service\CenturyService;
35f78da678SGreg Roachuse Fisharebest\Webtrees\Statistics\Service\ColorService;
368add1155SRico Sonntaguse Fisharebest\Webtrees\Tree;
3744cdc21eSGreg Roachuse Illuminate\Database\Capsule\Manager as DB;
382d7289dcSGreg Roachuse Illuminate\Database\Query\Builder;
39a69f5655SGreg Roachuse Illuminate\Database\Query\Expression;
40d1a467e4SGreg Roachuse Illuminate\Database\Query\JoinClause;
4176d39c55SGreg Roachuse stdClass;
423976b470SGreg Roach
434c78e066SGreg Roachuse function arsort;
444c78e066SGreg Roachuse function asort;
454c78e066SGreg Roachuse function e;
464c78e066SGreg Roachuse function floor;
474c78e066SGreg Roachuse function implode;
486ccdf4f0SGreg Roachuse function in_array;
494c78e066SGreg Roachuse function str_replace;
504c78e066SGreg Roachuse function view;
513976b470SGreg Roach
528add1155SRico Sonntag/**
534c78e066SGreg Roach * A repository providing methods for family related statistics.
548add1155SRico Sonntag */
558add1155SRico Sonntagclass FamilyRepository
568add1155SRico Sonntag{
57f78da678SGreg Roach    private CenturyService $century_service;
58f78da678SGreg Roach
59f78da678SGreg Roach    private ColorService $color_service;
60f78da678SGreg Roach
614c78e066SGreg Roach    private Tree $tree;
628add1155SRico Sonntag
638add1155SRico Sonntag    /**
64f78da678SGreg Roach     * @param CenturyService $century_service
65f78da678SGreg Roach     * @param ColorService   $color_service
668add1155SRico Sonntag     * @param Tree           $tree
678add1155SRico Sonntag     */
68f78da678SGreg Roach    public function __construct(CenturyService $century_service, ColorService $color_service, Tree $tree)
698add1155SRico Sonntag    {
70f78da678SGreg Roach        $this->century_service = $century_service;
71f78da678SGreg Roach        $this->color_service   = $color_service;
728add1155SRico Sonntag        $this->tree            = $tree;
738add1155SRico Sonntag    }
748add1155SRico Sonntag
758add1155SRico Sonntag    /**
768add1155SRico Sonntag     * General query on family.
778add1155SRico Sonntag     *
788add1155SRico Sonntag     * @param string $type
798add1155SRico Sonntag     *
808add1155SRico Sonntag     * @return string
818add1155SRico Sonntag     */
828add1155SRico Sonntag    private function familyQuery(string $type): string
838add1155SRico Sonntag    {
8444cdc21eSGreg Roach        $row = DB::table('families')
8544cdc21eSGreg Roach            ->where('f_file', '=', $this->tree->id())
8644cdc21eSGreg Roach            ->orderBy('f_numchil', 'desc')
8744cdc21eSGreg Roach            ->first();
888add1155SRico Sonntag
8944cdc21eSGreg Roach        if ($row === null) {
908add1155SRico Sonntag            return '';
918add1155SRico Sonntag        }
928add1155SRico Sonntag
9344cdc21eSGreg Roach        /** @var Family $family */
946b9cb339SGreg Roach        $family = Registry::familyFactory()->mapper($this->tree)($row);
958add1155SRico Sonntag
9644cdc21eSGreg Roach        if (!$family->canShow()) {
9744cdc21eSGreg Roach            return I18N::translate('This information is private and cannot be shown.');
988add1155SRico Sonntag        }
998add1155SRico Sonntag
1008add1155SRico Sonntag        switch ($type) {
1018add1155SRico Sonntag            default:
1028add1155SRico Sonntag            case 'full':
10344cdc21eSGreg Roach                return $family->formatList();
1048add1155SRico Sonntag
10544cdc21eSGreg Roach            case 'size':
10644cdc21eSGreg Roach                return I18N::number((int) $row->f_numchil);
10744cdc21eSGreg Roach
10844cdc21eSGreg Roach            case 'name':
10939ca88baSGreg Roach                return '<a href="' . e($family->url()) . '">' . $family->fullName() . '</a>';
11044cdc21eSGreg Roach        }
1118add1155SRico Sonntag    }
1128add1155SRico Sonntag
1138add1155SRico Sonntag    /**
1148add1155SRico Sonntag     * Find the family with the most children.
1158add1155SRico Sonntag     *
1168add1155SRico Sonntag     * @return string
1178add1155SRico Sonntag     */
1188add1155SRico Sonntag    public function largestFamily(): string
1198add1155SRico Sonntag    {
1208add1155SRico Sonntag        return $this->familyQuery('full');
1218add1155SRico Sonntag    }
1228add1155SRico Sonntag
1238add1155SRico Sonntag    /**
1248add1155SRico Sonntag     * Find the number of children in the largest family.
1258add1155SRico Sonntag     *
1268add1155SRico Sonntag     * @return string
1278add1155SRico Sonntag     */
1288add1155SRico Sonntag    public function largestFamilySize(): string
1298add1155SRico Sonntag    {
1308add1155SRico Sonntag        return $this->familyQuery('size');
1318add1155SRico Sonntag    }
1328add1155SRico Sonntag
1338add1155SRico Sonntag    /**
1348add1155SRico Sonntag     * Find the family with the most children.
1358add1155SRico Sonntag     *
1368add1155SRico Sonntag     * @return string
1378add1155SRico Sonntag     */
1388add1155SRico Sonntag    public function largestFamilyName(): string
1398add1155SRico Sonntag    {
1408add1155SRico Sonntag        return $this->familyQuery('name');
1418add1155SRico Sonntag    }
1428add1155SRico Sonntag
1438add1155SRico Sonntag    /**
1448add1155SRico Sonntag     * Find the couple with the most grandchildren.
1458add1155SRico Sonntag     *
1468add1155SRico Sonntag     * @param int $total
1478add1155SRico Sonntag     *
14876d39c55SGreg Roach     * @return array<array<string,int|Family>>
1498add1155SRico Sonntag     */
1508add1155SRico Sonntag    private function topTenGrandFamilyQuery(int $total): array
1518add1155SRico Sonntag    {
1522d7289dcSGreg Roach        return DB::table('families')
1530b5fd0a6SGreg Roach            ->join('link AS children', static function (JoinClause $join): void {
1542d7289dcSGreg Roach                $join
1552d7289dcSGreg Roach                    ->on('children.l_from', '=', 'f_id')
1562d7289dcSGreg Roach                    ->on('children.l_file', '=', 'f_file')
1572d7289dcSGreg Roach                    ->where('children.l_type', '=', 'CHIL');
1580b5fd0a6SGreg Roach            })->join('link AS mchildren', static function (JoinClause $join): void {
1592d7289dcSGreg Roach                $join
1602d7289dcSGreg Roach                    ->on('mchildren.l_file', '=', 'children.l_file')
1612d7289dcSGreg Roach                    ->on('mchildren.l_from', '=', 'children.l_to')
1622d7289dcSGreg Roach                    ->where('mchildren.l_type', '=', 'FAMS');
1630b5fd0a6SGreg Roach            })->join('link AS gchildren', static function (JoinClause $join): void {
1642d7289dcSGreg Roach                $join
1652d7289dcSGreg Roach                    ->on('gchildren.l_file', '=', 'mchildren.l_file')
1662d7289dcSGreg Roach                    ->on('gchildren.l_from', '=', 'mchildren.l_to')
1672d7289dcSGreg Roach                    ->where('gchildren.l_type', '=', 'CHIL');
1682d7289dcSGreg Roach            })
1692d7289dcSGreg Roach            ->where('f_file', '=', $this->tree->id())
1702d7289dcSGreg Roach            ->groupBy(['f_id', 'f_file'])
171a69f5655SGreg Roach            ->orderBy(new Expression('COUNT(*)'), 'DESC')
17247256fc5SGreg Roach            ->select(['families.*'])
1732d7289dcSGreg Roach            ->limit($total)
1742d7289dcSGreg Roach            ->get()
1756b9cb339SGreg Roach            ->map(Registry::familyFactory()->mapper($this->tree))
1762d7289dcSGreg Roach            ->filter(GedcomRecord::accessFilter())
1770b5fd0a6SGreg Roach            ->map(static function (Family $family): array {
1782d7289dcSGreg Roach                $count = 0;
1792d7289dcSGreg Roach                foreach ($family->children() as $child) {
1802d7289dcSGreg Roach                    foreach ($child->spouseFamilies() as $spouse_family) {
1812d7289dcSGreg Roach                        $count += $spouse_family->children()->count();
1822d7289dcSGreg Roach                    }
1838add1155SRico Sonntag                }
1848add1155SRico Sonntag
1852d7289dcSGreg Roach                return [
1868add1155SRico Sonntag                    'family' => $family,
1872d7289dcSGreg Roach                    'count'  => $count,
1888add1155SRico Sonntag                ];
1892d7289dcSGreg Roach            })
1902d7289dcSGreg Roach            ->all();
1918add1155SRico Sonntag    }
1928add1155SRico Sonntag
1938add1155SRico Sonntag    /**
1948add1155SRico Sonntag     * Find the couple with the most grandchildren.
1958add1155SRico Sonntag     *
1968add1155SRico Sonntag     * @param int $total
1978add1155SRico Sonntag     *
1988add1155SRico Sonntag     * @return string
1998add1155SRico Sonntag     */
2008add1155SRico Sonntag    public function topTenLargestGrandFamily(int $total = 10): string
2018add1155SRico Sonntag    {
2022d7289dcSGreg Roach        return view('statistics/families/top10-nolist-grand', [
2032d7289dcSGreg Roach            'records' => $this->topTenGrandFamilyQuery($total),
2042d7289dcSGreg Roach        ]);
2058add1155SRico Sonntag    }
2068add1155SRico Sonntag
2078add1155SRico Sonntag    /**
2088add1155SRico Sonntag     * Find the couple with the most grandchildren.
2098add1155SRico Sonntag     *
2108add1155SRico Sonntag     * @param int $total
2118add1155SRico Sonntag     *
2128add1155SRico Sonntag     * @return string
2138add1155SRico Sonntag     */
2148add1155SRico Sonntag    public function topTenLargestGrandFamilyList(int $total = 10): string
2158add1155SRico Sonntag    {
2162d7289dcSGreg Roach        return view('statistics/families/top10-list-grand', [
2172d7289dcSGreg Roach            'records' => $this->topTenGrandFamilyQuery($total),
2182d7289dcSGreg Roach        ]);
2198add1155SRico Sonntag    }
2208add1155SRico Sonntag
2218add1155SRico Sonntag    /**
2228add1155SRico Sonntag     * Find the families with no children.
2238add1155SRico Sonntag     *
2248add1155SRico Sonntag     * @return int
2258add1155SRico Sonntag     */
2268add1155SRico Sonntag    private function noChildrenFamiliesQuery(): int
2278add1155SRico Sonntag    {
2282d7289dcSGreg Roach        return DB::table('families')
2292d7289dcSGreg Roach            ->where('f_file', '=', $this->tree->id())
2302d7289dcSGreg Roach            ->where('f_numchil', '=', 0)
2312d7289dcSGreg Roach            ->count();
2328add1155SRico Sonntag    }
2338add1155SRico Sonntag
2348add1155SRico Sonntag    /**
2358add1155SRico Sonntag     * Find the families with no children.
2368add1155SRico Sonntag     *
2378add1155SRico Sonntag     * @return string
2388add1155SRico Sonntag     */
2398add1155SRico Sonntag    public function noChildrenFamilies(): string
2408add1155SRico Sonntag    {
2418add1155SRico Sonntag        return I18N::number($this->noChildrenFamiliesQuery());
2428add1155SRico Sonntag    }
2438add1155SRico Sonntag
2448add1155SRico Sonntag    /**
2458add1155SRico Sonntag     * Find the families with no children.
2468add1155SRico Sonntag     *
2478add1155SRico Sonntag     * @param string $type
2488add1155SRico Sonntag     *
2498add1155SRico Sonntag     * @return string
2508add1155SRico Sonntag     */
25173d58381SGreg Roach    public function noChildrenFamiliesList(string $type = 'list'): string
2528add1155SRico Sonntag    {
2532c928961SGreg Roach        $families = DB::table('families')
2542c928961SGreg Roach            ->where('f_file', '=', $this->tree->id())
2552c928961SGreg Roach            ->where('f_numchil', '=', 0)
2562c928961SGreg Roach            ->get()
2576b9cb339SGreg Roach            ->map(Registry::familyFactory()->mapper($this->tree))
2582c928961SGreg Roach            ->filter(GedcomRecord::accessFilter());
2598add1155SRico Sonntag
2608add1155SRico Sonntag        $top10 = [];
2612c928961SGreg Roach
2623413ec75SRico Sonntag        /** @var Family $family */
2632c928961SGreg Roach        foreach ($families as $family) {
2648add1155SRico Sonntag            if ($type === 'list') {
26539ca88baSGreg Roach                $top10[] = '<li><a href="' . e($family->url()) . '">' . $family->fullName() . '</a></li>';
2668add1155SRico Sonntag            } else {
26739ca88baSGreg Roach                $top10[] = '<a href="' . e($family->url()) . '">' . $family->fullName() . '</a>';
2688add1155SRico Sonntag            }
2698add1155SRico Sonntag        }
2708add1155SRico Sonntag
2718add1155SRico Sonntag        if ($type === 'list') {
2728add1155SRico Sonntag            $top10 = implode('', $top10);
2738add1155SRico Sonntag        } else {
2748add1155SRico Sonntag            $top10 = implode('; ', $top10);
2758add1155SRico Sonntag        }
2768add1155SRico Sonntag
2772c928961SGreg Roach
2788add1155SRico Sonntag        if ($type === 'list') {
2798add1155SRico Sonntag            return '<ul>' . $top10 . '</ul>';
2808add1155SRico Sonntag        }
2818add1155SRico Sonntag
2828add1155SRico Sonntag        return $top10;
2838add1155SRico Sonntag    }
2848add1155SRico Sonntag
2858add1155SRico Sonntag    /**
2868add1155SRico Sonntag     * Create a chart of children with no families.
2878add1155SRico Sonntag     *
2888add1155SRico Sonntag     * @param int $year1
2898add1155SRico Sonntag     * @param int $year2
2908add1155SRico Sonntag     *
2918add1155SRico Sonntag     * @return string
2928add1155SRico Sonntag     */
29388de55fdSRico Sonntag    public function chartNoChildrenFamilies(int $year1 = -1, int $year2 = -1): string
2948add1155SRico Sonntag    {
2958add1155SRico Sonntag        $no_child_fam = $this->noChildrenFamiliesQuery();
2968add1155SRico Sonntag
297f78da678SGreg Roach        return (new ChartNoChildrenFamilies($this->century_service, $this->tree))
29888de55fdSRico Sonntag            ->chartNoChildrenFamilies($no_child_fam, $year1, $year2);
2998add1155SRico Sonntag    }
3008add1155SRico Sonntag
3018add1155SRico Sonntag    /**
3028add1155SRico Sonntag     * Returns the ages between siblings.
3038add1155SRico Sonntag     *
3048add1155SRico Sonntag     * @param int $total The total number of records to query
3058add1155SRico Sonntag     *
306f70bcff5SGreg Roach     * @return array<object>
3078add1155SRico Sonntag     */
3088add1155SRico Sonntag    private function ageBetweenSiblingsQuery(int $total): array
3098add1155SRico Sonntag    {
310b1126ab4SGreg Roach        $prefix = DB::connection()->getTablePrefix();
3118add1155SRico Sonntag
312b1126ab4SGreg Roach        return DB::table('link AS link1')
3130b5fd0a6SGreg Roach            ->join('link AS link2', static function (JoinClause $join): void {
314b1126ab4SGreg Roach                $join
315b1126ab4SGreg Roach                    ->on('link2.l_from', '=', 'link1.l_from')
316b1126ab4SGreg Roach                    ->on('link2.l_type', '=', 'link1.l_type')
317b1126ab4SGreg Roach                    ->on('link2.l_file', '=', 'link1.l_file');
318b1126ab4SGreg Roach            })
3190b5fd0a6SGreg Roach            ->join('dates AS child1', static function (JoinClause $join): void {
320b1126ab4SGreg Roach                $join
321b1126ab4SGreg Roach                    ->on('child1.d_gid', '=', 'link1.l_to')
322b1126ab4SGreg Roach                    ->on('child1.d_file', '=', 'link1.l_file')
323b1126ab4SGreg Roach                    ->where('child1.d_fact', '=', 'BIRT')
324b1126ab4SGreg Roach                    ->where('child1.d_julianday1', '<>', 0);
325b1126ab4SGreg Roach            })
3260b5fd0a6SGreg Roach            ->join('dates AS child2', static function (JoinClause $join): void {
327b1126ab4SGreg Roach                $join
328b1126ab4SGreg Roach                    ->on('child2.d_gid', '=', 'link2.l_to')
329b1126ab4SGreg Roach                    ->on('child2.d_file', '=', 'link2.l_file')
330b1126ab4SGreg Roach                    ->where('child2.d_fact', '=', 'BIRT')
331b1126ab4SGreg Roach                    ->whereColumn('child2.d_julianday2', '>', 'child1.d_julianday1');
332b1126ab4SGreg Roach            })
333b1126ab4SGreg Roach            ->where('link1.l_type', '=', 'CHIL')
334b1126ab4SGreg Roach            ->where('link1.l_file', '=', $this->tree->id())
335b1126ab4SGreg Roach            ->distinct()
336a69f5655SGreg 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')])
337b1126ab4SGreg Roach            ->orderBy('age', 'DESC')
338b1126ab4SGreg Roach            ->take($total)
339b1126ab4SGreg Roach            ->get()
340b1126ab4SGreg Roach            ->all();
3418add1155SRico Sonntag    }
3428add1155SRico Sonntag
3438add1155SRico Sonntag    /**
3448add1155SRico Sonntag     * Returns the calculated age the time of event.
3458add1155SRico Sonntag     *
3468add1155SRico Sonntag     * @param int $age The age from the database record
3478add1155SRico Sonntag     *
3488add1155SRico Sonntag     * @return string
3498add1155SRico Sonntag     */
3508add1155SRico Sonntag    private function calculateAge(int $age): string
3518add1155SRico Sonntag    {
352054771e9SGreg Roach        if ($age < 31) {
353054771e9SGreg Roach            return I18N::plural('%s day', '%s days', $age, I18N::number($age));
3548add1155SRico Sonntag        }
3558add1155SRico Sonntag
356054771e9SGreg Roach        if ($age < 365) {
357054771e9SGreg Roach            $months = (int) ($age / 30.5);
358054771e9SGreg Roach
3591061e22bSGreg Roach            return I18N::plural('%s month', '%s months', $months, I18N::number($months));
360054771e9SGreg Roach        }
361054771e9SGreg Roach
362054771e9SGreg Roach        $years = (int) ($age / 365.25);
363054771e9SGreg Roach
364054771e9SGreg Roach        return I18N::plural('%s year', '%s years', $years, I18N::number($years));
3658add1155SRico Sonntag    }
3668add1155SRico Sonntag
3678add1155SRico Sonntag    /**
3688add1155SRico Sonntag     * Find the ages between siblings.
3698add1155SRico Sonntag     *
3708add1155SRico Sonntag     * @param int $total The total number of records to query
3718add1155SRico Sonntag     *
372f78da678SGreg Roach     * @return array<string,Individual|Family|string>
3736ccdf4f0SGreg Roach     * @throws Exception
3748add1155SRico Sonntag     */
3758add1155SRico Sonntag    private function ageBetweenSiblingsNoList(int $total): array
3768add1155SRico Sonntag    {
3778add1155SRico Sonntag        $rows = $this->ageBetweenSiblingsQuery($total);
3788add1155SRico Sonntag
3798add1155SRico Sonntag        foreach ($rows as $fam) {
3806b9cb339SGreg Roach            $family = Registry::familyFactory()->make($fam->family, $this->tree);
3816b9cb339SGreg Roach            $child1 = Registry::individualFactory()->make($fam->ch1, $this->tree);
3826b9cb339SGreg Roach            $child2 = Registry::individualFactory()->make($fam->ch2, $this->tree);
3838add1155SRico Sonntag
384f78da678SGreg Roach            if ($family !== null && $child1 !== null && $child2 !== null && $child1->canShow() && $child2->canShow()) {
3858add1155SRico Sonntag                // ! Single array (no list)
3868add1155SRico Sonntag                return [
3878add1155SRico Sonntag                    'child1' => $child1,
3888add1155SRico Sonntag                    'child2' => $child2,
3898add1155SRico Sonntag                    'family' => $family,
3908add1155SRico Sonntag                    'age'    => $this->calculateAge((int) $fam->age),
3918add1155SRico Sonntag                ];
3928add1155SRico Sonntag            }
3938add1155SRico Sonntag        }
3948add1155SRico Sonntag
3958add1155SRico Sonntag        return [];
3968add1155SRico Sonntag    }
3978add1155SRico Sonntag
3988add1155SRico Sonntag    /**
3998add1155SRico Sonntag     * Find the ages between siblings.
4008add1155SRico Sonntag     *
4018add1155SRico Sonntag     * @param int  $total The total number of records to query
4028add1155SRico Sonntag     * @param bool $one   Include each family only once if true
4038add1155SRico Sonntag     *
404f78da678SGreg Roach     * @return array<int,array<string,Individual|Family|string>>
4056ccdf4f0SGreg Roach     * @throws Exception
4068add1155SRico Sonntag     */
4078add1155SRico Sonntag    private function ageBetweenSiblingsList(int $total, bool $one): array
4088add1155SRico Sonntag    {
4098add1155SRico Sonntag        $rows  = $this->ageBetweenSiblingsQuery($total);
4108add1155SRico Sonntag        $top10 = [];
4118add1155SRico Sonntag        $dist  = [];
4128add1155SRico Sonntag
4138add1155SRico Sonntag        foreach ($rows as $fam) {
4146b9cb339SGreg Roach            $family = Registry::familyFactory()->make($fam->family, $this->tree);
4156b9cb339SGreg Roach            $child1 = Registry::individualFactory()->make($fam->ch1, $this->tree);
4166b9cb339SGreg Roach            $child2 = Registry::individualFactory()->make($fam->ch2, $this->tree);
4178add1155SRico Sonntag
4188add1155SRico Sonntag            $age = $this->calculateAge((int) $fam->age);
4198add1155SRico Sonntag
4206ccdf4f0SGreg Roach            if ($one && !in_array($fam->family, $dist, true)) {
421f78da678SGreg Roach                if ($family !== null && $child1 !== null && $child2 !== null && $child1->canShow() && $child2->canShow()) {
4228add1155SRico Sonntag                    $top10[] = [
4238add1155SRico Sonntag                        'child1' => $child1,
4248add1155SRico Sonntag                        'child2' => $child2,
4258add1155SRico Sonntag                        'family' => $family,
4268add1155SRico Sonntag                        'age'    => $age,
4278add1155SRico Sonntag                    ];
4288add1155SRico Sonntag
4298add1155SRico Sonntag                    $dist[] = $fam->family;
4308add1155SRico Sonntag                }
431f78da678SGreg Roach            } elseif (!$one && $family !== null && $child1 !== null && $child2 !== null && $child1->canShow() && $child2->canShow()) {
4328add1155SRico Sonntag                $top10[] = [
4338add1155SRico Sonntag                    'child1' => $child1,
4348add1155SRico Sonntag                    'child2' => $child2,
4358add1155SRico Sonntag                    'family' => $family,
4368add1155SRico Sonntag                    'age'    => $age,
4378add1155SRico Sonntag                ];
4388add1155SRico Sonntag            }
4398add1155SRico Sonntag        }
4408add1155SRico Sonntag
4418add1155SRico Sonntag        return $top10;
4428add1155SRico Sonntag    }
4438add1155SRico Sonntag
4448add1155SRico Sonntag    /**
4458add1155SRico Sonntag     * Find the ages between siblings.
4468add1155SRico Sonntag     *
4478add1155SRico Sonntag     * @param int $total The total number of records to query
4488add1155SRico Sonntag     *
4498add1155SRico Sonntag     * @return string
4508add1155SRico Sonntag     */
4518add1155SRico Sonntag    private function ageBetweenSiblingsAge(int $total): string
4528add1155SRico Sonntag    {
4538add1155SRico Sonntag        $rows = $this->ageBetweenSiblingsQuery($total);
4548add1155SRico Sonntag
4558add1155SRico Sonntag        foreach ($rows as $fam) {
4568add1155SRico Sonntag            return $this->calculateAge((int) $fam->age);
4578add1155SRico Sonntag        }
4588add1155SRico Sonntag
4598add1155SRico Sonntag        return '';
4608add1155SRico Sonntag    }
4618add1155SRico Sonntag
4628add1155SRico Sonntag    /**
4638add1155SRico Sonntag     * Find the ages between siblings.
4648add1155SRico Sonntag     *
4658add1155SRico Sonntag     * @param int $total The total number of records to query
4668add1155SRico Sonntag     *
4678add1155SRico Sonntag     * @return string
4686ccdf4f0SGreg Roach     * @throws Exception
4698add1155SRico Sonntag     */
4708add1155SRico Sonntag    private function ageBetweenSiblingsName(int $total): string
4718add1155SRico Sonntag    {
4728add1155SRico Sonntag        $rows = $this->ageBetweenSiblingsQuery($total);
4738add1155SRico Sonntag
4748add1155SRico Sonntag        foreach ($rows as $fam) {
4756b9cb339SGreg Roach            $family = Registry::familyFactory()->make($fam->family, $this->tree);
4766b9cb339SGreg Roach            $child1 = Registry::individualFactory()->make($fam->ch1, $this->tree);
4776b9cb339SGreg Roach            $child2 = Registry::individualFactory()->make($fam->ch2, $this->tree);
4788add1155SRico Sonntag
479f78da678SGreg Roach            if ($family !== null && $child1 !== null && $child2 !== null && $child1->canShow() && $child2->canShow()) {
48039ca88baSGreg Roach                $return = '<a href="' . e($child2->url()) . '">' . $child2->fullName() . '</a> ';
4818add1155SRico Sonntag                $return .= I18N::translate('and') . ' ';
48239ca88baSGreg Roach                $return .= '<a href="' . e($child1->url()) . '">' . $child1->fullName() . '</a>';
4838add1155SRico Sonntag                $return .= ' <a href="' . e($family->url()) . '">[' . I18N::translate('View this family') . ']</a>';
4848add1155SRico Sonntag            } else {
4858add1155SRico Sonntag                $return = I18N::translate('This information is private and cannot be shown.');
4868add1155SRico Sonntag            }
4878add1155SRico Sonntag
4888add1155SRico Sonntag            return $return;
4898add1155SRico Sonntag        }
4908add1155SRico Sonntag
4918add1155SRico Sonntag        return '';
4928add1155SRico Sonntag    }
4938add1155SRico Sonntag
4948add1155SRico Sonntag    /**
4958add1155SRico Sonntag     * Find the names of siblings with the widest age gap.
4968add1155SRico Sonntag     *
4978add1155SRico Sonntag     * @param int $total
4988add1155SRico Sonntag     *
4998add1155SRico Sonntag     * @return string
5008add1155SRico Sonntag     */
5018add1155SRico Sonntag    public function topAgeBetweenSiblingsName(int $total = 10): string
5028add1155SRico Sonntag    {
5038add1155SRico Sonntag        return $this->ageBetweenSiblingsName($total);
5048add1155SRico Sonntag    }
5058add1155SRico Sonntag
5068add1155SRico Sonntag    /**
5078add1155SRico Sonntag     * Find the widest age gap between siblings.
5088add1155SRico Sonntag     *
5098add1155SRico Sonntag     * @param int $total
5108add1155SRico Sonntag     *
5118add1155SRico Sonntag     * @return string
5128add1155SRico Sonntag     */
5138add1155SRico Sonntag    public function topAgeBetweenSiblings(int $total = 10): string
5148add1155SRico Sonntag    {
5158add1155SRico Sonntag        return $this->ageBetweenSiblingsAge($total);
5168add1155SRico Sonntag    }
5178add1155SRico Sonntag
5188add1155SRico Sonntag    /**
5198add1155SRico Sonntag     * Find the name of siblings with the widest age gap.
5208add1155SRico Sonntag     *
5218add1155SRico Sonntag     * @param int $total
5228add1155SRico Sonntag     *
5238add1155SRico Sonntag     * @return string
5248add1155SRico Sonntag     */
5258add1155SRico Sonntag    public function topAgeBetweenSiblingsFullName(int $total = 10): string
5268add1155SRico Sonntag    {
5278add1155SRico Sonntag        $record = $this->ageBetweenSiblingsNoList($total);
5288add1155SRico Sonntag
529b2448a1bSGreg Roach        if ($record === []) {
530dd7dd2a1SRico Sonntag            return I18N::translate('This information is not available.');
531cb2263dcSGreg Roach        }
532cb2263dcSGreg Roach
533cb2263dcSGreg Roach        return view('statistics/families/top10-nolist-age', [
5348add1155SRico Sonntag            'record' => $record,
535cb2263dcSGreg Roach        ]);
5368add1155SRico Sonntag    }
5378add1155SRico Sonntag
5388add1155SRico Sonntag    /**
5398add1155SRico Sonntag     * Find the siblings with the widest age gaps.
5408add1155SRico Sonntag     *
5418add1155SRico Sonntag     * @param int    $total
5428add1155SRico Sonntag     * @param string $one
5438add1155SRico Sonntag     *
5448add1155SRico Sonntag     * @return string
5458add1155SRico Sonntag     */
5468add1155SRico Sonntag    public function topAgeBetweenSiblingsList(int $total = 10, string $one = ''): string
5478add1155SRico Sonntag    {
5488add1155SRico Sonntag        $records = $this->ageBetweenSiblingsList($total, (bool) $one);
5498add1155SRico Sonntag
550cb2263dcSGreg Roach        return view('statistics/families/top10-list-age', [
5518add1155SRico Sonntag            'records' => $records,
552cb2263dcSGreg Roach        ]);
5538add1155SRico Sonntag    }
5548add1155SRico Sonntag
5558add1155SRico Sonntag    /**
556*52f124b0SAlejandro Criado-Pérez     * General query on families/children.
5578add1155SRico Sonntag     *
5588add1155SRico Sonntag     * @param int    $year1
5598add1155SRico Sonntag     * @param int    $year2
5608add1155SRico Sonntag     *
56176d39c55SGreg Roach     * @return array<stdClass>
5628add1155SRico Sonntag     */
563b1126ab4SGreg Roach    public function statsChildrenQuery(int $year1 = -1, int $year2 = -1): array
5648add1155SRico Sonntag    {
565b1126ab4SGreg Roach        $query = DB::table('families')
566b1126ab4SGreg Roach            ->where('f_file', '=', $this->tree->id())
5677f5c2944SGreg Roach            ->groupBy(['f_numchil'])
568a69f5655SGreg Roach            ->select(['f_numchil', new Expression('COUNT(*) AS total')]);
5698add1155SRico Sonntag
5708add1155SRico Sonntag        if ($year1 >= 0 && $year2 >= 0) {
571b1126ab4SGreg Roach            $query
5720b5fd0a6SGreg Roach                ->join('dates', static function (JoinClause $join): void {
573b1126ab4SGreg Roach                    $join
574b1126ab4SGreg Roach                        ->on('d_file', '=', 'f_file')
575b1126ab4SGreg Roach                        ->on('d_gid', '=', 'f_id');
576b1126ab4SGreg Roach                })
577b1126ab4SGreg Roach                ->where('d_fact', '=', 'MARR')
578b1126ab4SGreg Roach                ->whereIn('d_type', ['@#DGREGORIAN@', '@#DJULIAN@'])
579b1126ab4SGreg Roach                ->whereBetween('d_year', [$year1, $year2]);
5808add1155SRico Sonntag        }
5818add1155SRico Sonntag
582b1126ab4SGreg Roach        return $query->get()->all();
5838add1155SRico Sonntag    }
5848add1155SRico Sonntag
5858add1155SRico Sonntag    /**
5868add1155SRico Sonntag     * Genearl query on families/children.
5878add1155SRico Sonntag     *
5888add1155SRico Sonntag     * @return string
5898add1155SRico Sonntag     */
59088de55fdSRico Sonntag    public function statsChildren(): string
5918add1155SRico Sonntag    {
592f78da678SGreg Roach        return (new ChartChildren($this->century_service, $this->tree))
59388de55fdSRico Sonntag            ->chartChildren();
5948add1155SRico Sonntag    }
5958add1155SRico Sonntag
5968add1155SRico Sonntag    /**
5978add1155SRico Sonntag     * Count the total children.
5988add1155SRico Sonntag     *
5998add1155SRico Sonntag     * @return string
6008add1155SRico Sonntag     */
6018add1155SRico Sonntag    public function totalChildren(): string
6028add1155SRico Sonntag    {
603d1a467e4SGreg Roach        $total = (int) DB::table('families')
604d1a467e4SGreg Roach            ->where('f_file', '=', $this->tree->id())
605d1a467e4SGreg Roach            ->sum('f_numchil');
6068add1155SRico Sonntag
6078add1155SRico Sonntag        return I18N::number($total);
6088add1155SRico Sonntag    }
6098add1155SRico Sonntag
6108add1155SRico Sonntag    /**
6118add1155SRico Sonntag     * Find the average number of children in families.
6128add1155SRico Sonntag     *
6138add1155SRico Sonntag     * @return string
6148add1155SRico Sonntag     */
6158add1155SRico Sonntag    public function averageChildren(): string
6168add1155SRico Sonntag    {
617d1a467e4SGreg Roach        $average = (float) DB::table('families')
618d1a467e4SGreg Roach            ->where('f_file', '=', $this->tree->id())
619d1a467e4SGreg Roach            ->avg('f_numchil');
6208add1155SRico Sonntag
6218add1155SRico Sonntag        return I18N::number($average, 2);
6228add1155SRico Sonntag    }
6238add1155SRico Sonntag
6248add1155SRico Sonntag    /**
6258add1155SRico Sonntag     * General query on families.
6268add1155SRico Sonntag     *
6278add1155SRico Sonntag     * @param int $total
6288add1155SRico Sonntag     *
62991c84b80SGreg Roach     * @return array<array<string,mixed>>
6308add1155SRico Sonntag     */
6318add1155SRico Sonntag    private function topTenFamilyQuery(int $total): array
6328add1155SRico Sonntag    {
6332d7289dcSGreg Roach        return DB::table('families')
6342d7289dcSGreg Roach            ->where('f_file', '=', $this->tree->id())
6352d7289dcSGreg Roach            ->orderBy('f_numchil', 'DESC')
6362d7289dcSGreg Roach            ->limit($total)
6372d7289dcSGreg Roach            ->get()
6386b9cb339SGreg Roach            ->map(Registry::familyFactory()->mapper($this->tree))
6392d7289dcSGreg Roach            ->filter(GedcomRecord::accessFilter())
6400b5fd0a6SGreg Roach            ->map(static function (Family $family): array {
6412d7289dcSGreg Roach                return [
6428add1155SRico Sonntag                    'family' => $family,
6432d7289dcSGreg Roach                    'count'  => $family->numberOfChildren(),
6448add1155SRico Sonntag                ];
6452d7289dcSGreg Roach            })
6462d7289dcSGreg Roach            ->all();
6478add1155SRico Sonntag    }
6488add1155SRico Sonntag
6498add1155SRico Sonntag    /**
6508add1155SRico Sonntag     * The the families with the most children.
6518add1155SRico Sonntag     *
6528add1155SRico Sonntag     * @param int $total
6538add1155SRico Sonntag     *
6548add1155SRico Sonntag     * @return string
6558add1155SRico Sonntag     */
6568add1155SRico Sonntag    public function topTenLargestFamily(int $total = 10): string
6578add1155SRico Sonntag    {
6588add1155SRico Sonntag        $records = $this->topTenFamilyQuery($total);
6598add1155SRico Sonntag
660c0112ce8SGreg Roach        return view('statistics/families/top10-nolist', [
6618add1155SRico Sonntag            'records' => $records,
662c0112ce8SGreg Roach        ]);
6638add1155SRico Sonntag    }
6648add1155SRico Sonntag
6658add1155SRico Sonntag    /**
6668add1155SRico Sonntag     * Find the families with the most children.
6678add1155SRico Sonntag     *
6688add1155SRico Sonntag     * @param int $total
6698add1155SRico Sonntag     *
6708add1155SRico Sonntag     * @return string
6718add1155SRico Sonntag     */
6728add1155SRico Sonntag    public function topTenLargestFamilyList(int $total = 10): string
6738add1155SRico Sonntag    {
6748add1155SRico Sonntag        $records = $this->topTenFamilyQuery($total);
6758add1155SRico Sonntag
676c0112ce8SGreg Roach        return view('statistics/families/top10-list', [
6778add1155SRico Sonntag            'records' => $records,
678c0112ce8SGreg Roach        ]);
6798add1155SRico Sonntag    }
6808add1155SRico Sonntag
6818add1155SRico Sonntag    /**
6828add1155SRico Sonntag     * Create a chart of the largest families.
6838add1155SRico Sonntag     *
6848add1155SRico Sonntag     * @param string|null $color_from
6858add1155SRico Sonntag     * @param string|null $color_to
6868add1155SRico Sonntag     * @param int         $total
6878add1155SRico Sonntag     *
6888add1155SRico Sonntag     * @return string
6898add1155SRico Sonntag     */
6908add1155SRico Sonntag    public function chartLargestFamilies(
6918add1155SRico Sonntag        string $color_from = null,
6928add1155SRico Sonntag        string $color_to = null,
6938add1155SRico Sonntag        int $total = 10
694e2cbf57aSGreg Roach    ): string {
695f78da678SGreg Roach        return (new ChartFamilyLargest($this->color_service, $this->tree))
69688de55fdSRico Sonntag            ->chartLargestFamilies($color_from, $color_to, $total);
6978add1155SRico Sonntag    }
6988add1155SRico Sonntag
6998add1155SRico Sonntag    /**
7008add1155SRico Sonntag     * Find the month in the year of the birth of the first child.
7018add1155SRico Sonntag     *
702999da590SGreg Roach     * @param int $year1
703999da590SGreg Roach     * @param int $year2
7048add1155SRico Sonntag     *
705999da590SGreg Roach     * @return Builder
7068add1155SRico Sonntag     */
707999da590SGreg Roach    public function monthFirstChildQuery(int $year1 = -1, int $year2 = -1): Builder
7088add1155SRico Sonntag    {
709b1126ab4SGreg Roach        $first_child_subquery = DB::table('link')
7100b5fd0a6SGreg Roach            ->join('dates', static function (JoinClause $join): void {
711b1126ab4SGreg Roach                $join
712b1126ab4SGreg Roach                    ->on('d_gid', '=', 'l_to')
713b1126ab4SGreg Roach                    ->on('d_file', '=', 'l_file')
714b1126ab4SGreg Roach                    ->where('d_julianday1', '<>', 0)
715b1126ab4SGreg Roach                    ->whereIn('d_month', ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC']);
716b1126ab4SGreg Roach            })
717b1126ab4SGreg Roach            ->where('l_file', '=', $this->tree->id())
718b1126ab4SGreg Roach            ->where('l_type', '=', 'CHIL')
719a69f5655SGreg Roach            ->select(['l_from AS family_id', new Expression('MIN(d_julianday1) AS min_birth_jd')])
7207f5c2944SGreg Roach            ->groupBy(['family_id']);
721b1126ab4SGreg Roach
722b1126ab4SGreg Roach        $query = DB::table('link')
7230b5fd0a6SGreg Roach            ->join('dates', static function (JoinClause $join): void {
724b1126ab4SGreg Roach                $join
725b1126ab4SGreg Roach                    ->on('d_gid', '=', 'l_to')
726b1126ab4SGreg Roach                    ->on('d_file', '=', 'l_file');
727b1126ab4SGreg Roach            })
7280b5fd0a6SGreg Roach            ->joinSub($first_child_subquery, 'subquery', static function (JoinClause $join): void {
729b1126ab4SGreg Roach                $join
730b1126ab4SGreg Roach                    ->on('family_id', '=', 'l_from')
731b1126ab4SGreg Roach                    ->on('min_birth_jd', '=', 'd_julianday1');
732b1126ab4SGreg Roach            })
733b1126ab4SGreg Roach            ->where('link.l_file', '=', $this->tree->id())
734999da590SGreg Roach            ->where('link.l_type', '=', 'CHIL')
735a69f5655SGreg Roach            ->select(['d_month', new Expression('COUNT(*) AS total')])
736999da590SGreg Roach            ->groupBy(['d_month']);
737b1126ab4SGreg Roach
738999da590SGreg Roach        if ($year1 >= 0 && $year2 >= 0) {
739999da590SGreg Roach            $query->whereBetween('d_year', [$year1, $year2]);
740999da590SGreg Roach        }
741b1126ab4SGreg Roach
742999da590SGreg Roach        return $query;
743999da590SGreg Roach    }
744999da590SGreg Roach
745999da590SGreg Roach    /**
746999da590SGreg Roach     * Find the month in the year of the birth of the first child.
747999da590SGreg Roach     *
748999da590SGreg Roach     * @param int $year1
749999da590SGreg Roach     * @param int $year2
750999da590SGreg Roach     *
751999da590SGreg Roach     * @return Builder
752999da590SGreg Roach     */
753999da590SGreg Roach    public function monthFirstChildBySexQuery(int $year1 = -1, int $year2 = -1): Builder
754999da590SGreg Roach    {
755999da590SGreg Roach        return $this->monthFirstChildQuery($year1, $year2)
7560b5fd0a6SGreg Roach            ->join('individuals', static function (JoinClause $join): void {
757b1126ab4SGreg Roach                $join
758b1126ab4SGreg Roach                    ->on('i_file', '=', 'l_file')
759b1126ab4SGreg Roach                    ->on('i_id', '=', 'l_to');
760b1126ab4SGreg Roach            })
761a69f5655SGreg Roach            ->select(['d_month', 'i_sex', new Expression('COUNT(*) AS total')])
762b1126ab4SGreg Roach            ->groupBy(['d_month', 'i_sex']);
7638add1155SRico Sonntag    }
7648add1155SRico Sonntag
7658add1155SRico Sonntag    /**
7668add1155SRico Sonntag     * Number of husbands.
7678add1155SRico Sonntag     *
7688add1155SRico Sonntag     * @return string
7698add1155SRico Sonntag     */
7708add1155SRico Sonntag    public function totalMarriedMales(): string
7718add1155SRico Sonntag    {
772320f6a24SGreg Roach        $n = DB::table('families')
773d1a467e4SGreg Roach            ->where('f_file', '=', $this->tree->id())
774d1a467e4SGreg Roach            ->where('f_gedcom', 'LIKE', "%\n1 MARR%")
775d1a467e4SGreg Roach            ->distinct()
776d1a467e4SGreg Roach            ->count('f_husb');
7778add1155SRico Sonntag
7788add1155SRico Sonntag        return I18N::number($n);
7798add1155SRico Sonntag    }
7808add1155SRico Sonntag
7818add1155SRico Sonntag    /**
7828add1155SRico Sonntag     * Number of wives.
7838add1155SRico Sonntag     *
7848add1155SRico Sonntag     * @return string
7858add1155SRico Sonntag     */
7868add1155SRico Sonntag    public function totalMarriedFemales(): string
7878add1155SRico Sonntag    {
788320f6a24SGreg Roach        $n = DB::table('families')
789d1a467e4SGreg Roach            ->where('f_file', '=', $this->tree->id())
790d1a467e4SGreg Roach            ->where('f_gedcom', 'LIKE', "%\n1 MARR%")
791d1a467e4SGreg Roach            ->distinct()
792d1a467e4SGreg Roach            ->count('f_wife');
7938add1155SRico Sonntag
7948add1155SRico Sonntag        return I18N::number($n);
7958add1155SRico Sonntag    }
7968add1155SRico Sonntag
7978add1155SRico Sonntag    /**
7988add1155SRico Sonntag     * General query on parents.
7998add1155SRico Sonntag     *
8008add1155SRico Sonntag     * @param string $type
8018add1155SRico Sonntag     * @param string $age_dir
8028add1155SRico Sonntag     * @param string $sex
8038add1155SRico Sonntag     * @param bool   $show_years
8048add1155SRico Sonntag     *
8058add1155SRico Sonntag     * @return string
8068add1155SRico Sonntag     */
8078add1155SRico Sonntag    private function parentsQuery(string $type, string $age_dir, string $sex, bool $show_years): string
8088add1155SRico Sonntag    {
8098add1155SRico Sonntag        if ($sex === 'F') {
8108add1155SRico Sonntag            $sex_field = 'WIFE';
8118add1155SRico Sonntag        } else {
8128add1155SRico Sonntag            $sex_field = 'HUSB';
8138add1155SRico Sonntag        }
8148add1155SRico Sonntag
8158add1155SRico Sonntag        if ($age_dir !== 'ASC') {
8168add1155SRico Sonntag            $age_dir = 'DESC';
8178add1155SRico Sonntag        }
8188add1155SRico Sonntag
8192c928961SGreg Roach        $prefix = DB::connection()->getTablePrefix();
8208add1155SRico Sonntag
8212c928961SGreg Roach        $row = DB::table('link AS parentfamily')
8220b5fd0a6SGreg Roach            ->join('link AS childfamily', static function (JoinClause $join): void {
8232c928961SGreg Roach                $join
8242c928961SGreg Roach                    ->on('childfamily.l_file', '=', 'parentfamily.l_file')
8252c928961SGreg Roach                    ->on('childfamily.l_from', '=', 'parentfamily.l_from')
8262c928961SGreg Roach                    ->where('childfamily.l_type', '=', 'CHIL');
8272c928961SGreg Roach            })
8280b5fd0a6SGreg Roach            ->join('dates AS birth', static function (JoinClause $join): void {
8292c928961SGreg Roach                $join
8302c928961SGreg Roach                    ->on('birth.d_file', '=', 'parentfamily.l_file')
8312c928961SGreg Roach                    ->on('birth.d_gid', '=', 'parentfamily.l_to')
8322c928961SGreg Roach                    ->where('birth.d_fact', '=', 'BIRT')
8332c928961SGreg Roach                    ->where('birth.d_julianday1', '<>', 0);
8342c928961SGreg Roach            })
8350b5fd0a6SGreg Roach            ->join('dates AS childbirth', static function (JoinClause $join): void {
8362c928961SGreg Roach                $join
8372c928961SGreg Roach                    ->on('childbirth.d_file', '=', 'parentfamily.l_file')
8383413ec75SRico Sonntag                    ->on('childbirth.d_gid', '=', 'childfamily.l_to')
8393413ec75SRico Sonntag                    ->where('childbirth.d_fact', '=', 'BIRT');
8402c928961SGreg Roach            })
8412c928961SGreg Roach            ->where('childfamily.l_file', '=', $this->tree->id())
8422c928961SGreg Roach            ->where('parentfamily.l_type', '=', $sex_field)
8432c928961SGreg Roach            ->where('childbirth.d_julianday2', '>', 'birth.d_julianday1')
844a69f5655SGreg Roach            ->select(['parentfamily.l_to AS id', new Expression($prefix . 'childbirth.d_julianday2 - ' . $prefix . 'birth.d_julianday1 AS age')])
8452c928961SGreg Roach            ->take(1)
8462c928961SGreg Roach            ->orderBy('age', $age_dir)
8472c928961SGreg Roach            ->get()
8482c928961SGreg Roach            ->first();
8492c928961SGreg Roach
8502c928961SGreg Roach        if ($row === null) {
851bd055353SGreg Roach            return I18N::translate('This information is not available.');
8528add1155SRico Sonntag        }
8538add1155SRico Sonntag
8546b9cb339SGreg Roach        $person = Registry::individualFactory()->make($row->id, $this->tree);
8558add1155SRico Sonntag
8568add1155SRico Sonntag        switch ($type) {
8578add1155SRico Sonntag            default:
8588add1155SRico Sonntag            case 'full':
859f78da678SGreg Roach                if ($person !== null && $person->canShow()) {
8608add1155SRico Sonntag                    $result = $person->formatList();
8618add1155SRico Sonntag                } else {
8628add1155SRico Sonntag                    $result = I18N::translate('This information is private and cannot be shown.');
8638add1155SRico Sonntag                }
8648add1155SRico Sonntag                break;
8658add1155SRico Sonntag
8668add1155SRico Sonntag            case 'name':
86739ca88baSGreg Roach                $result = '<a href="' . e($person->url()) . '">' . $person->fullName() . '</a>';
8688add1155SRico Sonntag                break;
8698add1155SRico Sonntag
8708add1155SRico Sonntag            case 'age':
8718add1155SRico Sonntag                $age = $row->age;
8728add1155SRico Sonntag
8738add1155SRico Sonntag                if ($show_years) {
8748add1155SRico Sonntag                    $result = $this->calculateAge((int) $row->age);
8758add1155SRico Sonntag                } else {
8768add1155SRico Sonntag                    $result = (string) floor($age / 365.25);
8778add1155SRico Sonntag                }
8788add1155SRico Sonntag
8798add1155SRico Sonntag                break;
8808add1155SRico Sonntag        }
8818add1155SRico Sonntag
8828add1155SRico Sonntag        return $result;
8838add1155SRico Sonntag    }
8848add1155SRico Sonntag
8858add1155SRico Sonntag    /**
8868add1155SRico Sonntag     * Find the youngest mother
8878add1155SRico Sonntag     *
8888add1155SRico Sonntag     * @return string
8898add1155SRico Sonntag     */
8908add1155SRico Sonntag    public function youngestMother(): string
8918add1155SRico Sonntag    {
8928add1155SRico Sonntag        return $this->parentsQuery('full', 'ASC', 'F', false);
8938add1155SRico Sonntag    }
8948add1155SRico Sonntag
8958add1155SRico Sonntag    /**
8968add1155SRico Sonntag     * Find the name of the youngest mother.
8978add1155SRico Sonntag     *
8988add1155SRico Sonntag     * @return string
8998add1155SRico Sonntag     */
9008add1155SRico Sonntag    public function youngestMotherName(): string
9018add1155SRico Sonntag    {
9028add1155SRico Sonntag        return $this->parentsQuery('name', 'ASC', 'F', false);
9038add1155SRico Sonntag    }
9048add1155SRico Sonntag
9058add1155SRico Sonntag    /**
9068add1155SRico Sonntag     * Find the age of the youngest mother.
9078add1155SRico Sonntag     *
9088add1155SRico Sonntag     * @param string $show_years
9098add1155SRico Sonntag     *
9108add1155SRico Sonntag     * @return string
9118add1155SRico Sonntag     */
9128add1155SRico Sonntag    public function youngestMotherAge(string $show_years = ''): string
9138add1155SRico Sonntag    {
9148add1155SRico Sonntag        return $this->parentsQuery('age', 'ASC', 'F', (bool) $show_years);
9158add1155SRico Sonntag    }
9168add1155SRico Sonntag
9178add1155SRico Sonntag    /**
9188add1155SRico Sonntag     * Find the oldest mother.
9198add1155SRico Sonntag     *
9208add1155SRico Sonntag     * @return string
9218add1155SRico Sonntag     */
9228add1155SRico Sonntag    public function oldestMother(): string
9238add1155SRico Sonntag    {
9248add1155SRico Sonntag        return $this->parentsQuery('full', 'DESC', 'F', false);
9258add1155SRico Sonntag    }
9268add1155SRico Sonntag
9278add1155SRico Sonntag    /**
9288add1155SRico Sonntag     * Find the name of the oldest mother.
9298add1155SRico Sonntag     *
9308add1155SRico Sonntag     * @return string
9318add1155SRico Sonntag     */
9328add1155SRico Sonntag    public function oldestMotherName(): string
9338add1155SRico Sonntag    {
9348add1155SRico Sonntag        return $this->parentsQuery('name', 'DESC', 'F', false);
9358add1155SRico Sonntag    }
9368add1155SRico Sonntag
9378add1155SRico Sonntag    /**
9388add1155SRico Sonntag     * Find the age of the oldest mother.
9398add1155SRico Sonntag     *
9408add1155SRico Sonntag     * @param string $show_years
9418add1155SRico Sonntag     *
9428add1155SRico Sonntag     * @return string
9438add1155SRico Sonntag     */
9448add1155SRico Sonntag    public function oldestMotherAge(string $show_years = ''): string
9458add1155SRico Sonntag    {
9468add1155SRico Sonntag        return $this->parentsQuery('age', 'DESC', 'F', (bool) $show_years);
9478add1155SRico Sonntag    }
9488add1155SRico Sonntag
9498add1155SRico Sonntag    /**
9508add1155SRico Sonntag     * Find the youngest father.
9518add1155SRico Sonntag     *
9528add1155SRico Sonntag     * @return string
9538add1155SRico Sonntag     */
9548add1155SRico Sonntag    public function youngestFather(): string
9558add1155SRico Sonntag    {
9568add1155SRico Sonntag        return $this->parentsQuery('full', 'ASC', 'M', false);
9578add1155SRico Sonntag    }
9588add1155SRico Sonntag
9598add1155SRico Sonntag    /**
9608add1155SRico Sonntag     * Find the name of the youngest father.
9618add1155SRico Sonntag     *
9628add1155SRico Sonntag     * @return string
9638add1155SRico Sonntag     */
9648add1155SRico Sonntag    public function youngestFatherName(): string
9658add1155SRico Sonntag    {
9668add1155SRico Sonntag        return $this->parentsQuery('name', 'ASC', 'M', false);
9678add1155SRico Sonntag    }
9688add1155SRico Sonntag
9698add1155SRico Sonntag    /**
9708add1155SRico Sonntag     * Find the age of the youngest father.
9718add1155SRico Sonntag     *
9728add1155SRico Sonntag     * @param string $show_years
9738add1155SRico Sonntag     *
9748add1155SRico Sonntag     * @return string
9758add1155SRico Sonntag     */
9768add1155SRico Sonntag    public function youngestFatherAge(string $show_years = ''): string
9778add1155SRico Sonntag    {
9788add1155SRico Sonntag        return $this->parentsQuery('age', 'ASC', 'M', (bool) $show_years);
9798add1155SRico Sonntag    }
9808add1155SRico Sonntag
9818add1155SRico Sonntag    /**
9828add1155SRico Sonntag     * Find the oldest father.
9838add1155SRico Sonntag     *
9848add1155SRico Sonntag     * @return string
9858add1155SRico Sonntag     */
9868add1155SRico Sonntag    public function oldestFather(): string
9878add1155SRico Sonntag    {
9888add1155SRico Sonntag        return $this->parentsQuery('full', 'DESC', 'M', false);
9898add1155SRico Sonntag    }
9908add1155SRico Sonntag
9918add1155SRico Sonntag    /**
9928add1155SRico Sonntag     * Find the name of the oldest father.
9938add1155SRico Sonntag     *
9948add1155SRico Sonntag     * @return string
9958add1155SRico Sonntag     */
9968add1155SRico Sonntag    public function oldestFatherName(): string
9978add1155SRico Sonntag    {
9988add1155SRico Sonntag        return $this->parentsQuery('name', 'DESC', 'M', false);
9998add1155SRico Sonntag    }
10008add1155SRico Sonntag
10018add1155SRico Sonntag    /**
10028add1155SRico Sonntag     * Find the age of the oldest father.
10038add1155SRico Sonntag     *
10048add1155SRico Sonntag     * @param string $show_years
10058add1155SRico Sonntag     *
10068add1155SRico Sonntag     * @return string
10078add1155SRico Sonntag     */
10088add1155SRico Sonntag    public function oldestFatherAge(string $show_years = ''): string
10098add1155SRico Sonntag    {
10108add1155SRico Sonntag        return $this->parentsQuery('age', 'DESC', 'M', (bool) $show_years);
10118add1155SRico Sonntag    }
10128add1155SRico Sonntag
10138add1155SRico Sonntag    /**
10148add1155SRico Sonntag     * General query on age at marriage.
10158add1155SRico Sonntag     *
10168add1155SRico Sonntag     * @param string $type
10175ad3f7b6SGreg Roach     * @param string $age_dir "ASC" or "DESC"
10188add1155SRico Sonntag     * @param int    $total
10198add1155SRico Sonntag     *
10208add1155SRico Sonntag     * @return string
10218add1155SRico Sonntag     */
10228add1155SRico Sonntag    private function ageOfMarriageQuery(string $type, string $age_dir, int $total): string
10238add1155SRico Sonntag    {
10245ad3f7b6SGreg Roach        $prefix = DB::connection()->getTablePrefix();
10258add1155SRico Sonntag
10265ad3f7b6SGreg Roach        $hrows = DB::table('families')
10275ad3f7b6SGreg Roach            ->where('f_file', '=', $this->tree->id())
10280b5fd0a6SGreg Roach            ->join('dates AS married', static function (JoinClause $join): void {
10295ad3f7b6SGreg Roach                $join
10305ad3f7b6SGreg Roach                    ->on('married.d_file', '=', 'f_file')
10315ad3f7b6SGreg Roach                    ->on('married.d_gid', '=', 'f_id')
10325ad3f7b6SGreg Roach                    ->where('married.d_fact', '=', 'MARR')
10335ad3f7b6SGreg Roach                    ->where('married.d_julianday1', '<>', 0);
10345ad3f7b6SGreg Roach            })
10350b5fd0a6SGreg Roach            ->join('dates AS husbdeath', static function (JoinClause $join): void {
10365ad3f7b6SGreg Roach                $join
10375ad3f7b6SGreg Roach                    ->on('husbdeath.d_gid', '=', 'f_husb')
10385ad3f7b6SGreg Roach                    ->on('husbdeath.d_file', '=', 'f_file')
10395ad3f7b6SGreg Roach                    ->where('husbdeath.d_fact', '=', 'DEAT');
10405ad3f7b6SGreg Roach            })
10415ad3f7b6SGreg Roach            ->whereColumn('married.d_julianday1', '<', 'husbdeath.d_julianday2')
10427f5c2944SGreg Roach            ->groupBy(['f_id'])
1043a69f5655SGreg Roach            ->select(['f_id AS family', new Expression('MIN(' . $prefix . 'husbdeath.d_julianday2 - ' . $prefix . 'married.d_julianday1) AS age')])
10445ad3f7b6SGreg Roach            ->get()
10455ad3f7b6SGreg Roach            ->all();
10468add1155SRico Sonntag
10475ad3f7b6SGreg Roach        $wrows = DB::table('families')
10485ad3f7b6SGreg Roach            ->where('f_file', '=', $this->tree->id())
10490b5fd0a6SGreg Roach            ->join('dates AS married', static function (JoinClause $join): void {
10505ad3f7b6SGreg Roach                $join
10515ad3f7b6SGreg Roach                    ->on('married.d_file', '=', 'f_file')
10525ad3f7b6SGreg Roach                    ->on('married.d_gid', '=', 'f_id')
10535ad3f7b6SGreg Roach                    ->where('married.d_fact', '=', 'MARR')
10545ad3f7b6SGreg Roach                    ->where('married.d_julianday1', '<>', 0);
10555ad3f7b6SGreg Roach            })
10560b5fd0a6SGreg Roach            ->join('dates AS wifedeath', static function (JoinClause $join): void {
10575ad3f7b6SGreg Roach                $join
10585ad3f7b6SGreg Roach                    ->on('wifedeath.d_gid', '=', 'f_wife')
10595ad3f7b6SGreg Roach                    ->on('wifedeath.d_file', '=', 'f_file')
10605ad3f7b6SGreg Roach                    ->where('wifedeath.d_fact', '=', 'DEAT');
10615ad3f7b6SGreg Roach            })
10625ad3f7b6SGreg Roach            ->whereColumn('married.d_julianday1', '<', 'wifedeath.d_julianday2')
10637f5c2944SGreg Roach            ->groupBy(['f_id'])
1064a69f5655SGreg Roach            ->select(['f_id AS family', new Expression('MIN(' . $prefix . 'wifedeath.d_julianday2 - ' . $prefix . 'married.d_julianday1) AS age')])
10655ad3f7b6SGreg Roach            ->get()
10665ad3f7b6SGreg Roach            ->all();
10678add1155SRico Sonntag
10685ad3f7b6SGreg Roach        $drows = DB::table('families')
10695ad3f7b6SGreg Roach            ->where('f_file', '=', $this->tree->id())
10700b5fd0a6SGreg Roach            ->join('dates AS married', static function (JoinClause $join): void {
10715ad3f7b6SGreg Roach                $join
10725ad3f7b6SGreg Roach                    ->on('married.d_file', '=', 'f_file')
10735ad3f7b6SGreg Roach                    ->on('married.d_gid', '=', 'f_id')
10745ad3f7b6SGreg Roach                    ->where('married.d_fact', '=', 'MARR')
10755ad3f7b6SGreg Roach                    ->where('married.d_julianday1', '<>', 0);
10765ad3f7b6SGreg Roach            })
10770b5fd0a6SGreg Roach            ->join('dates AS divorced', static function (JoinClause $join): void {
10785ad3f7b6SGreg Roach                $join
10795ad3f7b6SGreg Roach                    ->on('divorced.d_gid', '=', 'f_id')
10805ad3f7b6SGreg Roach                    ->on('divorced.d_file', '=', 'f_file')
10815ad3f7b6SGreg Roach                    ->whereIn('divorced.d_fact', ['DIV', 'ANUL', '_SEPR']);
10825ad3f7b6SGreg Roach            })
10835ad3f7b6SGreg Roach            ->whereColumn('married.d_julianday1', '<', 'divorced.d_julianday2')
10847f5c2944SGreg Roach            ->groupBy(['f_id'])
1085a69f5655SGreg Roach            ->select(['f_id AS family', new Expression('MIN(' . $prefix . 'divorced.d_julianday2 - ' . $prefix . 'married.d_julianday1) AS age')])
10865ad3f7b6SGreg Roach            ->get()
10875ad3f7b6SGreg Roach            ->all();
10888add1155SRico Sonntag
10898add1155SRico Sonntag        $rows = [];
10908add1155SRico Sonntag        foreach ($drows as $family) {
10918add1155SRico Sonntag            $rows[$family->family] = $family->age;
10928add1155SRico Sonntag        }
10938add1155SRico Sonntag
10948add1155SRico Sonntag        foreach ($hrows as $family) {
10958add1155SRico Sonntag            if (!isset($rows[$family->family])) {
10968add1155SRico Sonntag                $rows[$family->family] = $family->age;
10978add1155SRico Sonntag            }
10988add1155SRico Sonntag        }
10998add1155SRico Sonntag
11008add1155SRico Sonntag        foreach ($wrows as $family) {
11018add1155SRico Sonntag            if (!isset($rows[$family->family])) {
11028add1155SRico Sonntag                $rows[$family->family] = $family->age;
11038add1155SRico Sonntag            } elseif ($rows[$family->family] > $family->age) {
11048add1155SRico Sonntag                $rows[$family->family] = $family->age;
11058add1155SRico Sonntag            }
11068add1155SRico Sonntag        }
11078add1155SRico Sonntag
11088add1155SRico Sonntag        if ($age_dir === 'DESC') {
11098add1155SRico Sonntag            arsort($rows);
11108add1155SRico Sonntag        } else {
11118add1155SRico Sonntag            asort($rows);
11128add1155SRico Sonntag        }
11138add1155SRico Sonntag
11148add1155SRico Sonntag        $top10 = [];
11158add1155SRico Sonntag        $i     = 0;
1116b092a991SGreg Roach        foreach ($rows as $xref => $age) {
11176b9cb339SGreg Roach            $family = Registry::familyFactory()->make((string) $xref, $this->tree);
11188add1155SRico Sonntag            if ($type === 'name') {
11198add1155SRico Sonntag                return $family->formatList();
11208add1155SRico Sonntag            }
11218add1155SRico Sonntag
11228add1155SRico Sonntag            $age = $this->calculateAge((int) $age);
11238add1155SRico Sonntag
11248add1155SRico Sonntag            if ($type === 'age') {
11258add1155SRico Sonntag                return $age;
11268add1155SRico Sonntag            }
11278add1155SRico Sonntag
112839ca88baSGreg Roach            $husb = $family->husband();
112939ca88baSGreg Roach            $wife = $family->wife();
11308add1155SRico Sonntag
1131ef475b14SGreg Roach            if (
1132ef475b14SGreg Roach                $husb instanceof Individual &&
1133ef475b14SGreg Roach                $wife instanceof Individual &&
1134ef475b14SGreg Roach                ($husb->getAllDeathDates() || !$husb->isDead()) &&
1135ef475b14SGreg Roach                ($wife->getAllDeathDates() || !$wife->isDead())
1136ef475b14SGreg Roach            ) {
113741acc92bSGreg Roach                if ($family->canShow()) {
11388add1155SRico Sonntag                    if ($type === 'list') {
113939ca88baSGreg Roach                        $top10[] = '<li><a href="' . e($family->url()) . '">' . $family->fullName() . '</a> (' . $age . ')' . '</li>';
11408add1155SRico Sonntag                    } else {
114139ca88baSGreg Roach                        $top10[] = '<a href="' . e($family->url()) . '">' . $family->fullName() . '</a> (' . $age . ')';
11428add1155SRico Sonntag                    }
11438add1155SRico Sonntag                }
11448add1155SRico Sonntag                if (++$i === $total) {
11458add1155SRico Sonntag                    break;
11468add1155SRico Sonntag                }
11478add1155SRico Sonntag            }
11488add1155SRico Sonntag        }
11498add1155SRico Sonntag
11508add1155SRico Sonntag        if ($type === 'list') {
11518add1155SRico Sonntag            $top10 = implode('', $top10);
11528add1155SRico Sonntag        } else {
11538add1155SRico Sonntag            $top10 = implode('; ', $top10);
11548add1155SRico Sonntag        }
11558add1155SRico Sonntag
11568add1155SRico Sonntag        if (I18N::direction() === 'rtl') {
11578add1155SRico Sonntag            $top10 = str_replace([
11588add1155SRico Sonntag                '[',
11598add1155SRico Sonntag                ']',
11608add1155SRico Sonntag                '(',
11618add1155SRico Sonntag                ')',
11628add1155SRico Sonntag                '+',
11638add1155SRico Sonntag            ], [
11648add1155SRico Sonntag                '&rlm;[',
11658add1155SRico Sonntag                '&rlm;]',
11668add1155SRico Sonntag                '&rlm;(',
11678add1155SRico Sonntag                '&rlm;)',
11688add1155SRico Sonntag                '&rlm;+',
11698add1155SRico Sonntag            ], $top10);
11708add1155SRico Sonntag        }
11718add1155SRico Sonntag
11728add1155SRico Sonntag        if ($type === 'list') {
11738add1155SRico Sonntag            return '<ul>' . $top10 . '</ul>';
11748add1155SRico Sonntag        }
11758add1155SRico Sonntag
11768add1155SRico Sonntag        return $top10;
11778add1155SRico Sonntag    }
11788add1155SRico Sonntag
11798add1155SRico Sonntag    /**
11808add1155SRico Sonntag     * General query on marriage ages.
11818add1155SRico Sonntag     *
11828add1155SRico Sonntag     * @return string
11838add1155SRico Sonntag     */
11848add1155SRico Sonntag    public function topAgeOfMarriageFamily(): string
11858add1155SRico Sonntag    {
11868add1155SRico Sonntag        return $this->ageOfMarriageQuery('name', 'DESC', 1);
11878add1155SRico Sonntag    }
11888add1155SRico Sonntag
11898add1155SRico Sonntag    /**
11908add1155SRico Sonntag     * General query on marriage ages.
11918add1155SRico Sonntag     *
11928add1155SRico Sonntag     * @return string
11938add1155SRico Sonntag     */
11948add1155SRico Sonntag    public function topAgeOfMarriage(): string
11958add1155SRico Sonntag    {
11968add1155SRico Sonntag        return $this->ageOfMarriageQuery('age', 'DESC', 1);
11978add1155SRico Sonntag    }
11988add1155SRico Sonntag
11998add1155SRico Sonntag    /**
12008add1155SRico Sonntag     * General query on marriage ages.
12018add1155SRico Sonntag     *
12028add1155SRico Sonntag     * @param int $total
12038add1155SRico Sonntag     *
12048add1155SRico Sonntag     * @return string
12058add1155SRico Sonntag     */
12068add1155SRico Sonntag    public function topAgeOfMarriageFamilies(int $total = 10): string
12078add1155SRico Sonntag    {
12088add1155SRico Sonntag        return $this->ageOfMarriageQuery('nolist', 'DESC', $total);
12098add1155SRico Sonntag    }
12108add1155SRico Sonntag
12118add1155SRico Sonntag    /**
12128add1155SRico Sonntag     * General query on marriage ages.
12138add1155SRico Sonntag     *
12148add1155SRico Sonntag     * @param int $total
12158add1155SRico Sonntag     *
12168add1155SRico Sonntag     * @return string
12178add1155SRico Sonntag     */
12188add1155SRico Sonntag    public function topAgeOfMarriageFamiliesList(int $total = 10): string
12198add1155SRico Sonntag    {
12208add1155SRico Sonntag        return $this->ageOfMarriageQuery('list', 'DESC', $total);
12218add1155SRico Sonntag    }
12228add1155SRico Sonntag
12238add1155SRico Sonntag    /**
12248add1155SRico Sonntag     * General query on marriage ages.
12258add1155SRico Sonntag     *
12268add1155SRico Sonntag     * @return string
12278add1155SRico Sonntag     */
12288add1155SRico Sonntag    public function minAgeOfMarriageFamily(): string
12298add1155SRico Sonntag    {
12308add1155SRico Sonntag        return $this->ageOfMarriageQuery('name', 'ASC', 1);
12318add1155SRico Sonntag    }
12328add1155SRico Sonntag
12338add1155SRico Sonntag    /**
12348add1155SRico Sonntag     * General query on marriage ages.
12358add1155SRico Sonntag     *
12368add1155SRico Sonntag     * @return string
12378add1155SRico Sonntag     */
12388add1155SRico Sonntag    public function minAgeOfMarriage(): string
12398add1155SRico Sonntag    {
12408add1155SRico Sonntag        return $this->ageOfMarriageQuery('age', 'ASC', 1);
12418add1155SRico Sonntag    }
12428add1155SRico Sonntag
12438add1155SRico Sonntag    /**
12448add1155SRico Sonntag     * General query on marriage ages.
12458add1155SRico Sonntag     *
12468add1155SRico Sonntag     * @param int $total
12478add1155SRico Sonntag     *
12488add1155SRico Sonntag     * @return string
12498add1155SRico Sonntag     */
12508add1155SRico Sonntag    public function minAgeOfMarriageFamilies(int $total = 10): string
12518add1155SRico Sonntag    {
12528add1155SRico Sonntag        return $this->ageOfMarriageQuery('nolist', 'ASC', $total);
12538add1155SRico Sonntag    }
12548add1155SRico Sonntag
12558add1155SRico Sonntag    /**
12568add1155SRico Sonntag     * General query on marriage ages.
12578add1155SRico Sonntag     *
12588add1155SRico Sonntag     * @param int $total
12598add1155SRico Sonntag     *
12608add1155SRico Sonntag     * @return string
12618add1155SRico Sonntag     */
12628add1155SRico Sonntag    public function minAgeOfMarriageFamiliesList(int $total = 10): string
12638add1155SRico Sonntag    {
12648add1155SRico Sonntag        return $this->ageOfMarriageQuery('list', 'ASC', $total);
12658add1155SRico Sonntag    }
12668add1155SRico Sonntag
12678add1155SRico Sonntag    /**
12688add1155SRico Sonntag     * Find the ages between spouses.
12698add1155SRico Sonntag     *
12708add1155SRico Sonntag     * @param string $age_dir
12718add1155SRico Sonntag     * @param int    $total
12728add1155SRico Sonntag     *
127391c84b80SGreg Roach     * @return array<array<string,mixed>>
12748add1155SRico Sonntag     */
12758add1155SRico Sonntag    private function ageBetweenSpousesQuery(string $age_dir, int $total): array
12768add1155SRico Sonntag    {
1277d1a467e4SGreg Roach        $prefix = DB::connection()->getTablePrefix();
1278d1a467e4SGreg Roach
1279d1a467e4SGreg Roach        $query = DB::table('families')
1280d1a467e4SGreg Roach            ->where('f_file', '=', $this->tree->id())
12810b5fd0a6SGreg Roach            ->join('dates AS wife', static function (JoinClause $join): void {
1282d1a467e4SGreg Roach                $join
1283d1a467e4SGreg Roach                    ->on('wife.d_gid', '=', 'f_wife')
1284d1a467e4SGreg Roach                    ->on('wife.d_file', '=', 'f_file')
1285d1a467e4SGreg Roach                    ->where('wife.d_fact', '=', 'BIRT')
1286d1a467e4SGreg Roach                    ->where('wife.d_julianday1', '<>', 0);
1287d1a467e4SGreg Roach            })
12880b5fd0a6SGreg Roach            ->join('dates AS husb', static function (JoinClause $join): void {
1289d1a467e4SGreg Roach                $join
1290d1a467e4SGreg Roach                    ->on('husb.d_gid', '=', 'f_husb')
1291d1a467e4SGreg Roach                    ->on('husb.d_file', '=', 'f_file')
1292d1a467e4SGreg Roach                    ->where('husb.d_fact', '=', 'BIRT')
1293d1a467e4SGreg Roach                    ->where('husb.d_julianday1', '<>', 0);
1294d1a467e4SGreg Roach            });
1295d1a467e4SGreg Roach
12968add1155SRico Sonntag        if ($age_dir === 'DESC') {
1297d1a467e4SGreg Roach            $query
1298d1a467e4SGreg Roach                ->whereColumn('wife.d_julianday1', '>=', 'husb.d_julianday1')
1299a69f5655SGreg Roach                ->orderBy(new Expression('MIN(' . $prefix . 'wife.d_julianday1) - MIN(' . $prefix . 'husb.d_julianday1)'), 'DESC');
13008add1155SRico Sonntag        } else {
1301d1a467e4SGreg Roach            $query
1302d1a467e4SGreg Roach                ->whereColumn('husb.d_julianday1', '>=', 'wife.d_julianday1')
1303a69f5655SGreg Roach                ->orderBy(new Expression('MIN(' . $prefix . 'husb.d_julianday1) - MIN(' . $prefix . 'wife.d_julianday1)'), 'DESC');
13048add1155SRico Sonntag        }
13058add1155SRico Sonntag
1306c0112ce8SGreg Roach        return $query
1307d1a467e4SGreg Roach            ->groupBy(['f_id', 'f_file'])
130847256fc5SGreg Roach            ->select(['families.*'])
1309d1a467e4SGreg Roach            ->take($total)
1310d1a467e4SGreg Roach            ->get()
13116b9cb339SGreg Roach            ->map(Registry::familyFactory()->mapper($this->tree))
1312c0112ce8SGreg Roach            ->filter(GedcomRecord::accessFilter())
1313c0112ce8SGreg Roach            ->map(function (Family $family) use ($age_dir): array {
1314d1a467e4SGreg Roach                $husb_birt_jd = $family->husband()->getBirthDate()->minimumJulianDay();
1315d1a467e4SGreg Roach                $wife_birt_jd = $family->wife()->getBirthDate()->minimumJulianDay();
13168add1155SRico Sonntag
1317d1a467e4SGreg Roach                if ($age_dir === 'DESC') {
1318d1a467e4SGreg Roach                    $diff = $wife_birt_jd - $husb_birt_jd;
1319d1a467e4SGreg Roach                } else {
1320d1a467e4SGreg Roach                    $diff = $husb_birt_jd - $wife_birt_jd;
13218add1155SRico Sonntag                }
13228add1155SRico Sonntag
1323c0112ce8SGreg Roach                return [
13248add1155SRico Sonntag                    'family' => $family,
132593ccd686SRico Sonntag                    'age'    => $this->calculateAge($diff),
13268add1155SRico Sonntag                ];
1327c0112ce8SGreg Roach            })
1328c0112ce8SGreg Roach            ->all();
13298add1155SRico Sonntag    }
13308add1155SRico Sonntag
13318add1155SRico Sonntag    /**
13328add1155SRico Sonntag     * Find the age between husband and wife.
13338add1155SRico Sonntag     *
13348add1155SRico Sonntag     * @param int $total
13358add1155SRico Sonntag     *
13368add1155SRico Sonntag     * @return string
13378add1155SRico Sonntag     */
13388add1155SRico Sonntag    public function ageBetweenSpousesMF(int $total = 10): string
13398add1155SRico Sonntag    {
13408add1155SRico Sonntag        $records = $this->ageBetweenSpousesQuery('DESC', $total);
13418add1155SRico Sonntag
1342c0112ce8SGreg Roach        return view('statistics/families/top10-nolist-spouses', [
13438add1155SRico Sonntag            'records' => $records,
1344c0112ce8SGreg Roach        ]);
13458add1155SRico Sonntag    }
13468add1155SRico Sonntag
13478add1155SRico Sonntag    /**
13488add1155SRico Sonntag     * Find the age between husband and wife.
13498add1155SRico Sonntag     *
13508add1155SRico Sonntag     * @param int $total
13518add1155SRico Sonntag     *
13528add1155SRico Sonntag     * @return string
13538add1155SRico Sonntag     */
13548add1155SRico Sonntag    public function ageBetweenSpousesMFList(int $total = 10): string
13558add1155SRico Sonntag    {
13568add1155SRico Sonntag        $records = $this->ageBetweenSpousesQuery('DESC', $total);
13578add1155SRico Sonntag
1358c0112ce8SGreg Roach        return view('statistics/families/top10-list-spouses', [
13598add1155SRico Sonntag            'records' => $records,
1360c0112ce8SGreg Roach        ]);
13618add1155SRico Sonntag    }
13628add1155SRico Sonntag
13638add1155SRico Sonntag    /**
13648add1155SRico Sonntag     * Find the age between wife and husband..
13658add1155SRico Sonntag     *
13668add1155SRico Sonntag     * @param int $total
13678add1155SRico Sonntag     *
13688add1155SRico Sonntag     * @return string
13698add1155SRico Sonntag     */
13708add1155SRico Sonntag    public function ageBetweenSpousesFM(int $total = 10): string
13718add1155SRico Sonntag    {
13728add1155SRico Sonntag        $records = $this->ageBetweenSpousesQuery('ASC', $total);
13738add1155SRico Sonntag
1374c0112ce8SGreg Roach        return view('statistics/families/top10-nolist-spouses', [
13758add1155SRico Sonntag            'records' => $records,
1376c0112ce8SGreg Roach        ]);
13778add1155SRico Sonntag    }
13788add1155SRico Sonntag
13798add1155SRico Sonntag    /**
13808add1155SRico Sonntag     * Find the age between wife and husband..
13818add1155SRico Sonntag     *
13828add1155SRico Sonntag     * @param int $total
13838add1155SRico Sonntag     *
13848add1155SRico Sonntag     * @return string
13858add1155SRico Sonntag     */
13868add1155SRico Sonntag    public function ageBetweenSpousesFMList(int $total = 10): string
13878add1155SRico Sonntag    {
13888add1155SRico Sonntag        $records = $this->ageBetweenSpousesQuery('ASC', $total);
13898add1155SRico Sonntag
1390c0112ce8SGreg Roach        return view('statistics/families/top10-list-spouses', [
13918add1155SRico Sonntag            'records' => $records,
1392c0112ce8SGreg Roach        ]);
13938add1155SRico Sonntag    }
13948add1155SRico Sonntag
13958add1155SRico Sonntag    /**
13968add1155SRico Sonntag     * General query on ages at marriage.
13978add1155SRico Sonntag     *
1398afa8d404SGreg Roach     * @param string $sex "M" or "F"
13998add1155SRico Sonntag     * @param int    $year1
14008add1155SRico Sonntag     * @param int    $year2
14018add1155SRico Sonntag     *
140276d39c55SGreg Roach     * @return array<stdClass>
14038add1155SRico Sonntag     */
140424f2a3afSGreg Roach    public function statsMarrAgeQuery(string $sex, int $year1 = -1, int $year2 = -1): array
14058add1155SRico Sonntag    {
1406afa8d404SGreg Roach        $prefix = DB::connection()->getTablePrefix();
1407afa8d404SGreg Roach
1408afa8d404SGreg Roach        $query = DB::table('dates AS married')
14090b5fd0a6SGreg Roach            ->join('families', static function (JoinClause $join): void {
1410afa8d404SGreg Roach                $join
1411afa8d404SGreg Roach                    ->on('f_file', '=', 'married.d_file')
1412afa8d404SGreg Roach                    ->on('f_id', '=', 'married.d_gid');
1413afa8d404SGreg Roach            })
14140b5fd0a6SGreg Roach            ->join('dates AS birth', static function (JoinClause $join) use ($sex): void {
1415afa8d404SGreg Roach                $join
1416afa8d404SGreg Roach                    ->on('birth.d_file', '=', 'married.d_file')
1417afa8d404SGreg Roach                    ->on('birth.d_gid', '=', $sex === 'M' ? 'f_husb' : 'f_wife')
1418afa8d404SGreg Roach                    ->where('birth.d_julianday1', '<>', 0)
1419afa8d404SGreg Roach                    ->where('birth.d_fact', '=', 'BIRT')
1420afa8d404SGreg Roach                    ->whereIn('birth.d_type', ['@#DGREGORIAN@', '@#DJULIAN@']);
1421afa8d404SGreg Roach            })
1422afa8d404SGreg Roach            ->where('married.d_file', '=', $this->tree->id())
1423afa8d404SGreg Roach            ->where('married.d_fact', '=', 'MARR')
1424afa8d404SGreg Roach            ->whereIn('married.d_type', ['@#DGREGORIAN@', '@#DJULIAN@'])
1425afa8d404SGreg Roach            ->whereColumn('married.d_julianday1', '>', 'birth.d_julianday1')
1426a69f5655SGreg Roach            ->select(['f_id', 'birth.d_gid', new Expression($prefix . 'married.d_julianday2 - ' . $prefix . 'birth.d_julianday1 AS age')]);
1427afa8d404SGreg Roach
14288add1155SRico Sonntag        if ($year1 >= 0 && $year2 >= 0) {
1429afa8d404SGreg Roach            $query->whereBetween('married.d_year', [$year1, $year2]);
14308add1155SRico Sonntag        }
14318add1155SRico Sonntag
1432afa8d404SGreg Roach        return $query
1433afa8d404SGreg Roach            ->get()
1434bfed30e4SGreg Roach            ->map(static function (stdClass $row): stdClass {
14358add1155SRico Sonntag                $row->age = (int) $row->age;
14368add1155SRico Sonntag
1437afa8d404SGreg Roach                return $row;
1438afa8d404SGreg Roach            })
1439afa8d404SGreg Roach            ->all();
14408add1155SRico Sonntag    }
14418add1155SRico Sonntag
14428add1155SRico Sonntag    /**
14438add1155SRico Sonntag     * General query on marriage ages.
14448add1155SRico Sonntag     *
14458add1155SRico Sonntag     * @return string
14468add1155SRico Sonntag     */
144788de55fdSRico Sonntag    public function statsMarrAge(): string
14488add1155SRico Sonntag    {
1449f78da678SGreg Roach        return (new ChartMarriageAge($this->century_service, $this->tree))
145088de55fdSRico Sonntag            ->chartMarriageAge();
14518add1155SRico Sonntag    }
14528add1155SRico Sonntag
14538add1155SRico Sonntag    /**
14548add1155SRico Sonntag     * Query the database for marriage tags.
14558add1155SRico Sonntag     *
14562c928961SGreg Roach     * @param string $type       "full", "name" or "age"
14572c928961SGreg Roach     * @param string $age_dir    "ASC" or "DESC"
14582c928961SGreg Roach     * @param string $sex        "F" or "M"
14598add1155SRico Sonntag     * @param bool   $show_years
14608add1155SRico Sonntag     *
14618add1155SRico Sonntag     * @return string
14628add1155SRico Sonntag     */
14638add1155SRico Sonntag    private function marriageQuery(string $type, string $age_dir, string $sex, bool $show_years): string
14648add1155SRico Sonntag    {
14658add1155SRico Sonntag        if ($sex === 'F') {
14668add1155SRico Sonntag            $sex_field = 'f_wife';
14678add1155SRico Sonntag        } else {
14688add1155SRico Sonntag            $sex_field = 'f_husb';
14698add1155SRico Sonntag        }
14708add1155SRico Sonntag
14718add1155SRico Sonntag        if ($age_dir !== 'ASC') {
14728add1155SRico Sonntag            $age_dir = 'DESC';
14738add1155SRico Sonntag        }
14748add1155SRico Sonntag
14752c928961SGreg Roach        $prefix = DB::connection()->getTablePrefix();
14768add1155SRico Sonntag
14772c928961SGreg Roach        $row = DB::table('families')
14780b5fd0a6SGreg Roach            ->join('dates AS married', static function (JoinClause $join): void {
14792c928961SGreg Roach                $join
14802c928961SGreg Roach                    ->on('married.d_file', '=', 'f_file')
14812c928961SGreg Roach                    ->on('married.d_gid', '=', 'f_id')
14822c928961SGreg Roach                    ->where('married.d_fact', '=', 'MARR');
14832c928961SGreg Roach            })
14840b5fd0a6SGreg Roach            ->join('individuals', static function (JoinClause $join) use ($sex, $sex_field): void {
14852c928961SGreg Roach                $join
14862c928961SGreg Roach                    ->on('i_file', '=', 'f_file')
14872c928961SGreg Roach                    ->on('i_id', '=', $sex_field)
14882c928961SGreg Roach                    ->where('i_sex', '=', $sex);
14892c928961SGreg Roach            })
14900b5fd0a6SGreg Roach            ->join('dates AS birth', static function (JoinClause $join): void {
14912c928961SGreg Roach                $join
14922c928961SGreg Roach                    ->on('birth.d_file', '=', 'i_file')
14932c928961SGreg Roach                    ->on('birth.d_gid', '=', 'i_id')
14942c928961SGreg Roach                    ->where('birth.d_fact', '=', 'BIRT')
14952c928961SGreg Roach                    ->where('birth.d_julianday1', '<>', 0);
14962c928961SGreg Roach            })
14972c928961SGreg Roach            ->where('f_file', '=', $this->tree->id())
14982c928961SGreg Roach            ->where('married.d_julianday2', '>', 'birth.d_julianday1')
1499a69f5655SGreg Roach            ->orderBy(new Expression($prefix . 'married.d_julianday2 - ' . $prefix . 'birth.d_julianday1'), $age_dir)
1500a69f5655SGreg Roach            ->select(['f_id AS famid', $sex_field, new Expression($prefix . 'married.d_julianday2 - ' . $prefix . 'birth.d_julianday1 AS age'), 'i_id'])
15012c928961SGreg Roach            ->take(1)
15022c928961SGreg Roach            ->get()
15032c928961SGreg Roach            ->first();
15042c928961SGreg Roach
15052c928961SGreg Roach        if ($row === null) {
1506bd055353SGreg Roach            return I18N::translate('This information is not available.');
15078add1155SRico Sonntag        }
15088add1155SRico Sonntag
15096b9cb339SGreg Roach        $family = Registry::familyFactory()->make($row->famid, $this->tree);
15106b9cb339SGreg Roach        $person = Registry::individualFactory()->make($row->i_id, $this->tree);
15118add1155SRico Sonntag
15128add1155SRico Sonntag        switch ($type) {
15138add1155SRico Sonntag            default:
15148add1155SRico Sonntag            case 'full':
1515f78da678SGreg Roach                if ($family !== null && $family->canShow()) {
15168add1155SRico Sonntag                    $result = $family->formatList();
15178add1155SRico Sonntag                } else {
15188add1155SRico Sonntag                    $result = I18N::translate('This information is private and cannot be shown.');
15198add1155SRico Sonntag                }
15208add1155SRico Sonntag                break;
15218add1155SRico Sonntag
15228add1155SRico Sonntag            case 'name':
152339ca88baSGreg Roach                $result = '<a href="' . e($family->url()) . '">' . $person->fullName() . '</a>';
15248add1155SRico Sonntag                break;
15258add1155SRico Sonntag
15268add1155SRico Sonntag            case 'age':
15278add1155SRico Sonntag                $age = $row->age;
15288add1155SRico Sonntag
15298add1155SRico Sonntag                if ($show_years) {
15308add1155SRico Sonntag                    $result = $this->calculateAge((int) $row->age);
15318add1155SRico Sonntag                } else {
15328add1155SRico Sonntag                    $result = I18N::number((int) ($age / 365.25));
15338add1155SRico Sonntag                }
15348add1155SRico Sonntag
15358add1155SRico Sonntag                break;
15368add1155SRico Sonntag        }
15378add1155SRico Sonntag
15388add1155SRico Sonntag        return $result;
15398add1155SRico Sonntag    }
15408add1155SRico Sonntag
15418add1155SRico Sonntag    /**
15428add1155SRico Sonntag     * Find the youngest wife.
15438add1155SRico Sonntag     *
15448add1155SRico Sonntag     * @return string
15458add1155SRico Sonntag     */
15468add1155SRico Sonntag    public function youngestMarriageFemale(): string
15478add1155SRico Sonntag    {
15488add1155SRico Sonntag        return $this->marriageQuery('full', 'ASC', 'F', false);
15498add1155SRico Sonntag    }
15508add1155SRico Sonntag
15518add1155SRico Sonntag    /**
15528add1155SRico Sonntag     * Find the name of the youngest wife.
15538add1155SRico Sonntag     *
15548add1155SRico Sonntag     * @return string
15558add1155SRico Sonntag     */
15568add1155SRico Sonntag    public function youngestMarriageFemaleName(): string
15578add1155SRico Sonntag    {
15588add1155SRico Sonntag        return $this->marriageQuery('name', 'ASC', 'F', false);
15598add1155SRico Sonntag    }
15608add1155SRico Sonntag
15618add1155SRico Sonntag    /**
15628add1155SRico Sonntag     * Find the age of the youngest wife.
15638add1155SRico Sonntag     *
15648add1155SRico Sonntag     * @param string $show_years
15658add1155SRico Sonntag     *
15668add1155SRico Sonntag     * @return string
15678add1155SRico Sonntag     */
15688add1155SRico Sonntag    public function youngestMarriageFemaleAge(string $show_years = ''): string
15698add1155SRico Sonntag    {
15708add1155SRico Sonntag        return $this->marriageQuery('age', 'ASC', 'F', (bool) $show_years);
15718add1155SRico Sonntag    }
15728add1155SRico Sonntag
15738add1155SRico Sonntag    /**
15748add1155SRico Sonntag     * Find the oldest wife.
15758add1155SRico Sonntag     *
15768add1155SRico Sonntag     * @return string
15778add1155SRico Sonntag     */
15788add1155SRico Sonntag    public function oldestMarriageFemale(): string
15798add1155SRico Sonntag    {
15808add1155SRico Sonntag        return $this->marriageQuery('full', 'DESC', 'F', false);
15818add1155SRico Sonntag    }
15828add1155SRico Sonntag
15838add1155SRico Sonntag    /**
15848add1155SRico Sonntag     * Find the name of the oldest wife.
15858add1155SRico Sonntag     *
15868add1155SRico Sonntag     * @return string
15878add1155SRico Sonntag     */
15888add1155SRico Sonntag    public function oldestMarriageFemaleName(): string
15898add1155SRico Sonntag    {
15908add1155SRico Sonntag        return $this->marriageQuery('name', 'DESC', 'F', false);
15918add1155SRico Sonntag    }
15928add1155SRico Sonntag
15938add1155SRico Sonntag    /**
15948add1155SRico Sonntag     * Find the age of the oldest wife.
15958add1155SRico Sonntag     *
15968add1155SRico Sonntag     * @param string $show_years
15978add1155SRico Sonntag     *
15988add1155SRico Sonntag     * @return string
15998add1155SRico Sonntag     */
16008add1155SRico Sonntag    public function oldestMarriageFemaleAge(string $show_years = ''): string
16018add1155SRico Sonntag    {
16028add1155SRico Sonntag        return $this->marriageQuery('age', 'DESC', 'F', (bool) $show_years);
16038add1155SRico Sonntag    }
16048add1155SRico Sonntag
16058add1155SRico Sonntag    /**
16068add1155SRico Sonntag     * Find the youngest husband.
16078add1155SRico Sonntag     *
16088add1155SRico Sonntag     * @return string
16098add1155SRico Sonntag     */
16108add1155SRico Sonntag    public function youngestMarriageMale(): string
16118add1155SRico Sonntag    {
16128add1155SRico Sonntag        return $this->marriageQuery('full', 'ASC', 'M', false);
16138add1155SRico Sonntag    }
16148add1155SRico Sonntag
16158add1155SRico Sonntag    /**
16168add1155SRico Sonntag     * Find the name of the youngest husband.
16178add1155SRico Sonntag     *
16188add1155SRico Sonntag     * @return string
16198add1155SRico Sonntag     */
16208add1155SRico Sonntag    public function youngestMarriageMaleName(): string
16218add1155SRico Sonntag    {
16228add1155SRico Sonntag        return $this->marriageQuery('name', 'ASC', 'M', false);
16238add1155SRico Sonntag    }
16248add1155SRico Sonntag
16258add1155SRico Sonntag    /**
16268add1155SRico Sonntag     * Find the age of the youngest husband.
16278add1155SRico Sonntag     *
16288add1155SRico Sonntag     * @param string $show_years
16298add1155SRico Sonntag     *
16308add1155SRico Sonntag     * @return string
16318add1155SRico Sonntag     */
16328add1155SRico Sonntag    public function youngestMarriageMaleAge(string $show_years = ''): string
16338add1155SRico Sonntag    {
16348add1155SRico Sonntag        return $this->marriageQuery('age', 'ASC', 'M', (bool) $show_years);
16358add1155SRico Sonntag    }
16368add1155SRico Sonntag
16378add1155SRico Sonntag    /**
16388add1155SRico Sonntag     * Find the oldest husband.
16398add1155SRico Sonntag     *
16408add1155SRico Sonntag     * @return string
16418add1155SRico Sonntag     */
16428add1155SRico Sonntag    public function oldestMarriageMale(): string
16438add1155SRico Sonntag    {
16448add1155SRico Sonntag        return $this->marriageQuery('full', 'DESC', 'M', false);
16458add1155SRico Sonntag    }
16468add1155SRico Sonntag
16478add1155SRico Sonntag    /**
16488add1155SRico Sonntag     * Find the name of the oldest husband.
16498add1155SRico Sonntag     *
16508add1155SRico Sonntag     * @return string
16518add1155SRico Sonntag     */
16528add1155SRico Sonntag    public function oldestMarriageMaleName(): string
16538add1155SRico Sonntag    {
16548add1155SRico Sonntag        return $this->marriageQuery('name', 'DESC', 'M', false);
16558add1155SRico Sonntag    }
16568add1155SRico Sonntag
16578add1155SRico Sonntag    /**
16588add1155SRico Sonntag     * Find the age of the oldest husband.
16598add1155SRico Sonntag     *
16608add1155SRico Sonntag     * @param string $show_years
16618add1155SRico Sonntag     *
16628add1155SRico Sonntag     * @return string
16638add1155SRico Sonntag     */
16648add1155SRico Sonntag    public function oldestMarriageMaleAge(string $show_years = ''): string
16658add1155SRico Sonntag    {
16668add1155SRico Sonntag        return $this->marriageQuery('age', 'DESC', 'M', (bool) $show_years);
16678add1155SRico Sonntag    }
16688add1155SRico Sonntag
16698add1155SRico Sonntag    /**
16708add1155SRico Sonntag     * General query on marriages.
16718add1155SRico Sonntag     *
16728add1155SRico Sonntag     * @param int  $year1
16738add1155SRico Sonntag     * @param int  $year2
16748add1155SRico Sonntag     *
1675e6f3d5e2SGreg Roach     * @return Builder
16768add1155SRico Sonntag     */
1677e6f3d5e2SGreg Roach    public function statsMarriageQuery(int $year1 = -1, int $year2 = -1): Builder
16788add1155SRico Sonntag    {
1679e6f3d5e2SGreg Roach        $query = DB::table('dates')
1680e6f3d5e2SGreg Roach            ->where('d_file', '=', $this->tree->id())
1681e6f3d5e2SGreg Roach            ->where('d_fact', '=', 'MARR')
1682a69f5655SGreg Roach            ->select(['d_month', new Expression('COUNT(*) AS total')])
16837f5c2944SGreg Roach            ->groupBy(['d_month']);
1684e6f3d5e2SGreg Roach
1685e6f3d5e2SGreg Roach        if ($year1 >= 0 && $year2 >= 0) {
1686e6f3d5e2SGreg Roach            $query->whereBetween('d_year', [$year1, $year2]);
1687e6f3d5e2SGreg Roach        }
1688e6f3d5e2SGreg Roach
1689e6f3d5e2SGreg Roach        return $query;
1690e6f3d5e2SGreg Roach    }
1691e6f3d5e2SGreg Roach
1692e6f3d5e2SGreg Roach    /**
1693e6f3d5e2SGreg Roach     * General query on marriages.
1694e6f3d5e2SGreg Roach     *
1695e6f3d5e2SGreg Roach     * @param int  $year1
1696e6f3d5e2SGreg Roach     * @param int  $year2
1697e6f3d5e2SGreg Roach     *
1698e6f3d5e2SGreg Roach     * @return Builder
1699e6f3d5e2SGreg Roach     */
1700e6f3d5e2SGreg Roach    public function statsFirstMarriageQuery(int $year1 = -1, int $year2 = -1): Builder
1701e6f3d5e2SGreg Roach    {
17022d7289dcSGreg Roach        $query = DB::table('families')
17030b5fd0a6SGreg Roach            ->join('dates', static function (JoinClause $join): void {
17042d7289dcSGreg Roach                $join
17052d7289dcSGreg Roach                    ->on('d_gid', '=', 'f_id')
17062d7289dcSGreg Roach                    ->on('d_file', '=', 'f_file')
17072d7289dcSGreg Roach                    ->where('d_fact', '=', 'MARR')
170818e9654eSGreg Roach                    ->whereIn('d_month', ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'])
17092d7289dcSGreg Roach                    ->where('d_julianday2', '<>', 0);
17102d7289dcSGreg Roach            })
1711dca2dd78SGreg Roach            ->where('f_file', '=', $this->tree->id());
17128add1155SRico Sonntag
17138add1155SRico Sonntag        if ($year1 >= 0 && $year2 >= 0) {
17142d7289dcSGreg Roach            $query->whereBetween('d_year', [$year1, $year2]);
17158add1155SRico Sonntag        }
17168add1155SRico Sonntag
17172d7289dcSGreg Roach        return $query
1718dca2dd78SGreg Roach            ->select(['f_husb', 'f_wife', 'd_month AS month'])
17198305ac84SGreg Roach            ->orderBy('d_julianday2');
17208add1155SRico Sonntag    }
17218add1155SRico Sonntag
17228add1155SRico Sonntag    /**
17238add1155SRico Sonntag     * General query on marriages.
17248add1155SRico Sonntag     *
17258add1155SRico Sonntag     * @param string|null $color_from
17268add1155SRico Sonntag     * @param string|null $color_to
17278add1155SRico Sonntag     *
17288add1155SRico Sonntag     * @return string
17298add1155SRico Sonntag     */
173088de55fdSRico Sonntag    public function statsMarr(string $color_from = null, string $color_to = null): string
17318add1155SRico Sonntag    {
1732f78da678SGreg Roach        return (new ChartMarriage($this->century_service, $this->color_service, $this->tree))
173388de55fdSRico Sonntag            ->chartMarriage($color_from, $color_to);
17348add1155SRico Sonntag    }
17358add1155SRico Sonntag
17368add1155SRico Sonntag    /**
17378add1155SRico Sonntag     * General divorce query.
17388add1155SRico Sonntag     *
17398add1155SRico Sonntag     * @param string|null $color_from
17408add1155SRico Sonntag     * @param string|null $color_to
17418add1155SRico Sonntag     *
17428add1155SRico Sonntag     * @return string
17438add1155SRico Sonntag     */
174488de55fdSRico Sonntag    public function statsDiv(string $color_from = null, string $color_to = null): string
17458add1155SRico Sonntag    {
1746f78da678SGreg Roach        return (new ChartDivorce($this->century_service, $this->color_service, $this->tree))
174788de55fdSRico Sonntag            ->chartDivorce($color_from, $color_to);
17488add1155SRico Sonntag    }
17498add1155SRico Sonntag}
1750