xref: /webtrees/app/Statistics/Repository/FamilyRepository.php (revision 18e9654e776d817224b6f769b1bf65d4cef974d6)
18add1155SRico Sonntag<?php
28add1155SRico Sonntag/**
38add1155SRico Sonntag * webtrees: online genealogy
4242a7862SGreg Roach * Copyright (C) 2019 webtrees development team
58add1155SRico Sonntag * This program is free software: you can redistribute it and/or modify
68add1155SRico Sonntag * it under the terms of the GNU General Public License as published by
78add1155SRico Sonntag * the Free Software Foundation, either version 3 of the License, or
88add1155SRico Sonntag * (at your option) any later version.
98add1155SRico Sonntag * This program is distributed in the hope that it will be useful,
108add1155SRico Sonntag * but WITHOUT ANY WARRANTY; without even the implied warranty of
118add1155SRico Sonntag * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
128add1155SRico Sonntag * GNU General Public License for more details.
138add1155SRico Sonntag * You should have received a copy of the GNU General Public License
148add1155SRico Sonntag * along with this program. If not, see <http://www.gnu.org/licenses/>.
158add1155SRico Sonntag */
168add1155SRico Sonntagdeclare(strict_types=1);
178add1155SRico Sonntag
188add1155SRico Sonntagnamespace Fisharebest\Webtrees\Statistics\Repository;
198add1155SRico Sonntag
208add1155SRico Sonntaguse Fisharebest\Webtrees\Family;
218add1155SRico Sonntaguse Fisharebest\Webtrees\Functions\FunctionsDate;
22d1a467e4SGreg Roachuse Fisharebest\Webtrees\GedcomRecord;
238add1155SRico Sonntaguse Fisharebest\Webtrees\I18N;
248add1155SRico Sonntaguse Fisharebest\Webtrees\Individual;
258add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartChildren;
268add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartDivorce;
278add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartFamilyLargest;
288add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartMarriage;
298add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartMarriageAge;
308add1155SRico Sonntaguse Fisharebest\Webtrees\Statistics\Google\ChartNoChildrenFamilies;
318add1155SRico Sonntaguse Fisharebest\Webtrees\Tree;
3244cdc21eSGreg Roachuse Illuminate\Database\Capsule\Manager as DB;
332d7289dcSGreg Roachuse Illuminate\Database\Query\Builder;
34d1a467e4SGreg Roachuse Illuminate\Database\Query\JoinClause;
358add1155SRico Sonntaguse stdClass;
368add1155SRico Sonntag
378add1155SRico Sonntag/**
388add1155SRico Sonntag *
398add1155SRico Sonntag */
408add1155SRico Sonntagclass FamilyRepository
418add1155SRico Sonntag{
428add1155SRico Sonntag    /**
438add1155SRico Sonntag     * @var Tree
448add1155SRico Sonntag     */
458add1155SRico Sonntag    private $tree;
468add1155SRico Sonntag
478add1155SRico Sonntag    /**
488add1155SRico Sonntag     * Constructor.
498add1155SRico Sonntag     *
508add1155SRico Sonntag     * @param Tree $tree
518add1155SRico Sonntag     */
528add1155SRico Sonntag    public function __construct(Tree $tree)
538add1155SRico Sonntag    {
548add1155SRico Sonntag        $this->tree = $tree;
558add1155SRico Sonntag    }
568add1155SRico Sonntag
578add1155SRico Sonntag    /**
588add1155SRico Sonntag     * General query on family.
598add1155SRico Sonntag     *
608add1155SRico Sonntag     * @param string $type
618add1155SRico Sonntag     *
628add1155SRico Sonntag     * @return string
638add1155SRico Sonntag     */
648add1155SRico Sonntag    private function familyQuery(string $type): string
658add1155SRico Sonntag    {
6644cdc21eSGreg Roach        $row = DB::table('families')
6744cdc21eSGreg Roach            ->where('f_file', '=', $this->tree->id())
6844cdc21eSGreg Roach            ->orderBy('f_numchil', 'desc')
6944cdc21eSGreg Roach            ->first();
708add1155SRico Sonntag
7144cdc21eSGreg Roach        if ($row === null) {
728add1155SRico Sonntag            return '';
738add1155SRico Sonntag        }
748add1155SRico Sonntag
7544cdc21eSGreg Roach        /** @var Family $family */
7644cdc21eSGreg Roach        $family = Family::rowMapper()($row);
778add1155SRico Sonntag
7844cdc21eSGreg Roach        if (!$family->canShow()) {
7944cdc21eSGreg Roach            return I18N::translate('This information is private and cannot be shown.');
808add1155SRico Sonntag        }
818add1155SRico Sonntag
828add1155SRico Sonntag        switch ($type) {
838add1155SRico Sonntag            default:
848add1155SRico Sonntag            case 'full':
8544cdc21eSGreg Roach                return $family->formatList();
868add1155SRico Sonntag
8744cdc21eSGreg Roach            case 'size':
8844cdc21eSGreg Roach                return I18N::number((int) $row->f_numchil);
8944cdc21eSGreg Roach
9044cdc21eSGreg Roach            case 'name':
9139ca88baSGreg Roach                return '<a href="' . e($family->url()) . '">' . $family->fullName() . '</a>';
9244cdc21eSGreg Roach        }
938add1155SRico Sonntag    }
948add1155SRico Sonntag
958add1155SRico Sonntag    /**
968add1155SRico Sonntag     * Find the family with the most children.
978add1155SRico Sonntag     *
988add1155SRico Sonntag     * @return string
998add1155SRico Sonntag     */
1008add1155SRico Sonntag    public function largestFamily(): string
1018add1155SRico Sonntag    {
1028add1155SRico Sonntag        return $this->familyQuery('full');
1038add1155SRico Sonntag    }
1048add1155SRico Sonntag
1058add1155SRico Sonntag    /**
1068add1155SRico Sonntag     * Find the number of children in the largest family.
1078add1155SRico Sonntag     *
1088add1155SRico Sonntag     * @return string
1098add1155SRico Sonntag     */
1108add1155SRico Sonntag    public function largestFamilySize(): string
1118add1155SRico Sonntag    {
1128add1155SRico Sonntag        return $this->familyQuery('size');
1138add1155SRico Sonntag    }
1148add1155SRico Sonntag
1158add1155SRico Sonntag    /**
1168add1155SRico Sonntag     * Find the family with the most children.
1178add1155SRico Sonntag     *
1188add1155SRico Sonntag     * @return string
1198add1155SRico Sonntag     */
1208add1155SRico Sonntag    public function largestFamilyName(): string
1218add1155SRico Sonntag    {
1228add1155SRico Sonntag        return $this->familyQuery('name');
1238add1155SRico Sonntag    }
1248add1155SRico Sonntag
1258add1155SRico Sonntag    /**
1268add1155SRico Sonntag     * Find the couple with the most grandchildren.
1278add1155SRico Sonntag     *
1288add1155SRico Sonntag     * @param int $total
1298add1155SRico Sonntag     *
1308add1155SRico Sonntag     * @return array
1318add1155SRico Sonntag     */
1328add1155SRico Sonntag    private function topTenGrandFamilyQuery(int $total): array
1338add1155SRico Sonntag    {
1342d7289dcSGreg Roach        return DB::table('families')
1352b7831a1SGreg Roach            ->join('link AS children', function (JoinClause $join): void {
1362d7289dcSGreg Roach                $join
1372d7289dcSGreg Roach                    ->on('children.l_from', '=', 'f_id')
1382d7289dcSGreg Roach                    ->on('children.l_file', '=', 'f_file')
1392d7289dcSGreg Roach                    ->where('children.l_type', '=', 'CHIL');
1402b7831a1SGreg Roach            })->join('link AS mchildren', function (JoinClause $join): void {
1412d7289dcSGreg Roach                $join
1422d7289dcSGreg Roach                    ->on('mchildren.l_file', '=', 'children.l_file')
1432d7289dcSGreg Roach                    ->on('mchildren.l_from', '=', 'children.l_to')
1442d7289dcSGreg Roach                    ->where('mchildren.l_type', '=', 'FAMS');
1452b7831a1SGreg Roach            })->join('link AS gchildren', function (JoinClause $join): void {
1462d7289dcSGreg Roach                $join
1472d7289dcSGreg Roach                    ->on('gchildren.l_file', '=', 'mchildren.l_file')
1482d7289dcSGreg Roach                    ->on('gchildren.l_from', '=', 'mchildren.l_to')
1492d7289dcSGreg Roach                    ->where('gchildren.l_type', '=', 'CHIL');
1502d7289dcSGreg Roach            })
1512d7289dcSGreg Roach            ->where('f_file', '=', $this->tree->id())
1522d7289dcSGreg Roach            ->groupBy(['f_id', 'f_file'])
1532d7289dcSGreg Roach            ->orderBy(DB::raw('COUNT(*)'), 'DESC')
1542d7289dcSGreg Roach            ->select('families.*')
1552d7289dcSGreg Roach            ->limit($total)
1562d7289dcSGreg Roach            ->get()
1572d7289dcSGreg Roach            ->map(Family::rowMapper())
1582d7289dcSGreg Roach            ->filter(GedcomRecord::accessFilter())
1592d7289dcSGreg Roach            ->map(function (Family $family): array {
1602d7289dcSGreg Roach                $count = 0;
1612d7289dcSGreg Roach                foreach ($family->children() as $child) {
1622d7289dcSGreg Roach                    foreach ($child->spouseFamilies() as $spouse_family) {
1632d7289dcSGreg Roach                        $count += $spouse_family->children()->count();
1642d7289dcSGreg Roach                    }
1658add1155SRico Sonntag                }
1668add1155SRico Sonntag
1672d7289dcSGreg Roach                return [
1688add1155SRico Sonntag                    'family' => $family,
1692d7289dcSGreg Roach                    'count'  => $count,
1708add1155SRico Sonntag                ];
1712d7289dcSGreg Roach            })
1722d7289dcSGreg Roach            ->all();
1738add1155SRico Sonntag    }
1748add1155SRico Sonntag
1758add1155SRico Sonntag    /**
1768add1155SRico Sonntag     * Find the couple with the most grandchildren.
1778add1155SRico Sonntag     *
1788add1155SRico Sonntag     * @param int $total
1798add1155SRico Sonntag     *
1808add1155SRico Sonntag     * @return string
1818add1155SRico Sonntag     */
1828add1155SRico Sonntag    public function topTenLargestGrandFamily(int $total = 10): string
1838add1155SRico Sonntag    {
1842d7289dcSGreg Roach        return view('statistics/families/top10-nolist-grand', [
1852d7289dcSGreg Roach            'records' => $this->topTenGrandFamilyQuery($total),
1862d7289dcSGreg Roach        ]);
1878add1155SRico Sonntag    }
1888add1155SRico Sonntag
1898add1155SRico Sonntag    /**
1908add1155SRico Sonntag     * Find the couple with the most grandchildren.
1918add1155SRico Sonntag     *
1928add1155SRico Sonntag     * @param int $total
1938add1155SRico Sonntag     *
1948add1155SRico Sonntag     * @return string
1958add1155SRico Sonntag     */
1968add1155SRico Sonntag    public function topTenLargestGrandFamilyList(int $total = 10): string
1978add1155SRico Sonntag    {
1982d7289dcSGreg Roach        return view('statistics/families/top10-list-grand', [
1992d7289dcSGreg Roach            'records' => $this->topTenGrandFamilyQuery($total),
2002d7289dcSGreg Roach        ]);
2018add1155SRico Sonntag    }
2028add1155SRico Sonntag
2038add1155SRico Sonntag    /**
2048add1155SRico Sonntag     * Find the families with no children.
2058add1155SRico Sonntag     *
2068add1155SRico Sonntag     * @return int
2078add1155SRico Sonntag     */
2088add1155SRico Sonntag    private function noChildrenFamiliesQuery(): int
2098add1155SRico Sonntag    {
2102d7289dcSGreg Roach        return DB::table('families')
2112d7289dcSGreg Roach            ->where('f_file', '=', $this->tree->id())
2122d7289dcSGreg Roach            ->where('f_numchil', '=', 0)
2132d7289dcSGreg Roach            ->count();
2148add1155SRico Sonntag    }
2158add1155SRico Sonntag
2168add1155SRico Sonntag    /**
2178add1155SRico Sonntag     * Find the families with no children.
2188add1155SRico Sonntag     *
2198add1155SRico Sonntag     * @return string
2208add1155SRico Sonntag     */
2218add1155SRico Sonntag    public function noChildrenFamilies(): string
2228add1155SRico Sonntag    {
2238add1155SRico Sonntag        return I18N::number($this->noChildrenFamiliesQuery());
2248add1155SRico Sonntag    }
2258add1155SRico Sonntag
2268add1155SRico Sonntag    /**
2278add1155SRico Sonntag     * Find the families with no children.
2288add1155SRico Sonntag     *
2298add1155SRico Sonntag     * @param string $type
2308add1155SRico Sonntag     *
2318add1155SRico Sonntag     * @return string
2328add1155SRico Sonntag     */
2338add1155SRico Sonntag    public function noChildrenFamiliesList($type = 'list'): string
2348add1155SRico Sonntag    {
2352c928961SGreg Roach        $families = DB::table('families')
2362c928961SGreg Roach            ->where('f_file', '=', $this->tree->id())
2372c928961SGreg Roach            ->where('f_numchil', '=', 0)
2382c928961SGreg Roach            ->get()
2392c928961SGreg Roach            ->map(Family::rowMapper())
2402c928961SGreg Roach            ->filter(GedcomRecord::accessFilter());
2418add1155SRico Sonntag
2428add1155SRico Sonntag        $top10 = [];
2432c928961SGreg Roach
2443413ec75SRico Sonntag        /** @var Family $family */
2452c928961SGreg Roach        foreach ($families as $family) {
2468add1155SRico Sonntag            if ($type === 'list') {
24739ca88baSGreg Roach                $top10[] = '<li><a href="' . e($family->url()) . '">' . $family->fullName() . '</a></li>';
2488add1155SRico Sonntag            } else {
24939ca88baSGreg Roach                $top10[] = '<a href="' . e($family->url()) . '">' . $family->fullName() . '</a>';
2508add1155SRico Sonntag            }
2518add1155SRico Sonntag        }
2528add1155SRico Sonntag
2538add1155SRico Sonntag        if ($type === 'list') {
2548add1155SRico Sonntag            $top10 = implode('', $top10);
2558add1155SRico Sonntag        } else {
2568add1155SRico Sonntag            $top10 = implode('; ', $top10);
2578add1155SRico Sonntag        }
2588add1155SRico Sonntag
2592c928961SGreg Roach
2608add1155SRico Sonntag        if ($type === 'list') {
2618add1155SRico Sonntag            return '<ul>' . $top10 . '</ul>';
2628add1155SRico Sonntag        }
2638add1155SRico Sonntag
2648add1155SRico Sonntag        return $top10;
2658add1155SRico Sonntag    }
2668add1155SRico Sonntag
2678add1155SRico Sonntag    /**
2688add1155SRico Sonntag     * Create a chart of children with no families.
2698add1155SRico Sonntag     *
2708add1155SRico Sonntag     * @param int $year1
2718add1155SRico Sonntag     * @param int $year2
2728add1155SRico Sonntag     *
2738add1155SRico Sonntag     * @return string
2748add1155SRico Sonntag     */
27588de55fdSRico Sonntag    public function chartNoChildrenFamilies(int $year1 = -1, int $year2 = -1): string
2768add1155SRico Sonntag    {
2778add1155SRico Sonntag        $no_child_fam = $this->noChildrenFamiliesQuery();
2788add1155SRico Sonntag
2798add1155SRico Sonntag        return (new ChartNoChildrenFamilies($this->tree))
28088de55fdSRico Sonntag            ->chartNoChildrenFamilies($no_child_fam, $year1, $year2);
2818add1155SRico Sonntag    }
2828add1155SRico Sonntag
2838add1155SRico Sonntag    /**
2848add1155SRico Sonntag     * Returns the ages between siblings.
2858add1155SRico Sonntag     *
2868add1155SRico Sonntag     * @param int $total The total number of records to query
2878add1155SRico Sonntag     *
2888add1155SRico Sonntag     * @return array
2898add1155SRico Sonntag     */
2908add1155SRico Sonntag    private function ageBetweenSiblingsQuery(int $total): array
2918add1155SRico Sonntag    {
292b1126ab4SGreg Roach        $prefix = DB::connection()->getTablePrefix();
2938add1155SRico Sonntag
294b1126ab4SGreg Roach        return DB::table('link AS link1')
295b1126ab4SGreg Roach            ->join('link AS link2', function (JoinClause $join): void {
296b1126ab4SGreg Roach                $join
297b1126ab4SGreg Roach                    ->on('link2.l_from', '=', 'link1.l_from')
298b1126ab4SGreg Roach                    ->on('link2.l_type', '=', 'link1.l_type')
299b1126ab4SGreg Roach                    ->on('link2.l_file', '=', 'link1.l_file');
300b1126ab4SGreg Roach            })
301b1126ab4SGreg Roach            ->join('dates AS child1', function (JoinClause $join): void {
302b1126ab4SGreg Roach                $join
303b1126ab4SGreg Roach                    ->on('child1.d_gid', '=', 'link1.l_to')
304b1126ab4SGreg Roach                    ->on('child1.d_file', '=', 'link1.l_file')
305b1126ab4SGreg Roach                    ->where('child1.d_fact', '=', 'BIRT')
306b1126ab4SGreg Roach                    ->where('child1.d_julianday1', '<>', 0);
307b1126ab4SGreg Roach            })
308b1126ab4SGreg Roach            ->join('dates AS child2', function (JoinClause $join): void {
309b1126ab4SGreg Roach                $join
310b1126ab4SGreg Roach                    ->on('child2.d_gid', '=', 'link2.l_to')
311b1126ab4SGreg Roach                    ->on('child2.d_file', '=', 'link2.l_file')
312b1126ab4SGreg Roach                    ->where('child2.d_fact', '=', 'BIRT')
313b1126ab4SGreg Roach                    ->whereColumn('child2.d_julianday2', '>', 'child1.d_julianday1');
314b1126ab4SGreg Roach            })
315b1126ab4SGreg Roach            ->where('link1.l_type', '=', 'CHIL')
316b1126ab4SGreg Roach            ->where('link1.l_file', '=', $this->tree->id())
317b1126ab4SGreg Roach            ->distinct()
318b1126ab4SGreg Roach            ->select(['link1.l_from AS family', 'link1.l_to AS ch1', 'link2.l_to AS ch2', DB::raw($prefix . 'child2.d_julianday2 - ' . $prefix . 'child1.d_julianday1 AS age')])
319b1126ab4SGreg Roach            ->orderBy('age', 'DESC')
320b1126ab4SGreg Roach            ->take($total)
321b1126ab4SGreg Roach            ->get()
322b1126ab4SGreg Roach            ->all();
3238add1155SRico Sonntag    }
3248add1155SRico Sonntag
3258add1155SRico Sonntag    /**
3268add1155SRico Sonntag     * Returns the calculated age the time of event.
3278add1155SRico Sonntag     *
3288add1155SRico Sonntag     * @param int $age The age from the database record
3298add1155SRico Sonntag     *
3308add1155SRico Sonntag     * @return string
3318add1155SRico Sonntag     */
3328add1155SRico Sonntag    private function calculateAge(int $age): string
3338add1155SRico Sonntag    {
3348add1155SRico Sonntag        if ((int) ($age / 365.25) > 0) {
3358add1155SRico Sonntag            $result = (int) ($age / 365.25) . 'y';
3368add1155SRico Sonntag        } elseif ((int) ($age / 30.4375) > 0) {
3378add1155SRico Sonntag            $result = (int) ($age / 30.4375) . 'm';
3388add1155SRico Sonntag        } else {
3398add1155SRico Sonntag            $result = $age . 'd';
3408add1155SRico Sonntag        }
3418add1155SRico Sonntag
3428add1155SRico Sonntag        return FunctionsDate::getAgeAtEvent($result);
3438add1155SRico Sonntag    }
3448add1155SRico Sonntag
3458add1155SRico Sonntag    /**
3468add1155SRico Sonntag     * Find the ages between siblings.
3478add1155SRico Sonntag     *
3488add1155SRico Sonntag     * @param int $total The total number of records to query
3498add1155SRico Sonntag     *
3508add1155SRico Sonntag     * @return array
3518add1155SRico Sonntag     * @throws \Exception
3528add1155SRico Sonntag     */
3538add1155SRico Sonntag    private function ageBetweenSiblingsNoList(int $total): array
3548add1155SRico Sonntag    {
3558add1155SRico Sonntag        $rows = $this->ageBetweenSiblingsQuery($total);
3568add1155SRico Sonntag
3578add1155SRico Sonntag        foreach ($rows as $fam) {
3588add1155SRico Sonntag            $family = Family::getInstance($fam->family, $this->tree);
3598add1155SRico Sonntag            $child1 = Individual::getInstance($fam->ch1, $this->tree);
3608add1155SRico Sonntag            $child2 = Individual::getInstance($fam->ch2, $this->tree);
3618add1155SRico Sonntag
3628add1155SRico Sonntag            if ($child1->canShow() && $child2->canShow()) {
3638add1155SRico Sonntag                // ! Single array (no list)
3648add1155SRico Sonntag                return [
3658add1155SRico Sonntag                    'child1' => $child1,
3668add1155SRico Sonntag                    'child2' => $child2,
3678add1155SRico Sonntag                    'family' => $family,
3688add1155SRico Sonntag                    'age'    => $this->calculateAge((int) $fam->age),
3698add1155SRico Sonntag                ];
3708add1155SRico Sonntag            }
3718add1155SRico Sonntag        }
3728add1155SRico Sonntag
3738add1155SRico Sonntag        return [];
3748add1155SRico Sonntag    }
3758add1155SRico Sonntag
3768add1155SRico Sonntag    /**
3778add1155SRico Sonntag     * Find the ages between siblings.
3788add1155SRico Sonntag     *
3798add1155SRico Sonntag     * @param int  $total The total number of records to query
3808add1155SRico Sonntag     * @param bool $one   Include each family only once if true
3818add1155SRico Sonntag     *
3828add1155SRico Sonntag     * @return array
3838add1155SRico Sonntag     * @throws \Exception
3848add1155SRico Sonntag     */
3858add1155SRico Sonntag    private function ageBetweenSiblingsList(int $total, bool $one): array
3868add1155SRico Sonntag    {
3878add1155SRico Sonntag        $rows  = $this->ageBetweenSiblingsQuery($total);
3888add1155SRico Sonntag        $top10 = [];
3898add1155SRico Sonntag        $dist  = [];
3908add1155SRico Sonntag
3918add1155SRico Sonntag        foreach ($rows as $fam) {
3928add1155SRico Sonntag            $family = Family::getInstance($fam->family, $this->tree);
3938add1155SRico Sonntag            $child1 = Individual::getInstance($fam->ch1, $this->tree);
3948add1155SRico Sonntag            $child2 = Individual::getInstance($fam->ch2, $this->tree);
3958add1155SRico Sonntag
3968add1155SRico Sonntag            $age = $this->calculateAge((int) $fam->age);
3978add1155SRico Sonntag
3988add1155SRico Sonntag            if ($one && !\in_array($fam->family, $dist, true)) {
3998add1155SRico Sonntag                if ($child1->canShow() && $child2->canShow()) {
4008add1155SRico Sonntag                    $top10[] = [
4018add1155SRico Sonntag                        'child1' => $child1,
4028add1155SRico Sonntag                        'child2' => $child2,
4038add1155SRico Sonntag                        'family' => $family,
4048add1155SRico Sonntag                        'age'    => $age,
4058add1155SRico Sonntag                    ];
4068add1155SRico Sonntag
4078add1155SRico Sonntag                    $dist[] = $fam->family;
4088add1155SRico Sonntag                }
4098add1155SRico Sonntag            } elseif (!$one && $child1->canShow() && $child2->canShow()) {
4108add1155SRico Sonntag                $top10[] = [
4118add1155SRico Sonntag                    'child1' => $child1,
4128add1155SRico Sonntag                    'child2' => $child2,
4138add1155SRico Sonntag                    'family' => $family,
4148add1155SRico Sonntag                    'age'    => $age,
4158add1155SRico Sonntag                ];
4168add1155SRico Sonntag            }
4178add1155SRico Sonntag        }
4188add1155SRico Sonntag
4198add1155SRico Sonntag        return $top10;
4208add1155SRico Sonntag    }
4218add1155SRico Sonntag
4228add1155SRico Sonntag    /**
4238add1155SRico Sonntag     * Find the ages between siblings.
4248add1155SRico Sonntag     *
4258add1155SRico Sonntag     * @param int $total The total number of records to query
4268add1155SRico Sonntag     *
4278add1155SRico Sonntag     * @return string
4288add1155SRico Sonntag     */
4298add1155SRico Sonntag    private function ageBetweenSiblingsAge(int $total): string
4308add1155SRico Sonntag    {
4318add1155SRico Sonntag        $rows = $this->ageBetweenSiblingsQuery($total);
4328add1155SRico Sonntag
4338add1155SRico Sonntag        foreach ($rows as $fam) {
4348add1155SRico Sonntag            return $this->calculateAge((int) $fam->age);
4358add1155SRico Sonntag        }
4368add1155SRico Sonntag
4378add1155SRico Sonntag        return '';
4388add1155SRico Sonntag    }
4398add1155SRico Sonntag
4408add1155SRico Sonntag    /**
4418add1155SRico Sonntag     * Find the ages between siblings.
4428add1155SRico Sonntag     *
4438add1155SRico Sonntag     * @param int $total The total number of records to query
4448add1155SRico Sonntag     *
4458add1155SRico Sonntag     * @return string
4468add1155SRico Sonntag     * @throws \Exception
4478add1155SRico Sonntag     */
4488add1155SRico Sonntag    private function ageBetweenSiblingsName(int $total): string
4498add1155SRico Sonntag    {
4508add1155SRico Sonntag        $rows = $this->ageBetweenSiblingsQuery($total);
4518add1155SRico Sonntag
4528add1155SRico Sonntag        foreach ($rows as $fam) {
4538add1155SRico Sonntag            $family = Family::getInstance($fam->family, $this->tree);
4548add1155SRico Sonntag            $child1 = Individual::getInstance($fam->ch1, $this->tree);
4558add1155SRico Sonntag            $child2 = Individual::getInstance($fam->ch2, $this->tree);
4568add1155SRico Sonntag
4578add1155SRico Sonntag            if ($child1->canShow() && $child2->canShow()) {
45839ca88baSGreg Roach                $return = '<a href="' . e($child2->url()) . '">' . $child2->fullName() . '</a> ';
4598add1155SRico Sonntag                $return .= I18N::translate('and') . ' ';
46039ca88baSGreg Roach                $return .= '<a href="' . e($child1->url()) . '">' . $child1->fullName() . '</a>';
4618add1155SRico Sonntag                $return .= ' <a href="' . e($family->url()) . '">[' . I18N::translate('View this family') . ']</a>';
4628add1155SRico Sonntag            } else {
4638add1155SRico Sonntag                $return = I18N::translate('This information is private and cannot be shown.');
4648add1155SRico Sonntag            }
4658add1155SRico Sonntag
4668add1155SRico Sonntag            return $return;
4678add1155SRico Sonntag        }
4688add1155SRico Sonntag
4698add1155SRico Sonntag        return '';
4708add1155SRico Sonntag    }
4718add1155SRico Sonntag
4728add1155SRico Sonntag    /**
4738add1155SRico Sonntag     * Find the names of siblings with the widest age gap.
4748add1155SRico Sonntag     *
4758add1155SRico Sonntag     * @param int $total
4768add1155SRico Sonntag     *
4778add1155SRico Sonntag     * @return string
4788add1155SRico Sonntag     */
4798add1155SRico Sonntag    public function topAgeBetweenSiblingsName(int $total = 10): string
4808add1155SRico Sonntag    {
4818add1155SRico Sonntag        return $this->ageBetweenSiblingsName($total);
4828add1155SRico Sonntag    }
4838add1155SRico Sonntag
4848add1155SRico Sonntag    /**
4858add1155SRico Sonntag     * Find the widest age gap between siblings.
4868add1155SRico Sonntag     *
4878add1155SRico Sonntag     * @param int $total
4888add1155SRico Sonntag     *
4898add1155SRico Sonntag     * @return string
4908add1155SRico Sonntag     */
4918add1155SRico Sonntag    public function topAgeBetweenSiblings(int $total = 10): string
4928add1155SRico Sonntag    {
4938add1155SRico Sonntag        return $this->ageBetweenSiblingsAge($total);
4948add1155SRico Sonntag    }
4958add1155SRico Sonntag
4968add1155SRico Sonntag    /**
4978add1155SRico Sonntag     * Find the name of siblings with the widest age gap.
4988add1155SRico Sonntag     *
4998add1155SRico Sonntag     * @param int $total
5008add1155SRico Sonntag     *
5018add1155SRico Sonntag     * @return string
5028add1155SRico Sonntag     */
5038add1155SRico Sonntag    public function topAgeBetweenSiblingsFullName(int $total = 10): string
5048add1155SRico Sonntag    {
5058add1155SRico Sonntag        $record = $this->ageBetweenSiblingsNoList($total);
5068add1155SRico Sonntag
507cb2263dcSGreg Roach        if (empty($record)) {
508dd7dd2a1SRico Sonntag            return I18N::translate('This information is not available.');
509cb2263dcSGreg Roach        }
510cb2263dcSGreg Roach
511cb2263dcSGreg Roach        return view('statistics/families/top10-nolist-age', [
5128add1155SRico Sonntag            'record' => $record,
513cb2263dcSGreg Roach        ]);
5148add1155SRico Sonntag    }
5158add1155SRico Sonntag
5168add1155SRico Sonntag    /**
5178add1155SRico Sonntag     * Find the siblings with the widest age gaps.
5188add1155SRico Sonntag     *
5198add1155SRico Sonntag     * @param int    $total
5208add1155SRico Sonntag     * @param string $one
5218add1155SRico Sonntag     *
5228add1155SRico Sonntag     * @return string
5238add1155SRico Sonntag     */
5248add1155SRico Sonntag    public function topAgeBetweenSiblingsList(int $total = 10, string $one = ''): string
5258add1155SRico Sonntag    {
5268add1155SRico Sonntag        $records = $this->ageBetweenSiblingsList($total, (bool) $one);
5278add1155SRico Sonntag
528cb2263dcSGreg Roach        return view('statistics/families/top10-list-age', [
5298add1155SRico Sonntag            'records' => $records,
530cb2263dcSGreg Roach        ]);
5318add1155SRico Sonntag    }
5328add1155SRico Sonntag
5338add1155SRico Sonntag    /**
5348add1155SRico Sonntag     * General query on familes/children.
5358add1155SRico Sonntag     *
5368add1155SRico Sonntag     * @param int    $year1
5378add1155SRico Sonntag     * @param int    $year2
5388add1155SRico Sonntag     *
5398add1155SRico Sonntag     * @return stdClass[]
5408add1155SRico Sonntag     */
541b1126ab4SGreg Roach    public function statsChildrenQuery(int $year1 = -1, int $year2 = -1): array
5428add1155SRico Sonntag    {
543b1126ab4SGreg Roach        $query = DB::table('families')
544b1126ab4SGreg Roach            ->where('f_file', '=', $this->tree->id())
545b1126ab4SGreg Roach            ->groupBy('f_numchil')
546b1126ab4SGreg Roach            ->select(['f_numchil', DB::raw('COUNT(*) AS total')]);
5478add1155SRico Sonntag
5488add1155SRico Sonntag        if ($year1 >= 0 && $year2 >= 0) {
549b1126ab4SGreg Roach            $query
550b1126ab4SGreg Roach                ->join('dates', function (JoinClause $join): void {
551b1126ab4SGreg Roach                    $join
552b1126ab4SGreg Roach                        ->on('d_file', '=', 'f_file')
553b1126ab4SGreg Roach                        ->on('d_gid', '=', 'f_id');
554b1126ab4SGreg Roach                })
555b1126ab4SGreg Roach                ->where('d_fact', '=', 'MARR')
556b1126ab4SGreg Roach                ->whereIn('d_type', ['@#DGREGORIAN@', '@#DJULIAN@'])
557b1126ab4SGreg Roach                ->whereBetween('d_year', [$year1, $year2]);
5588add1155SRico Sonntag        }
5598add1155SRico Sonntag
560b1126ab4SGreg Roach        return $query->get()->all();
5618add1155SRico Sonntag    }
5628add1155SRico Sonntag
5638add1155SRico Sonntag    /**
5648add1155SRico Sonntag     * Genearl query on families/children.
5658add1155SRico Sonntag     *
5668add1155SRico Sonntag     * @return string
5678add1155SRico Sonntag     */
56888de55fdSRico Sonntag    public function statsChildren(): string
5698add1155SRico Sonntag    {
5708add1155SRico Sonntag        return (new ChartChildren($this->tree))
57188de55fdSRico Sonntag            ->chartChildren();
5728add1155SRico Sonntag    }
5738add1155SRico Sonntag
5748add1155SRico Sonntag    /**
5758add1155SRico Sonntag     * Count the total children.
5768add1155SRico Sonntag     *
5778add1155SRico Sonntag     * @return string
5788add1155SRico Sonntag     */
5798add1155SRico Sonntag    public function totalChildren(): string
5808add1155SRico Sonntag    {
581d1a467e4SGreg Roach        $total = (int) DB::table('families')
582d1a467e4SGreg Roach            ->where('f_file', '=', $this->tree->id())
583d1a467e4SGreg Roach            ->sum('f_numchil');
5848add1155SRico Sonntag
5858add1155SRico Sonntag        return I18N::number($total);
5868add1155SRico Sonntag    }
5878add1155SRico Sonntag
5888add1155SRico Sonntag    /**
5898add1155SRico Sonntag     * Find the average number of children in families.
5908add1155SRico Sonntag     *
5918add1155SRico Sonntag     * @return string
5928add1155SRico Sonntag     */
5938add1155SRico Sonntag    public function averageChildren(): string
5948add1155SRico Sonntag    {
595d1a467e4SGreg Roach        $average = (float) DB::table('families')
596d1a467e4SGreg Roach            ->where('f_file', '=', $this->tree->id())
597d1a467e4SGreg Roach            ->avg('f_numchil');
5988add1155SRico Sonntag
5998add1155SRico Sonntag        return I18N::number($average, 2);
6008add1155SRico Sonntag    }
6018add1155SRico Sonntag
6028add1155SRico Sonntag    /**
6038add1155SRico Sonntag     * General query on families.
6048add1155SRico Sonntag     *
6058add1155SRico Sonntag     * @param int $total
6068add1155SRico Sonntag     *
6078add1155SRico Sonntag     * @return array
6088add1155SRico Sonntag     */
6098add1155SRico Sonntag    private function topTenFamilyQuery(int $total): array
6108add1155SRico Sonntag    {
6112d7289dcSGreg Roach        return DB::table('families')
6122d7289dcSGreg Roach            ->where('f_file', '=', $this->tree->id())
6132d7289dcSGreg Roach            ->orderBy('f_numchil', 'DESC')
6142d7289dcSGreg Roach            ->limit($total)
6152d7289dcSGreg Roach            ->get()
6162d7289dcSGreg Roach            ->map(Family::rowMapper())
6172d7289dcSGreg Roach            ->filter(GedcomRecord::accessFilter())
6182d7289dcSGreg Roach            ->map(function (Family $family): array {
6192d7289dcSGreg Roach                return [
6208add1155SRico Sonntag                    'family' => $family,
6212d7289dcSGreg Roach                    'count'  => $family->numberOfChildren(),
6228add1155SRico Sonntag                ];
6232d7289dcSGreg Roach            })
6242d7289dcSGreg Roach            ->all();
6258add1155SRico Sonntag    }
6268add1155SRico Sonntag
6278add1155SRico Sonntag    /**
6288add1155SRico Sonntag     * The the families with the most children.
6298add1155SRico Sonntag     *
6308add1155SRico Sonntag     * @param int $total
6318add1155SRico Sonntag     *
6328add1155SRico Sonntag     * @return string
6338add1155SRico Sonntag     */
6348add1155SRico Sonntag    public function topTenLargestFamily(int $total = 10): string
6358add1155SRico Sonntag    {
6368add1155SRico Sonntag        $records = $this->topTenFamilyQuery($total);
6378add1155SRico Sonntag
638c0112ce8SGreg Roach        return view('statistics/families/top10-nolist', [
6398add1155SRico Sonntag            'records' => $records,
640c0112ce8SGreg Roach        ]);
6418add1155SRico Sonntag    }
6428add1155SRico Sonntag
6438add1155SRico Sonntag    /**
6448add1155SRico Sonntag     * Find the families with the most children.
6458add1155SRico Sonntag     *
6468add1155SRico Sonntag     * @param int $total
6478add1155SRico Sonntag     *
6488add1155SRico Sonntag     * @return string
6498add1155SRico Sonntag     */
6508add1155SRico Sonntag    public function topTenLargestFamilyList(int $total = 10): string
6518add1155SRico Sonntag    {
6528add1155SRico Sonntag        $records = $this->topTenFamilyQuery($total);
6538add1155SRico Sonntag
654c0112ce8SGreg Roach        return view('statistics/families/top10-list', [
6558add1155SRico Sonntag            'records' => $records,
656c0112ce8SGreg Roach        ]);
6578add1155SRico Sonntag    }
6588add1155SRico Sonntag
6598add1155SRico Sonntag    /**
6608add1155SRico Sonntag     * Create a chart of the largest families.
6618add1155SRico Sonntag     *
6628add1155SRico Sonntag     * @param string|null $color_from
6638add1155SRico Sonntag     * @param string|null $color_to
6648add1155SRico Sonntag     * @param int         $total
6658add1155SRico Sonntag     *
6668add1155SRico Sonntag     * @return string
6678add1155SRico Sonntag     */
6688add1155SRico Sonntag    public function chartLargestFamilies(
6698add1155SRico Sonntag        string $color_from = null,
6708add1155SRico Sonntag        string $color_to = null,
6718add1155SRico Sonntag        int $total = 10
672e2cbf57aSGreg Roach    ): string {
6738add1155SRico Sonntag        return (new ChartFamilyLargest($this->tree))
67488de55fdSRico Sonntag            ->chartLargestFamilies($color_from, $color_to, $total);
6758add1155SRico Sonntag    }
6768add1155SRico Sonntag
6778add1155SRico Sonntag    /**
6788add1155SRico Sonntag     * Find the month in the year of the birth of the first child.
6798add1155SRico Sonntag     *
680999da590SGreg Roach     * @param int $year1
681999da590SGreg Roach     * @param int $year2
6828add1155SRico Sonntag     *
683999da590SGreg Roach     * @return Builder
6848add1155SRico Sonntag     */
685999da590SGreg Roach    public function monthFirstChildQuery(int $year1 = -1, int $year2 = -1): Builder
6868add1155SRico Sonntag    {
687b1126ab4SGreg Roach        $first_child_subquery = DB::table('link')
688b1126ab4SGreg Roach            ->join('dates', function (JoinClause $join): void {
689b1126ab4SGreg Roach                $join
690b1126ab4SGreg Roach                    ->on('d_gid', '=', 'l_to')
691b1126ab4SGreg Roach                    ->on('d_file', '=', 'l_file')
692b1126ab4SGreg Roach                    ->where('d_julianday1', '<>', 0)
693b1126ab4SGreg Roach                    ->whereIn('d_month', ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC']);
694b1126ab4SGreg Roach            })
695b1126ab4SGreg Roach            ->where('l_file', '=', $this->tree->id())
696b1126ab4SGreg Roach            ->where('l_type', '=', 'CHIL')
697b1126ab4SGreg Roach            ->select(['l_from AS family_id', DB::raw('MIN(d_julianday1) AS min_birth_jd')])
698b1126ab4SGreg Roach            ->groupBy('family_id');
699b1126ab4SGreg Roach
700b1126ab4SGreg Roach        $query = DB::table('link')
701b1126ab4SGreg Roach            ->join('dates', function (JoinClause $join): void {
702b1126ab4SGreg Roach                $join
703b1126ab4SGreg Roach                    ->on('d_gid', '=', 'l_to')
704b1126ab4SGreg Roach                    ->on('d_file', '=', 'l_file');
705b1126ab4SGreg Roach            })
706b1126ab4SGreg Roach            ->joinSub($first_child_subquery, 'subquery', function (JoinClause $join): void {
707b1126ab4SGreg Roach                $join
708b1126ab4SGreg Roach                    ->on('family_id', '=', 'l_from')
709b1126ab4SGreg Roach                    ->on('min_birth_jd', '=', 'd_julianday1');
710b1126ab4SGreg Roach            })
711b1126ab4SGreg Roach            ->where('link.l_file', '=', $this->tree->id())
712999da590SGreg Roach            ->where('link.l_type', '=', 'CHIL')
713999da590SGreg Roach            ->select(['d_month', DB::raw('COUNT(*) AS total')])
714999da590SGreg Roach            ->groupBy(['d_month']);
715b1126ab4SGreg Roach
716999da590SGreg Roach        if ($year1 >= 0 && $year2 >= 0) {
717999da590SGreg Roach            $query->whereBetween('d_year', [$year1, $year2]);
718999da590SGreg Roach        }
719b1126ab4SGreg Roach
720999da590SGreg Roach        return $query;
721999da590SGreg Roach    }
722999da590SGreg Roach
723999da590SGreg Roach    /**
724999da590SGreg Roach     * Find the month in the year of the birth of the first child.
725999da590SGreg Roach     *
726999da590SGreg Roach     * @param int $year1
727999da590SGreg Roach     * @param int $year2
728999da590SGreg Roach     *
729999da590SGreg Roach     * @return Builder
730999da590SGreg Roach     */
731999da590SGreg Roach    public function monthFirstChildBySexQuery(int $year1 = -1, int $year2 = -1): Builder
732999da590SGreg Roach    {
733999da590SGreg Roach        return $this->monthFirstChildQuery($year1, $year2)
734999da590SGreg Roach                ->join('individuals', function (JoinClause $join): void {
735b1126ab4SGreg Roach                    $join
736b1126ab4SGreg Roach                        ->on('i_file', '=', 'l_file')
737b1126ab4SGreg Roach                        ->on('i_id', '=', 'l_to');
738b1126ab4SGreg Roach                })
739b1126ab4SGreg Roach                ->select(['d_month', 'i_sex', DB::raw('COUNT(*) AS total')])
740b1126ab4SGreg Roach                ->groupBy(['d_month', 'i_sex']);
7418add1155SRico Sonntag    }
7428add1155SRico Sonntag
7438add1155SRico Sonntag    /**
7448add1155SRico Sonntag     * Number of husbands.
7458add1155SRico Sonntag     *
7468add1155SRico Sonntag     * @return string
7478add1155SRico Sonntag     */
7488add1155SRico Sonntag    public function totalMarriedMales(): string
7498add1155SRico Sonntag    {
750d1a467e4SGreg Roach        $n = (int) DB::table('families')
751d1a467e4SGreg Roach            ->where('f_file', '=', $this->tree->id())
752d1a467e4SGreg Roach            ->where('f_gedcom', 'LIKE', "%\n1 MARR%")
753d1a467e4SGreg Roach            ->distinct()
754d1a467e4SGreg Roach            ->count('f_husb');
7558add1155SRico Sonntag
7568add1155SRico Sonntag        return I18N::number($n);
7578add1155SRico Sonntag    }
7588add1155SRico Sonntag
7598add1155SRico Sonntag    /**
7608add1155SRico Sonntag     * Number of wives.
7618add1155SRico Sonntag     *
7628add1155SRico Sonntag     * @return string
7638add1155SRico Sonntag     */
7648add1155SRico Sonntag    public function totalMarriedFemales(): string
7658add1155SRico Sonntag    {
766d1a467e4SGreg Roach        $n = (int) DB::table('families')
767d1a467e4SGreg Roach            ->where('f_file', '=', $this->tree->id())
768d1a467e4SGreg Roach            ->where('f_gedcom', 'LIKE', "%\n1 MARR%")
769d1a467e4SGreg Roach            ->distinct()
770d1a467e4SGreg Roach            ->count('f_wife');
7718add1155SRico Sonntag
7728add1155SRico Sonntag        return I18N::number($n);
7738add1155SRico Sonntag    }
7748add1155SRico Sonntag
7758add1155SRico Sonntag    /**
7768add1155SRico Sonntag     * General query on parents.
7778add1155SRico Sonntag     *
7788add1155SRico Sonntag     * @param string $type
7798add1155SRico Sonntag     * @param string $age_dir
7808add1155SRico Sonntag     * @param string $sex
7818add1155SRico Sonntag     * @param bool   $show_years
7828add1155SRico Sonntag     *
7838add1155SRico Sonntag     * @return string
7848add1155SRico Sonntag     */
7858add1155SRico Sonntag    private function parentsQuery(string $type, string $age_dir, string $sex, bool $show_years): string
7868add1155SRico Sonntag    {
7878add1155SRico Sonntag        if ($sex === 'F') {
7888add1155SRico Sonntag            $sex_field = 'WIFE';
7898add1155SRico Sonntag        } else {
7908add1155SRico Sonntag            $sex_field = 'HUSB';
7918add1155SRico Sonntag        }
7928add1155SRico Sonntag
7938add1155SRico Sonntag        if ($age_dir !== 'ASC') {
7948add1155SRico Sonntag            $age_dir = 'DESC';
7958add1155SRico Sonntag        }
7968add1155SRico Sonntag
7972c928961SGreg Roach        $prefix = DB::connection()->getTablePrefix();
7988add1155SRico Sonntag
7992c928961SGreg Roach        $row = DB::table('link AS parentfamily')
8002c928961SGreg Roach            ->join('link AS childfamily', function (JoinClause $join): void {
8012c928961SGreg Roach                $join
8022c928961SGreg Roach                    ->on('childfamily.l_file', '=', 'parentfamily.l_file')
8032c928961SGreg Roach                    ->on('childfamily.l_from', '=', 'parentfamily.l_from')
8042c928961SGreg Roach                    ->where('childfamily.l_type', '=', 'CHIL');
8052c928961SGreg Roach            })
8062c928961SGreg Roach            ->join('dates AS birth', function (JoinClause $join): void {
8072c928961SGreg Roach                $join
8082c928961SGreg Roach                    ->on('birth.d_file', '=', 'parentfamily.l_file')
8092c928961SGreg Roach                    ->on('birth.d_gid', '=', 'parentfamily.l_to')
8102c928961SGreg Roach                    ->where('birth.d_fact', '=', 'BIRT')
8112c928961SGreg Roach                    ->where('birth.d_julianday1', '<>', 0);
8122c928961SGreg Roach            })
8132c928961SGreg Roach            ->join('dates AS childbirth', function (JoinClause $join): void {
8142c928961SGreg Roach                $join
8152c928961SGreg Roach                    ->on('childbirth.d_file', '=', 'parentfamily.l_file')
8163413ec75SRico Sonntag                    ->on('childbirth.d_gid', '=', 'childfamily.l_to')
8173413ec75SRico Sonntag                    ->where('childbirth.d_fact', '=', 'BIRT');
8182c928961SGreg Roach            })
8192c928961SGreg Roach            ->where('childfamily.l_file', '=', $this->tree->id())
8202c928961SGreg Roach            ->where('parentfamily.l_type', '=', $sex_field)
8212c928961SGreg Roach            ->where('childbirth.d_julianday2', '>', 'birth.d_julianday1')
8222c928961SGreg Roach            ->select(['parentfamily.l_to AS id', DB::raw($prefix . 'childbirth.d_julianday2 - ' . $prefix . 'birth.d_julianday1 AS age')])
8232c928961SGreg Roach            ->take(1)
8242c928961SGreg Roach            ->orderBy('age', $age_dir)
8252c928961SGreg Roach            ->get()
8262c928961SGreg Roach            ->first();
8272c928961SGreg Roach
8282c928961SGreg Roach        if ($row === null) {
8298add1155SRico Sonntag            return '';
8308add1155SRico Sonntag        }
8318add1155SRico Sonntag
8328add1155SRico Sonntag        $person = Individual::getInstance($row->id, $this->tree);
8338add1155SRico Sonntag
8348add1155SRico Sonntag        switch ($type) {
8358add1155SRico Sonntag            default:
8368add1155SRico Sonntag            case 'full':
8378add1155SRico Sonntag                if ($person && $person->canShow()) {
8388add1155SRico Sonntag                    $result = $person->formatList();
8398add1155SRico Sonntag                } else {
8408add1155SRico Sonntag                    $result = I18N::translate('This information is private and cannot be shown.');
8418add1155SRico Sonntag                }
8428add1155SRico Sonntag                break;
8438add1155SRico Sonntag
8448add1155SRico Sonntag            case 'name':
84539ca88baSGreg Roach                $result = '<a href="' . e($person->url()) . '">' . $person->fullName() . '</a>';
8468add1155SRico Sonntag                break;
8478add1155SRico Sonntag
8488add1155SRico Sonntag            case 'age':
8498add1155SRico Sonntag                $age = $row->age;
8508add1155SRico Sonntag
8518add1155SRico Sonntag                if ($show_years) {
8528add1155SRico Sonntag                    $result = $this->calculateAge((int) $row->age);
8538add1155SRico Sonntag                } else {
8548add1155SRico Sonntag                    $result = (string) floor($age / 365.25);
8558add1155SRico Sonntag                }
8568add1155SRico Sonntag
8578add1155SRico Sonntag                break;
8588add1155SRico Sonntag        }
8598add1155SRico Sonntag
8608add1155SRico Sonntag        return $result;
8618add1155SRico Sonntag    }
8628add1155SRico Sonntag
8638add1155SRico Sonntag    /**
8648add1155SRico Sonntag     * Find the youngest mother
8658add1155SRico Sonntag     *
8668add1155SRico Sonntag     * @return string
8678add1155SRico Sonntag     */
8688add1155SRico Sonntag    public function youngestMother(): string
8698add1155SRico Sonntag    {
8708add1155SRico Sonntag        return $this->parentsQuery('full', 'ASC', 'F', false);
8718add1155SRico Sonntag    }
8728add1155SRico Sonntag
8738add1155SRico Sonntag    /**
8748add1155SRico Sonntag     * Find the name of the youngest mother.
8758add1155SRico Sonntag     *
8768add1155SRico Sonntag     * @return string
8778add1155SRico Sonntag     */
8788add1155SRico Sonntag    public function youngestMotherName(): string
8798add1155SRico Sonntag    {
8808add1155SRico Sonntag        return $this->parentsQuery('name', 'ASC', 'F', false);
8818add1155SRico Sonntag    }
8828add1155SRico Sonntag
8838add1155SRico Sonntag    /**
8848add1155SRico Sonntag     * Find the age of the youngest mother.
8858add1155SRico Sonntag     *
8868add1155SRico Sonntag     * @param string $show_years
8878add1155SRico Sonntag     *
8888add1155SRico Sonntag     * @return string
8898add1155SRico Sonntag     */
8908add1155SRico Sonntag    public function youngestMotherAge(string $show_years = ''): string
8918add1155SRico Sonntag    {
8928add1155SRico Sonntag        return $this->parentsQuery('age', 'ASC', 'F', (bool) $show_years);
8938add1155SRico Sonntag    }
8948add1155SRico Sonntag
8958add1155SRico Sonntag    /**
8968add1155SRico Sonntag     * Find the oldest mother.
8978add1155SRico Sonntag     *
8988add1155SRico Sonntag     * @return string
8998add1155SRico Sonntag     */
9008add1155SRico Sonntag    public function oldestMother(): string
9018add1155SRico Sonntag    {
9028add1155SRico Sonntag        return $this->parentsQuery('full', 'DESC', 'F', false);
9038add1155SRico Sonntag    }
9048add1155SRico Sonntag
9058add1155SRico Sonntag    /**
9068add1155SRico Sonntag     * Find the name of the oldest mother.
9078add1155SRico Sonntag     *
9088add1155SRico Sonntag     * @return string
9098add1155SRico Sonntag     */
9108add1155SRico Sonntag    public function oldestMotherName(): string
9118add1155SRico Sonntag    {
9128add1155SRico Sonntag        return $this->parentsQuery('name', 'DESC', 'F', false);
9138add1155SRico Sonntag    }
9148add1155SRico Sonntag
9158add1155SRico Sonntag    /**
9168add1155SRico Sonntag     * Find the age of the oldest mother.
9178add1155SRico Sonntag     *
9188add1155SRico Sonntag     * @param string $show_years
9198add1155SRico Sonntag     *
9208add1155SRico Sonntag     * @return string
9218add1155SRico Sonntag     */
9228add1155SRico Sonntag    public function oldestMotherAge(string $show_years = ''): string
9238add1155SRico Sonntag    {
9248add1155SRico Sonntag        return $this->parentsQuery('age', 'DESC', 'F', (bool) $show_years);
9258add1155SRico Sonntag    }
9268add1155SRico Sonntag
9278add1155SRico Sonntag    /**
9288add1155SRico Sonntag     * Find the youngest father.
9298add1155SRico Sonntag     *
9308add1155SRico Sonntag     * @return string
9318add1155SRico Sonntag     */
9328add1155SRico Sonntag    public function youngestFather(): string
9338add1155SRico Sonntag    {
9348add1155SRico Sonntag        return $this->parentsQuery('full', 'ASC', 'M', false);
9358add1155SRico Sonntag    }
9368add1155SRico Sonntag
9378add1155SRico Sonntag    /**
9388add1155SRico Sonntag     * Find the name of the youngest father.
9398add1155SRico Sonntag     *
9408add1155SRico Sonntag     * @return string
9418add1155SRico Sonntag     */
9428add1155SRico Sonntag    public function youngestFatherName(): string
9438add1155SRico Sonntag    {
9448add1155SRico Sonntag        return $this->parentsQuery('name', 'ASC', 'M', false);
9458add1155SRico Sonntag    }
9468add1155SRico Sonntag
9478add1155SRico Sonntag    /**
9488add1155SRico Sonntag     * Find the age of the youngest father.
9498add1155SRico Sonntag     *
9508add1155SRico Sonntag     * @param string $show_years
9518add1155SRico Sonntag     *
9528add1155SRico Sonntag     * @return string
9538add1155SRico Sonntag     */
9548add1155SRico Sonntag    public function youngestFatherAge(string $show_years = ''): string
9558add1155SRico Sonntag    {
9568add1155SRico Sonntag        return $this->parentsQuery('age', 'ASC', 'M', (bool) $show_years);
9578add1155SRico Sonntag    }
9588add1155SRico Sonntag
9598add1155SRico Sonntag    /**
9608add1155SRico Sonntag     * Find the oldest father.
9618add1155SRico Sonntag     *
9628add1155SRico Sonntag     * @return string
9638add1155SRico Sonntag     */
9648add1155SRico Sonntag    public function oldestFather(): string
9658add1155SRico Sonntag    {
9668add1155SRico Sonntag        return $this->parentsQuery('full', 'DESC', 'M', false);
9678add1155SRico Sonntag    }
9688add1155SRico Sonntag
9698add1155SRico Sonntag    /**
9708add1155SRico Sonntag     * Find the name of the oldest father.
9718add1155SRico Sonntag     *
9728add1155SRico Sonntag     * @return string
9738add1155SRico Sonntag     */
9748add1155SRico Sonntag    public function oldestFatherName(): string
9758add1155SRico Sonntag    {
9768add1155SRico Sonntag        return $this->parentsQuery('name', 'DESC', 'M', false);
9778add1155SRico Sonntag    }
9788add1155SRico Sonntag
9798add1155SRico Sonntag    /**
9808add1155SRico Sonntag     * Find the age of the oldest father.
9818add1155SRico Sonntag     *
9828add1155SRico Sonntag     * @param string $show_years
9838add1155SRico Sonntag     *
9848add1155SRico Sonntag     * @return string
9858add1155SRico Sonntag     */
9868add1155SRico Sonntag    public function oldestFatherAge(string $show_years = ''): string
9878add1155SRico Sonntag    {
9888add1155SRico Sonntag        return $this->parentsQuery('age', 'DESC', 'M', (bool) $show_years);
9898add1155SRico Sonntag    }
9908add1155SRico Sonntag
9918add1155SRico Sonntag    /**
9928add1155SRico Sonntag     * General query on age at marriage.
9938add1155SRico Sonntag     *
9948add1155SRico Sonntag     * @param string $type
9955ad3f7b6SGreg Roach     * @param string $age_dir "ASC" or "DESC"
9968add1155SRico Sonntag     * @param int    $total
9978add1155SRico Sonntag     *
9988add1155SRico Sonntag     * @return string
9998add1155SRico Sonntag     */
10008add1155SRico Sonntag    private function ageOfMarriageQuery(string $type, string $age_dir, int $total): string
10018add1155SRico Sonntag    {
10025ad3f7b6SGreg Roach        $prefix = DB::connection()->getTablePrefix();
10038add1155SRico Sonntag
10045ad3f7b6SGreg Roach        $hrows = DB::table('families')
10055ad3f7b6SGreg Roach            ->where('f_file', '=', $this->tree->id())
10065ad3f7b6SGreg Roach            ->join('dates AS married', function (JoinClause $join): void {
10075ad3f7b6SGreg Roach                $join
10085ad3f7b6SGreg Roach                    ->on('married.d_file', '=', 'f_file')
10095ad3f7b6SGreg Roach                    ->on('married.d_gid', '=', 'f_id')
10105ad3f7b6SGreg Roach                    ->where('married.d_fact', '=', 'MARR')
10115ad3f7b6SGreg Roach                    ->where('married.d_julianday1', '<>', 0);
10125ad3f7b6SGreg Roach            })
10135ad3f7b6SGreg Roach            ->join('dates AS husbdeath', function (JoinClause $join): void {
10145ad3f7b6SGreg Roach                $join
10155ad3f7b6SGreg Roach                    ->on('husbdeath.d_gid', '=', 'f_husb')
10165ad3f7b6SGreg Roach                    ->on('husbdeath.d_file', '=', 'f_file')
10175ad3f7b6SGreg Roach                    ->where('husbdeath.d_fact', '=', 'DEAT');
10185ad3f7b6SGreg Roach            })
10195ad3f7b6SGreg Roach            ->whereColumn('married.d_julianday1', '<', 'husbdeath.d_julianday2')
10205ad3f7b6SGreg Roach            ->groupBy('f_id')
10215ad3f7b6SGreg Roach            ->select(['f_id AS family', DB::raw('MIN(' . $prefix . 'husbdeath.d_julianday2 - ' . $prefix . 'married.d_julianday1) AS age')])
10225ad3f7b6SGreg Roach            ->get()
10235ad3f7b6SGreg Roach            ->all();
10248add1155SRico Sonntag
10255ad3f7b6SGreg Roach        $wrows = DB::table('families')
10265ad3f7b6SGreg Roach            ->where('f_file', '=', $this->tree->id())
10275ad3f7b6SGreg Roach            ->join('dates AS married', function (JoinClause $join): void {
10285ad3f7b6SGreg Roach                $join
10295ad3f7b6SGreg Roach                    ->on('married.d_file', '=', 'f_file')
10305ad3f7b6SGreg Roach                    ->on('married.d_gid', '=', 'f_id')
10315ad3f7b6SGreg Roach                    ->where('married.d_fact', '=', 'MARR')
10325ad3f7b6SGreg Roach                    ->where('married.d_julianday1', '<>', 0);
10335ad3f7b6SGreg Roach            })
10345ad3f7b6SGreg Roach            ->join('dates AS wifedeath', function (JoinClause $join): void {
10355ad3f7b6SGreg Roach                $join
10365ad3f7b6SGreg Roach                    ->on('wifedeath.d_gid', '=', 'f_wife')
10375ad3f7b6SGreg Roach                    ->on('wifedeath.d_file', '=', 'f_file')
10385ad3f7b6SGreg Roach                    ->where('wifedeath.d_fact', '=', 'DEAT');
10395ad3f7b6SGreg Roach            })
10405ad3f7b6SGreg Roach            ->whereColumn('married.d_julianday1', '<', 'wifedeath.d_julianday2')
10415ad3f7b6SGreg Roach            ->groupBy('f_id')
10425ad3f7b6SGreg Roach            ->select(['f_id AS family', DB::raw('MIN(' . $prefix . 'wifedeath.d_julianday2 - ' . $prefix . 'married.d_julianday1) AS age')])
10435ad3f7b6SGreg Roach            ->get()
10445ad3f7b6SGreg Roach            ->all();
10458add1155SRico Sonntag
10465ad3f7b6SGreg Roach        $drows = DB::table('families')
10475ad3f7b6SGreg Roach            ->where('f_file', '=', $this->tree->id())
10485ad3f7b6SGreg Roach            ->join('dates AS married', function (JoinClause $join): void {
10495ad3f7b6SGreg Roach                $join
10505ad3f7b6SGreg Roach                    ->on('married.d_file', '=', 'f_file')
10515ad3f7b6SGreg Roach                    ->on('married.d_gid', '=', 'f_id')
10525ad3f7b6SGreg Roach                    ->where('married.d_fact', '=', 'MARR')
10535ad3f7b6SGreg Roach                    ->where('married.d_julianday1', '<>', 0);
10545ad3f7b6SGreg Roach            })
10555ad3f7b6SGreg Roach            ->join('dates AS divorced', function (JoinClause $join): void {
10565ad3f7b6SGreg Roach                $join
10575ad3f7b6SGreg Roach                    ->on('divorced.d_gid', '=', 'f_id')
10585ad3f7b6SGreg Roach                    ->on('divorced.d_file', '=', 'f_file')
10595ad3f7b6SGreg Roach                    ->whereIn('divorced.d_fact', ['DIV', 'ANUL', '_SEPR']);
10605ad3f7b6SGreg Roach            })
10615ad3f7b6SGreg Roach            ->whereColumn('married.d_julianday1', '<', 'divorced.d_julianday2')
10625ad3f7b6SGreg Roach            ->groupBy('f_id')
10635ad3f7b6SGreg Roach            ->select(['f_id AS family', DB::raw('MIN(' . $prefix . 'divorced.d_julianday2 - ' . $prefix . 'married.d_julianday1) AS age')])
10645ad3f7b6SGreg Roach            ->get()
10655ad3f7b6SGreg Roach            ->all();
10668add1155SRico Sonntag
10678add1155SRico Sonntag        $rows = [];
10688add1155SRico Sonntag        foreach ($drows as $family) {
10698add1155SRico Sonntag            $rows[$family->family] = $family->age;
10708add1155SRico Sonntag        }
10718add1155SRico Sonntag
10728add1155SRico Sonntag        foreach ($hrows as $family) {
10738add1155SRico Sonntag            if (!isset($rows[$family->family])) {
10748add1155SRico Sonntag                $rows[$family->family] = $family->age;
10758add1155SRico Sonntag            }
10768add1155SRico Sonntag        }
10778add1155SRico Sonntag
10788add1155SRico Sonntag        foreach ($wrows as $family) {
10798add1155SRico Sonntag            if (!isset($rows[$family->family])) {
10808add1155SRico Sonntag                $rows[$family->family] = $family->age;
10818add1155SRico Sonntag            } elseif ($rows[$family->family] > $family->age) {
10828add1155SRico Sonntag                $rows[$family->family] = $family->age;
10838add1155SRico Sonntag            }
10848add1155SRico Sonntag        }
10858add1155SRico Sonntag
10868add1155SRico Sonntag        if ($age_dir === 'DESC') {
10878add1155SRico Sonntag            arsort($rows);
10888add1155SRico Sonntag        } else {
10898add1155SRico Sonntag            asort($rows);
10908add1155SRico Sonntag        }
10918add1155SRico Sonntag
10928add1155SRico Sonntag        $top10 = [];
10938add1155SRico Sonntag        $i     = 0;
10948add1155SRico Sonntag        foreach ($rows as $fam => $age) {
10958add1155SRico Sonntag            $family = Family::getInstance($fam, $this->tree);
10968add1155SRico Sonntag            if ($type === 'name') {
10978add1155SRico Sonntag                return $family->formatList();
10988add1155SRico Sonntag            }
10998add1155SRico Sonntag
11008add1155SRico Sonntag            $age = $this->calculateAge((int) $age);
11018add1155SRico Sonntag
11028add1155SRico Sonntag            if ($type === 'age') {
11038add1155SRico Sonntag                return $age;
11048add1155SRico Sonntag            }
11058add1155SRico Sonntag
110639ca88baSGreg Roach            $husb = $family->husband();
110739ca88baSGreg Roach            $wife = $family->wife();
11088add1155SRico Sonntag
11098add1155SRico Sonntag            if (($husb && ($husb->getAllDeathDates() || !$husb->isDead()))
11108add1155SRico Sonntag                && ($wife && ($wife->getAllDeathDates() || !$wife->isDead()))
11118add1155SRico Sonntag            ) {
11128add1155SRico Sonntag                if ($family && $family->canShow()) {
11138add1155SRico Sonntag                    if ($type === 'list') {
111439ca88baSGreg Roach                        $top10[] = '<li><a href="' . e($family->url()) . '">' . $family->fullName() . '</a> (' . $age . ')' . '</li>';
11158add1155SRico Sonntag                    } else {
111639ca88baSGreg Roach                        $top10[] = '<a href="' . e($family->url()) . '">' . $family->fullName() . '</a> (' . $age . ')';
11178add1155SRico Sonntag                    }
11188add1155SRico Sonntag                }
11198add1155SRico Sonntag                if (++$i === $total) {
11208add1155SRico Sonntag                    break;
11218add1155SRico Sonntag                }
11228add1155SRico Sonntag            }
11238add1155SRico Sonntag        }
11248add1155SRico Sonntag
11258add1155SRico Sonntag        if ($type === 'list') {
11268add1155SRico Sonntag            $top10 = implode('', $top10);
11278add1155SRico Sonntag        } else {
11288add1155SRico Sonntag            $top10 = implode('; ', $top10);
11298add1155SRico Sonntag        }
11308add1155SRico Sonntag
11318add1155SRico Sonntag        if (I18N::direction() === 'rtl') {
11328add1155SRico Sonntag            $top10 = str_replace([
11338add1155SRico Sonntag                '[',
11348add1155SRico Sonntag                ']',
11358add1155SRico Sonntag                '(',
11368add1155SRico Sonntag                ')',
11378add1155SRico Sonntag                '+',
11388add1155SRico Sonntag            ], [
11398add1155SRico Sonntag                '&rlm;[',
11408add1155SRico Sonntag                '&rlm;]',
11418add1155SRico Sonntag                '&rlm;(',
11428add1155SRico Sonntag                '&rlm;)',
11438add1155SRico Sonntag                '&rlm;+',
11448add1155SRico Sonntag            ], $top10);
11458add1155SRico Sonntag        }
11468add1155SRico Sonntag
11478add1155SRico Sonntag        if ($type === 'list') {
11488add1155SRico Sonntag            return '<ul>' . $top10 . '</ul>';
11498add1155SRico Sonntag        }
11508add1155SRico Sonntag
11518add1155SRico Sonntag        return $top10;
11528add1155SRico Sonntag    }
11538add1155SRico Sonntag
11548add1155SRico Sonntag    /**
11558add1155SRico Sonntag     * General query on marriage ages.
11568add1155SRico Sonntag     *
11578add1155SRico Sonntag     * @return string
11588add1155SRico Sonntag     */
11598add1155SRico Sonntag    public function topAgeOfMarriageFamily(): string
11608add1155SRico Sonntag    {
11618add1155SRico Sonntag        return $this->ageOfMarriageQuery('name', 'DESC', 1);
11628add1155SRico Sonntag    }
11638add1155SRico Sonntag
11648add1155SRico Sonntag    /**
11658add1155SRico Sonntag     * General query on marriage ages.
11668add1155SRico Sonntag     *
11678add1155SRico Sonntag     * @return string
11688add1155SRico Sonntag     */
11698add1155SRico Sonntag    public function topAgeOfMarriage(): string
11708add1155SRico Sonntag    {
11718add1155SRico Sonntag        return $this->ageOfMarriageQuery('age', 'DESC', 1);
11728add1155SRico Sonntag    }
11738add1155SRico Sonntag
11748add1155SRico Sonntag    /**
11758add1155SRico Sonntag     * General query on marriage ages.
11768add1155SRico Sonntag     *
11778add1155SRico Sonntag     * @param int $total
11788add1155SRico Sonntag     *
11798add1155SRico Sonntag     * @return string
11808add1155SRico Sonntag     */
11818add1155SRico Sonntag    public function topAgeOfMarriageFamilies(int $total = 10): string
11828add1155SRico Sonntag    {
11838add1155SRico Sonntag        return $this->ageOfMarriageQuery('nolist', 'DESC', $total);
11848add1155SRico Sonntag    }
11858add1155SRico Sonntag
11868add1155SRico Sonntag    /**
11878add1155SRico Sonntag     * General query on marriage ages.
11888add1155SRico Sonntag     *
11898add1155SRico Sonntag     * @param int $total
11908add1155SRico Sonntag     *
11918add1155SRico Sonntag     * @return string
11928add1155SRico Sonntag     */
11938add1155SRico Sonntag    public function topAgeOfMarriageFamiliesList(int $total = 10): string
11948add1155SRico Sonntag    {
11958add1155SRico Sonntag        return $this->ageOfMarriageQuery('list', 'DESC', $total);
11968add1155SRico Sonntag    }
11978add1155SRico Sonntag
11988add1155SRico Sonntag    /**
11998add1155SRico Sonntag     * General query on marriage ages.
12008add1155SRico Sonntag     *
12018add1155SRico Sonntag     * @return string
12028add1155SRico Sonntag     */
12038add1155SRico Sonntag    public function minAgeOfMarriageFamily(): string
12048add1155SRico Sonntag    {
12058add1155SRico Sonntag        return $this->ageOfMarriageQuery('name', 'ASC', 1);
12068add1155SRico Sonntag    }
12078add1155SRico Sonntag
12088add1155SRico Sonntag    /**
12098add1155SRico Sonntag     * General query on marriage ages.
12108add1155SRico Sonntag     *
12118add1155SRico Sonntag     * @return string
12128add1155SRico Sonntag     */
12138add1155SRico Sonntag    public function minAgeOfMarriage(): string
12148add1155SRico Sonntag    {
12158add1155SRico Sonntag        return $this->ageOfMarriageQuery('age', 'ASC', 1);
12168add1155SRico Sonntag    }
12178add1155SRico Sonntag
12188add1155SRico Sonntag    /**
12198add1155SRico Sonntag     * General query on marriage ages.
12208add1155SRico Sonntag     *
12218add1155SRico Sonntag     * @param int $total
12228add1155SRico Sonntag     *
12238add1155SRico Sonntag     * @return string
12248add1155SRico Sonntag     */
12258add1155SRico Sonntag    public function minAgeOfMarriageFamilies(int $total = 10): string
12268add1155SRico Sonntag    {
12278add1155SRico Sonntag        return $this->ageOfMarriageQuery('nolist', 'ASC', $total);
12288add1155SRico Sonntag    }
12298add1155SRico Sonntag
12308add1155SRico Sonntag    /**
12318add1155SRico Sonntag     * General query on marriage ages.
12328add1155SRico Sonntag     *
12338add1155SRico Sonntag     * @param int $total
12348add1155SRico Sonntag     *
12358add1155SRico Sonntag     * @return string
12368add1155SRico Sonntag     */
12378add1155SRico Sonntag    public function minAgeOfMarriageFamiliesList(int $total = 10): string
12388add1155SRico Sonntag    {
12398add1155SRico Sonntag        return $this->ageOfMarriageQuery('list', 'ASC', $total);
12408add1155SRico Sonntag    }
12418add1155SRico Sonntag
12428add1155SRico Sonntag    /**
12438add1155SRico Sonntag     * Find the ages between spouses.
12448add1155SRico Sonntag     *
12458add1155SRico Sonntag     * @param string $age_dir
12468add1155SRico Sonntag     * @param int    $total
12478add1155SRico Sonntag     *
12488add1155SRico Sonntag     * @return array
12498add1155SRico Sonntag     */
12508add1155SRico Sonntag    private function ageBetweenSpousesQuery(string $age_dir, int $total): array
12518add1155SRico Sonntag    {
1252d1a467e4SGreg Roach        $prefix = DB::connection()->getTablePrefix();
1253d1a467e4SGreg Roach
1254d1a467e4SGreg Roach        $query = DB::table('families')
1255d1a467e4SGreg Roach            ->where('f_file', '=', $this->tree->id())
1256d1a467e4SGreg Roach            ->join('dates AS wife', function (JoinClause $join): void {
1257d1a467e4SGreg Roach                $join
1258d1a467e4SGreg Roach                    ->on('wife.d_gid', '=', 'f_wife')
1259d1a467e4SGreg Roach                    ->on('wife.d_file', '=', 'f_file')
1260d1a467e4SGreg Roach                    ->where('wife.d_fact', '=', 'BIRT')
1261d1a467e4SGreg Roach                    ->where('wife.d_julianday1', '<>', 0);
1262d1a467e4SGreg Roach            })
1263d1a467e4SGreg Roach            ->join('dates AS husb', function (JoinClause $join): void {
1264d1a467e4SGreg Roach                $join
1265d1a467e4SGreg Roach                    ->on('husb.d_gid', '=', 'f_husb')
1266d1a467e4SGreg Roach                    ->on('husb.d_file', '=', 'f_file')
1267d1a467e4SGreg Roach                    ->where('husb.d_fact', '=', 'BIRT')
1268d1a467e4SGreg Roach                    ->where('husb.d_julianday1', '<>', 0);
1269d1a467e4SGreg Roach            });
1270d1a467e4SGreg Roach
12718add1155SRico Sonntag        if ($age_dir === 'DESC') {
1272d1a467e4SGreg Roach            $query
1273d1a467e4SGreg Roach                ->whereColumn('wife.d_julianday1', '>=', 'husb.d_julianday1')
1274d1a467e4SGreg Roach                ->orderBy(DB::raw('MIN(' . $prefix . 'wife.d_julianday1) - MIN(' . $prefix . 'husb.d_julianday1)'), 'DESC');
12758add1155SRico Sonntag        } else {
1276d1a467e4SGreg Roach            $query
1277d1a467e4SGreg Roach                ->whereColumn('husb.d_julianday1', '>=', 'wife.d_julianday1')
1278d1a467e4SGreg Roach                ->orderBy(DB::raw('MIN(' . $prefix . 'husb.d_julianday1) - MIN(' . $prefix . 'wife.d_julianday1)'), 'DESC');
12798add1155SRico Sonntag        }
12808add1155SRico Sonntag
1281c0112ce8SGreg Roach        return $query
1282d1a467e4SGreg Roach            ->groupBy(['f_id', 'f_file'])
1283d1a467e4SGreg Roach            ->select('families.*')
1284d1a467e4SGreg Roach            ->take($total)
1285d1a467e4SGreg Roach            ->get()
1286d1a467e4SGreg Roach            ->map(Family::rowMapper())
1287c0112ce8SGreg Roach            ->filter(GedcomRecord::accessFilter())
1288c0112ce8SGreg Roach            ->map(function (Family $family) use ($age_dir): array {
1289d1a467e4SGreg Roach                $husb_birt_jd = $family->husband()->getBirthDate()->minimumJulianDay();
1290d1a467e4SGreg Roach                $wife_birt_jd = $family->wife()->getBirthDate()->minimumJulianDay();
12918add1155SRico Sonntag
1292d1a467e4SGreg Roach                if ($age_dir === 'DESC') {
1293d1a467e4SGreg Roach                    $diff = $wife_birt_jd - $husb_birt_jd;
1294d1a467e4SGreg Roach                } else {
1295d1a467e4SGreg Roach                    $diff = $husb_birt_jd - $wife_birt_jd;
12968add1155SRico Sonntag                }
12978add1155SRico Sonntag
1298c0112ce8SGreg Roach                return [
12998add1155SRico Sonntag                    'family' => $family,
130093ccd686SRico Sonntag                    'age'    => $this->calculateAge($diff),
13018add1155SRico Sonntag                ];
1302c0112ce8SGreg Roach            })
1303c0112ce8SGreg Roach            ->all();
13048add1155SRico Sonntag    }
13058add1155SRico Sonntag
13068add1155SRico Sonntag    /**
13078add1155SRico Sonntag     * Find the age between husband and wife.
13088add1155SRico Sonntag     *
13098add1155SRico Sonntag     * @param int $total
13108add1155SRico Sonntag     *
13118add1155SRico Sonntag     * @return string
13128add1155SRico Sonntag     */
13138add1155SRico Sonntag    public function ageBetweenSpousesMF(int $total = 10): string
13148add1155SRico Sonntag    {
13158add1155SRico Sonntag        $records = $this->ageBetweenSpousesQuery('DESC', $total);
13168add1155SRico Sonntag
1317c0112ce8SGreg Roach        return view('statistics/families/top10-nolist-spouses', [
13188add1155SRico Sonntag            'records' => $records,
1319c0112ce8SGreg Roach        ]);
13208add1155SRico Sonntag    }
13218add1155SRico Sonntag
13228add1155SRico Sonntag    /**
13238add1155SRico Sonntag     * Find the age between husband and wife.
13248add1155SRico Sonntag     *
13258add1155SRico Sonntag     * @param int $total
13268add1155SRico Sonntag     *
13278add1155SRico Sonntag     * @return string
13288add1155SRico Sonntag     */
13298add1155SRico Sonntag    public function ageBetweenSpousesMFList(int $total = 10): string
13308add1155SRico Sonntag    {
13318add1155SRico Sonntag        $records = $this->ageBetweenSpousesQuery('DESC', $total);
13328add1155SRico Sonntag
1333c0112ce8SGreg Roach        return view('statistics/families/top10-list-spouses', [
13348add1155SRico Sonntag            'records' => $records,
1335c0112ce8SGreg Roach        ]);
13368add1155SRico Sonntag    }
13378add1155SRico Sonntag
13388add1155SRico Sonntag    /**
13398add1155SRico Sonntag     * Find the age between wife and husband..
13408add1155SRico Sonntag     *
13418add1155SRico Sonntag     * @param int $total
13428add1155SRico Sonntag     *
13438add1155SRico Sonntag     * @return string
13448add1155SRico Sonntag     */
13458add1155SRico Sonntag    public function ageBetweenSpousesFM(int $total = 10): string
13468add1155SRico Sonntag    {
13478add1155SRico Sonntag        $records = $this->ageBetweenSpousesQuery('ASC', $total);
13488add1155SRico Sonntag
1349c0112ce8SGreg Roach        return view('statistics/families/top10-nolist-spouses', [
13508add1155SRico Sonntag            'records' => $records,
1351c0112ce8SGreg Roach        ]);
13528add1155SRico Sonntag    }
13538add1155SRico Sonntag
13548add1155SRico Sonntag    /**
13558add1155SRico Sonntag     * Find the age between wife and husband..
13568add1155SRico Sonntag     *
13578add1155SRico Sonntag     * @param int $total
13588add1155SRico Sonntag     *
13598add1155SRico Sonntag     * @return string
13608add1155SRico Sonntag     */
13618add1155SRico Sonntag    public function ageBetweenSpousesFMList(int $total = 10): string
13628add1155SRico Sonntag    {
13638add1155SRico Sonntag        $records = $this->ageBetweenSpousesQuery('ASC', $total);
13648add1155SRico Sonntag
1365c0112ce8SGreg Roach        return view('statistics/families/top10-list-spouses', [
13668add1155SRico Sonntag            'records' => $records,
1367c0112ce8SGreg Roach        ]);
13688add1155SRico Sonntag    }
13698add1155SRico Sonntag
13708add1155SRico Sonntag    /**
13718add1155SRico Sonntag     * General query on ages at marriage.
13728add1155SRico Sonntag     *
1373afa8d404SGreg Roach     * @param string $sex   "M" or "F"
13748add1155SRico Sonntag     * @param int    $year1
13758add1155SRico Sonntag     * @param int    $year2
13768add1155SRico Sonntag     *
13778add1155SRico Sonntag     * @return array
13788add1155SRico Sonntag     */
1379afa8d404SGreg Roach    public function statsMarrAgeQuery($sex, $year1 = -1, $year2 = -1): array
13808add1155SRico Sonntag    {
1381afa8d404SGreg Roach        $prefix = DB::connection()->getTablePrefix();
1382afa8d404SGreg Roach
1383afa8d404SGreg Roach        $query = DB::table('dates AS married')
1384afa8d404SGreg Roach            ->join('families', function (JoinClause $join): void {
1385afa8d404SGreg Roach                $join
1386afa8d404SGreg Roach                    ->on('f_file', '=', 'married.d_file')
1387afa8d404SGreg Roach                    ->on('f_id', '=', 'married.d_gid');
1388afa8d404SGreg Roach            })
1389afa8d404SGreg Roach            ->join('dates AS birth', function (JoinClause $join) use ($sex): void {
1390afa8d404SGreg Roach                $join
1391afa8d404SGreg Roach                    ->on('birth.d_file', '=', 'married.d_file')
1392afa8d404SGreg Roach                    ->on('birth.d_gid', '=', $sex === 'M' ? 'f_husb' : 'f_wife')
1393afa8d404SGreg Roach                    ->where('birth.d_julianday1', '<>', 0)
1394afa8d404SGreg Roach                    ->where('birth.d_fact', '=', 'BIRT')
1395afa8d404SGreg Roach                    ->whereIn('birth.d_type', ['@#DGREGORIAN@', '@#DJULIAN@']);
1396afa8d404SGreg Roach            })
1397afa8d404SGreg Roach            ->where('married.d_file', '=', $this->tree->id())
1398afa8d404SGreg Roach            ->where('married.d_fact', '=', 'MARR')
1399afa8d404SGreg Roach            ->whereIn('married.d_type', ['@#DGREGORIAN@', '@#DJULIAN@'])
1400afa8d404SGreg Roach            ->whereColumn('married.d_julianday1', '>', 'birth.d_julianday1')
1401afa8d404SGreg Roach            ->select(['f_id', 'birth.d_gid', DB::raw($prefix . 'married.d_julianday2 - ' . $prefix . 'birth.d_julianday1 AS age')]);
1402afa8d404SGreg Roach
14038add1155SRico Sonntag        if ($year1 >= 0 && $year2 >= 0) {
1404afa8d404SGreg Roach            $query->whereBetween('married.d_year', [$year1, $year2]);
14058add1155SRico Sonntag        }
14068add1155SRico Sonntag
1407afa8d404SGreg Roach        return $query
1408afa8d404SGreg Roach            ->get()
1409afa8d404SGreg Roach            ->map(function (stdClass $row): stdClass {
14108add1155SRico Sonntag                $row->age = (int) $row->age;
14118add1155SRico Sonntag
1412afa8d404SGreg Roach                return $row;
1413afa8d404SGreg Roach            })
1414afa8d404SGreg Roach            ->all();
14158add1155SRico Sonntag    }
14168add1155SRico Sonntag
14178add1155SRico Sonntag    /**
14188add1155SRico Sonntag     * General query on marriage ages.
14198add1155SRico Sonntag     *
14208add1155SRico Sonntag     * @return string
14218add1155SRico Sonntag     */
142288de55fdSRico Sonntag    public function statsMarrAge(): string
14238add1155SRico Sonntag    {
14248add1155SRico Sonntag        return (new ChartMarriageAge($this->tree))
142588de55fdSRico Sonntag            ->chartMarriageAge();
14268add1155SRico Sonntag    }
14278add1155SRico Sonntag
14288add1155SRico Sonntag    /**
14298add1155SRico Sonntag     * Query the database for marriage tags.
14308add1155SRico Sonntag     *
14312c928961SGreg Roach     * @param string $type       "full", "name" or "age"
14322c928961SGreg Roach     * @param string $age_dir    "ASC" or "DESC"
14332c928961SGreg Roach     * @param string $sex        "F" or "M"
14348add1155SRico Sonntag     * @param bool   $show_years
14358add1155SRico Sonntag     *
14368add1155SRico Sonntag     * @return string
14378add1155SRico Sonntag     */
14388add1155SRico Sonntag    private function marriageQuery(string $type, string $age_dir, string $sex, bool $show_years): string
14398add1155SRico Sonntag    {
14408add1155SRico Sonntag        if ($sex === 'F') {
14418add1155SRico Sonntag            $sex_field = 'f_wife';
14428add1155SRico Sonntag        } else {
14438add1155SRico Sonntag            $sex_field = 'f_husb';
14448add1155SRico Sonntag        }
14458add1155SRico Sonntag
14468add1155SRico Sonntag        if ($age_dir !== 'ASC') {
14478add1155SRico Sonntag            $age_dir = 'DESC';
14488add1155SRico Sonntag        }
14498add1155SRico Sonntag
14502c928961SGreg Roach        $prefix = DB::connection()->getTablePrefix();
14518add1155SRico Sonntag
14522c928961SGreg Roach        $row = DB::table('families')
14532c928961SGreg Roach            ->join('dates AS married', function (JoinClause $join): void {
14542c928961SGreg Roach                $join
14552c928961SGreg Roach                    ->on('married.d_file', '=', 'f_file')
14562c928961SGreg Roach                    ->on('married.d_gid', '=', 'f_id')
14572c928961SGreg Roach                    ->where('married.d_fact', '=', 'MARR');
14582c928961SGreg Roach            })
14592c928961SGreg Roach            ->join('individuals', function (JoinClause $join) use ($sex, $sex_field): void {
14602c928961SGreg Roach                $join
14612c928961SGreg Roach                    ->on('i_file', '=', 'f_file')
14622c928961SGreg Roach                    ->on('i_id', '=', $sex_field)
14632c928961SGreg Roach                    ->where('i_sex', '=', $sex);
14642c928961SGreg Roach            })
14652c928961SGreg Roach            ->join('dates AS birth', function (JoinClause $join): void {
14662c928961SGreg Roach                $join
14672c928961SGreg Roach                    ->on('birth.d_file', '=', 'i_file')
14682c928961SGreg Roach                    ->on('birth.d_gid', '=', 'i_id')
14692c928961SGreg Roach                    ->where('birth.d_fact', '=', 'BIRT')
14702c928961SGreg Roach                    ->where('birth.d_julianday1', '<>', 0);
14712c928961SGreg Roach            })
14722c928961SGreg Roach            ->where('f_file', '=', $this->tree->id())
14732c928961SGreg Roach            ->where('married.d_julianday2', '>', 'birth.d_julianday1')
14742c928961SGreg Roach            ->orderBy(DB::raw($prefix . 'married.d_julianday2 - ' . $prefix . 'birth.d_julianday1'), $age_dir)
14752c928961SGreg Roach            ->select(['f_id AS famid', $sex_field, DB::raw($prefix . 'married.d_julianday2 - ' . $prefix . 'birth.d_julianday1 AS age'), 'i_id'])
14762c928961SGreg Roach            ->take(1)
14772c928961SGreg Roach            ->get()
14782c928961SGreg Roach            ->first();
14792c928961SGreg Roach
14802c928961SGreg Roach        if ($row === null) {
14818add1155SRico Sonntag            return '';
14828add1155SRico Sonntag        }
14838add1155SRico Sonntag
14848add1155SRico Sonntag        $family = Family::getInstance($row->famid, $this->tree);
14858add1155SRico Sonntag        $person = Individual::getInstance($row->i_id, $this->tree);
14868add1155SRico Sonntag
14878add1155SRico Sonntag        switch ($type) {
14888add1155SRico Sonntag            default:
14898add1155SRico Sonntag            case 'full':
14908add1155SRico Sonntag                if ($family && $family->canShow()) {
14918add1155SRico Sonntag                    $result = $family->formatList();
14928add1155SRico Sonntag                } else {
14938add1155SRico Sonntag                    $result = I18N::translate('This information is private and cannot be shown.');
14948add1155SRico Sonntag                }
14958add1155SRico Sonntag                break;
14968add1155SRico Sonntag
14978add1155SRico Sonntag            case 'name':
149839ca88baSGreg Roach                $result = '<a href="' . e($family->url()) . '">' . $person->fullName() . '</a>';
14998add1155SRico Sonntag                break;
15008add1155SRico Sonntag
15018add1155SRico Sonntag            case 'age':
15028add1155SRico Sonntag                $age = $row->age;
15038add1155SRico Sonntag
15048add1155SRico Sonntag                if ($show_years) {
15058add1155SRico Sonntag                    $result = $this->calculateAge((int) $row->age);
15068add1155SRico Sonntag                } else {
15078add1155SRico Sonntag                    $result = I18N::number((int) ($age / 365.25));
15088add1155SRico Sonntag                }
15098add1155SRico Sonntag
15108add1155SRico Sonntag                break;
15118add1155SRico Sonntag        }
15128add1155SRico Sonntag
15138add1155SRico Sonntag        return $result;
15148add1155SRico Sonntag    }
15158add1155SRico Sonntag
15168add1155SRico Sonntag    /**
15178add1155SRico Sonntag     * Find the youngest wife.
15188add1155SRico Sonntag     *
15198add1155SRico Sonntag     * @return string
15208add1155SRico Sonntag     */
15218add1155SRico Sonntag    public function youngestMarriageFemale(): string
15228add1155SRico Sonntag    {
15238add1155SRico Sonntag        return $this->marriageQuery('full', 'ASC', 'F', false);
15248add1155SRico Sonntag    }
15258add1155SRico Sonntag
15268add1155SRico Sonntag    /**
15278add1155SRico Sonntag     * Find the name of the youngest wife.
15288add1155SRico Sonntag     *
15298add1155SRico Sonntag     * @return string
15308add1155SRico Sonntag     */
15318add1155SRico Sonntag    public function youngestMarriageFemaleName(): string
15328add1155SRico Sonntag    {
15338add1155SRico Sonntag        return $this->marriageQuery('name', 'ASC', 'F', false);
15348add1155SRico Sonntag    }
15358add1155SRico Sonntag
15368add1155SRico Sonntag    /**
15378add1155SRico Sonntag     * Find the age of the youngest wife.
15388add1155SRico Sonntag     *
15398add1155SRico Sonntag     * @param string $show_years
15408add1155SRico Sonntag     *
15418add1155SRico Sonntag     * @return string
15428add1155SRico Sonntag     */
15438add1155SRico Sonntag    public function youngestMarriageFemaleAge(string $show_years = ''): string
15448add1155SRico Sonntag    {
15458add1155SRico Sonntag        return $this->marriageQuery('age', 'ASC', 'F', (bool) $show_years);
15468add1155SRico Sonntag    }
15478add1155SRico Sonntag
15488add1155SRico Sonntag    /**
15498add1155SRico Sonntag     * Find the oldest wife.
15508add1155SRico Sonntag     *
15518add1155SRico Sonntag     * @return string
15528add1155SRico Sonntag     */
15538add1155SRico Sonntag    public function oldestMarriageFemale(): string
15548add1155SRico Sonntag    {
15558add1155SRico Sonntag        return $this->marriageQuery('full', 'DESC', 'F', false);
15568add1155SRico Sonntag    }
15578add1155SRico Sonntag
15588add1155SRico Sonntag    /**
15598add1155SRico Sonntag     * Find the name of the oldest wife.
15608add1155SRico Sonntag     *
15618add1155SRico Sonntag     * @return string
15628add1155SRico Sonntag     */
15638add1155SRico Sonntag    public function oldestMarriageFemaleName(): string
15648add1155SRico Sonntag    {
15658add1155SRico Sonntag        return $this->marriageQuery('name', 'DESC', 'F', false);
15668add1155SRico Sonntag    }
15678add1155SRico Sonntag
15688add1155SRico Sonntag    /**
15698add1155SRico Sonntag     * Find the age of the oldest wife.
15708add1155SRico Sonntag     *
15718add1155SRico Sonntag     * @param string $show_years
15728add1155SRico Sonntag     *
15738add1155SRico Sonntag     * @return string
15748add1155SRico Sonntag     */
15758add1155SRico Sonntag    public function oldestMarriageFemaleAge(string $show_years = ''): string
15768add1155SRico Sonntag    {
15778add1155SRico Sonntag        return $this->marriageQuery('age', 'DESC', 'F', (bool) $show_years);
15788add1155SRico Sonntag    }
15798add1155SRico Sonntag
15808add1155SRico Sonntag    /**
15818add1155SRico Sonntag     * Find the youngest husband.
15828add1155SRico Sonntag     *
15838add1155SRico Sonntag     * @return string
15848add1155SRico Sonntag     */
15858add1155SRico Sonntag    public function youngestMarriageMale(): string
15868add1155SRico Sonntag    {
15878add1155SRico Sonntag        return $this->marriageQuery('full', 'ASC', 'M', false);
15888add1155SRico Sonntag    }
15898add1155SRico Sonntag
15908add1155SRico Sonntag    /**
15918add1155SRico Sonntag     * Find the name of the youngest husband.
15928add1155SRico Sonntag     *
15938add1155SRico Sonntag     * @return string
15948add1155SRico Sonntag     */
15958add1155SRico Sonntag    public function youngestMarriageMaleName(): string
15968add1155SRico Sonntag    {
15978add1155SRico Sonntag        return $this->marriageQuery('name', 'ASC', 'M', false);
15988add1155SRico Sonntag    }
15998add1155SRico Sonntag
16008add1155SRico Sonntag    /**
16018add1155SRico Sonntag     * Find the age of the youngest husband.
16028add1155SRico Sonntag     *
16038add1155SRico Sonntag     * @param string $show_years
16048add1155SRico Sonntag     *
16058add1155SRico Sonntag     * @return string
16068add1155SRico Sonntag     */
16078add1155SRico Sonntag    public function youngestMarriageMaleAge(string $show_years = ''): string
16088add1155SRico Sonntag    {
16098add1155SRico Sonntag        return $this->marriageQuery('age', 'ASC', 'M', (bool) $show_years);
16108add1155SRico Sonntag    }
16118add1155SRico Sonntag
16128add1155SRico Sonntag    /**
16138add1155SRico Sonntag     * Find the oldest husband.
16148add1155SRico Sonntag     *
16158add1155SRico Sonntag     * @return string
16168add1155SRico Sonntag     */
16178add1155SRico Sonntag    public function oldestMarriageMale(): string
16188add1155SRico Sonntag    {
16198add1155SRico Sonntag        return $this->marriageQuery('full', 'DESC', 'M', false);
16208add1155SRico Sonntag    }
16218add1155SRico Sonntag
16228add1155SRico Sonntag    /**
16238add1155SRico Sonntag     * Find the name of the oldest husband.
16248add1155SRico Sonntag     *
16258add1155SRico Sonntag     * @return string
16268add1155SRico Sonntag     */
16278add1155SRico Sonntag    public function oldestMarriageMaleName(): string
16288add1155SRico Sonntag    {
16298add1155SRico Sonntag        return $this->marriageQuery('name', 'DESC', 'M', false);
16308add1155SRico Sonntag    }
16318add1155SRico Sonntag
16328add1155SRico Sonntag    /**
16338add1155SRico Sonntag     * Find the age of the oldest husband.
16348add1155SRico Sonntag     *
16358add1155SRico Sonntag     * @param string $show_years
16368add1155SRico Sonntag     *
16378add1155SRico Sonntag     * @return string
16388add1155SRico Sonntag     */
16398add1155SRico Sonntag    public function oldestMarriageMaleAge(string $show_years = ''): string
16408add1155SRico Sonntag    {
16418add1155SRico Sonntag        return $this->marriageQuery('age', 'DESC', 'M', (bool) $show_years);
16428add1155SRico Sonntag    }
16438add1155SRico Sonntag
16448add1155SRico Sonntag    /**
16458add1155SRico Sonntag     * General query on marriages.
16468add1155SRico Sonntag     *
16478add1155SRico Sonntag     * @param int  $year1
16488add1155SRico Sonntag     * @param int  $year2
16498add1155SRico Sonntag     *
1650e6f3d5e2SGreg Roach     * @return Builder
16518add1155SRico Sonntag     */
1652e6f3d5e2SGreg Roach    public function statsMarriageQuery(int $year1 = -1, int $year2 = -1): Builder
16538add1155SRico Sonntag    {
1654e6f3d5e2SGreg Roach        $query = DB::table('dates')
1655e6f3d5e2SGreg Roach            ->where('d_file', '=', $this->tree->id())
1656e6f3d5e2SGreg Roach            ->where('d_fact', '=', 'MARR')
1657e6f3d5e2SGreg Roach            ->select(['d_month', DB::raw('COUNT(*) AS total')])
1658e6f3d5e2SGreg Roach            ->groupBy('d_month');
1659e6f3d5e2SGreg Roach
1660e6f3d5e2SGreg Roach        if ($year1 >= 0 && $year2 >= 0) {
1661e6f3d5e2SGreg Roach            $query->whereBetween('d_year', [$year1, $year2]);
1662e6f3d5e2SGreg Roach        }
1663e6f3d5e2SGreg Roach
1664e6f3d5e2SGreg Roach        return $query;
1665e6f3d5e2SGreg Roach    }
1666e6f3d5e2SGreg Roach
1667e6f3d5e2SGreg Roach    /**
1668e6f3d5e2SGreg Roach     * General query on marriages.
1669e6f3d5e2SGreg Roach     *
1670e6f3d5e2SGreg Roach     * @param int  $year1
1671e6f3d5e2SGreg Roach     * @param int  $year2
1672e6f3d5e2SGreg Roach     *
1673e6f3d5e2SGreg Roach     * @return Builder
1674e6f3d5e2SGreg Roach     */
1675e6f3d5e2SGreg Roach    public function statsFirstMarriageQuery(int $year1 = -1, int $year2 = -1): Builder
1676e6f3d5e2SGreg Roach    {
16772d7289dcSGreg Roach        $query = DB::table('families')
16782d7289dcSGreg Roach            ->join('dates', function (JoinClause $join): void {
16792d7289dcSGreg Roach                $join
16802d7289dcSGreg Roach                    ->on('d_gid', '=', 'f_id')
16812d7289dcSGreg Roach                    ->on('d_file', '=', 'f_file')
16822d7289dcSGreg Roach                    ->where('d_fact', '=', 'MARR')
1683*18e9654eSGreg Roach                    ->whereIn('d_month', ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'])
16842d7289dcSGreg Roach                    ->where('d_julianday2', '<>', 0);
16852d7289dcSGreg Roach            })->join('individuals', function (JoinClause $join): void {
16862d7289dcSGreg Roach                $join
16872d7289dcSGreg Roach                    ->on('i_file', '=', 'f_file');
16882d7289dcSGreg Roach            })
16892d7289dcSGreg Roach            ->where('f_file', '=', $this->tree->id())
16902d7289dcSGreg Roach            ->where(function (Builder $query): void {
16912d7289dcSGreg Roach                $query
16922d7289dcSGreg Roach                    ->whereColumn('i_id', '=', 'f_husb')
16932d7289dcSGreg Roach                    ->orWhereColumn('i_id', '=', 'f_wife');
16942d7289dcSGreg Roach            });
16958add1155SRico Sonntag
16968add1155SRico Sonntag        if ($year1 >= 0 && $year2 >= 0) {
16972d7289dcSGreg Roach            $query->whereBetween('d_year', [$year1, $year2]);
16988add1155SRico Sonntag        }
16998add1155SRico Sonntag
17002d7289dcSGreg Roach        return $query
17012d7289dcSGreg Roach            ->select(['f_id AS fams', 'f_husb', 'f_wife', 'd_julianday2 AS age', 'd_month AS month', 'i_id AS indi'])
17022d7289dcSGreg Roach            ->orderBy('f_id')
17032d7289dcSGreg Roach            ->orderBy('i_id')
1704e6f3d5e2SGreg Roach            ->orderBy('d_julianday2');
17058add1155SRico Sonntag    }
17068add1155SRico Sonntag
17078add1155SRico Sonntag    /**
17088add1155SRico Sonntag     * General query on marriages.
17098add1155SRico Sonntag     *
17108add1155SRico Sonntag     * @param string|null $color_from
17118add1155SRico Sonntag     * @param string|null $color_to
17128add1155SRico Sonntag     *
17138add1155SRico Sonntag     * @return string
17148add1155SRico Sonntag     */
171588de55fdSRico Sonntag    public function statsMarr(string $color_from = null, string $color_to = null): string
17168add1155SRico Sonntag    {
17178add1155SRico Sonntag        return (new ChartMarriage($this->tree))
171888de55fdSRico Sonntag            ->chartMarriage($color_from, $color_to);
17198add1155SRico Sonntag    }
17208add1155SRico Sonntag
17218add1155SRico Sonntag    /**
17228add1155SRico Sonntag     * General divorce query.
17238add1155SRico Sonntag     *
17248add1155SRico Sonntag     * @param string|null $color_from
17258add1155SRico Sonntag     * @param string|null $color_to
17268add1155SRico Sonntag     *
17278add1155SRico Sonntag     * @return string
17288add1155SRico Sonntag     */
172988de55fdSRico Sonntag    public function statsDiv(string $color_from = null, string $color_to = null): string
17308add1155SRico Sonntag    {
17318add1155SRico Sonntag        return (new ChartDivorce($this->tree))
173288de55fdSRico Sonntag            ->chartDivorce($color_from, $color_to);
17338add1155SRico Sonntag    }
17348add1155SRico Sonntag}
1735