xref: /webtrees/app/Statistics/Repository/FamilyRepository.php (revision 242a78626998a767db1568b24919e94ae4b38ac3)
18add1155SRico Sonntag<?php
28add1155SRico Sonntag/**
38add1155SRico Sonntag * webtrees: online genealogy
4*242a7862SGreg 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\Database;
218add1155SRico Sonntaguse Fisharebest\Webtrees\Family;
228add1155SRico Sonntaguse Fisharebest\Webtrees\Functions\FunctionsDate;
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\Statistics\Helper\Sql;
328add1155SRico Sonntaguse Fisharebest\Webtrees\Tree;
3344cdc21eSGreg Roachuse Illuminate\Database\Capsule\Manager as DB;
348add1155SRico Sonntaguse stdClass;
358add1155SRico Sonntag
368add1155SRico Sonntag/**
378add1155SRico Sonntag *
388add1155SRico Sonntag */
398add1155SRico Sonntagclass FamilyRepository
408add1155SRico Sonntag{
418add1155SRico Sonntag    /**
428add1155SRico Sonntag     * @var Tree
438add1155SRico Sonntag     */
448add1155SRico Sonntag    private $tree;
458add1155SRico Sonntag
468add1155SRico Sonntag    /**
478add1155SRico Sonntag     * Constructor.
488add1155SRico Sonntag     *
498add1155SRico Sonntag     * @param Tree $tree
508add1155SRico Sonntag     */
518add1155SRico Sonntag    public function __construct(Tree $tree)
528add1155SRico Sonntag    {
538add1155SRico Sonntag        $this->tree = $tree;
548add1155SRico Sonntag    }
558add1155SRico Sonntag
568add1155SRico Sonntag    /**
578add1155SRico Sonntag     * General query on family.
588add1155SRico Sonntag     *
598add1155SRico Sonntag     * @param string $type
608add1155SRico Sonntag     *
618add1155SRico Sonntag     * @return string
628add1155SRico Sonntag     */
638add1155SRico Sonntag    private function familyQuery(string $type): string
648add1155SRico Sonntag    {
6544cdc21eSGreg Roach        $row = DB::table('families')
6644cdc21eSGreg Roach            ->where('f_file', '=', $this->tree->id())
6744cdc21eSGreg Roach            ->orderBy('f_numchil', 'desc')
6844cdc21eSGreg Roach            ->first();
698add1155SRico Sonntag
7044cdc21eSGreg Roach        if ($row === null) {
718add1155SRico Sonntag            return '';
728add1155SRico Sonntag        }
738add1155SRico Sonntag
7444cdc21eSGreg Roach        /** @var Family $family */
7544cdc21eSGreg Roach        $family = Family::rowMapper()($row);
768add1155SRico Sonntag
7744cdc21eSGreg Roach        if (!$family->canShow()) {
7844cdc21eSGreg Roach            return I18N::translate('This information is private and cannot be shown.');
798add1155SRico Sonntag        }
808add1155SRico Sonntag
818add1155SRico Sonntag        switch ($type) {
828add1155SRico Sonntag            default:
838add1155SRico Sonntag            case 'full':
8444cdc21eSGreg Roach                return $family->formatList();
858add1155SRico Sonntag
8644cdc21eSGreg Roach            case 'size':
8744cdc21eSGreg Roach                return I18N::number((int) $row->f_numchil);
8844cdc21eSGreg Roach
8944cdc21eSGreg Roach            case 'name':
9044cdc21eSGreg Roach                return '<a href="' . e($family->url()) . '">' . $family->getFullName() . '</a>';
9144cdc21eSGreg Roach        }
928add1155SRico Sonntag    }
938add1155SRico Sonntag
948add1155SRico Sonntag    /**
958add1155SRico Sonntag     * Run an SQL query and cache the result.
968add1155SRico Sonntag     *
978add1155SRico Sonntag     * @param string $sql
988add1155SRico Sonntag     *
998add1155SRico Sonntag     * @return stdClass[]
1008add1155SRico Sonntag     */
1018add1155SRico Sonntag    private function runSql($sql): array
1028add1155SRico Sonntag    {
1038add1155SRico Sonntag        return Sql::runSql($sql);
1048add1155SRico Sonntag    }
1058add1155SRico Sonntag
1068add1155SRico Sonntag    /**
1078add1155SRico Sonntag     * Find the family with the most children.
1088add1155SRico Sonntag     *
1098add1155SRico Sonntag     * @return string
1108add1155SRico Sonntag     */
1118add1155SRico Sonntag    public function largestFamily(): string
1128add1155SRico Sonntag    {
1138add1155SRico Sonntag        return $this->familyQuery('full');
1148add1155SRico Sonntag    }
1158add1155SRico Sonntag
1168add1155SRico Sonntag    /**
1178add1155SRico Sonntag     * Find the number of children in the largest family.
1188add1155SRico Sonntag     *
1198add1155SRico Sonntag     * @return string
1208add1155SRico Sonntag     */
1218add1155SRico Sonntag    public function largestFamilySize(): string
1228add1155SRico Sonntag    {
1238add1155SRico Sonntag        return $this->familyQuery('size');
1248add1155SRico Sonntag    }
1258add1155SRico Sonntag
1268add1155SRico Sonntag    /**
1278add1155SRico Sonntag     * Find the family with the most children.
1288add1155SRico Sonntag     *
1298add1155SRico Sonntag     * @return string
1308add1155SRico Sonntag     */
1318add1155SRico Sonntag    public function largestFamilyName(): string
1328add1155SRico Sonntag    {
1338add1155SRico Sonntag        return $this->familyQuery('name');
1348add1155SRico Sonntag    }
1358add1155SRico Sonntag
1368add1155SRico Sonntag    /**
1378add1155SRico Sonntag     * Find the couple with the most grandchildren.
1388add1155SRico Sonntag     *
1398add1155SRico Sonntag     * @param int $total
1408add1155SRico Sonntag     *
1418add1155SRico Sonntag     * @return array
1428add1155SRico Sonntag     */
1438add1155SRico Sonntag    private function topTenGrandFamilyQuery(int $total): array
1448add1155SRico Sonntag    {
1458add1155SRico Sonntag        $rows = $this->runSql(
1468add1155SRico Sonntag            "SELECT COUNT(*) AS tot, f_id AS id" .
1478add1155SRico Sonntag            " FROM `##families`" .
1488add1155SRico Sonntag            " JOIN `##link` AS children ON children.l_file = {$this->tree->id()}" .
1498add1155SRico Sonntag            " JOIN `##link` AS mchildren ON mchildren.l_file = {$this->tree->id()}" .
1508add1155SRico Sonntag            " JOIN `##link` AS gchildren ON gchildren.l_file = {$this->tree->id()}" .
1518add1155SRico Sonntag            " WHERE" .
1528add1155SRico Sonntag            " f_file={$this->tree->id()} AND" .
1538add1155SRico Sonntag            " children.l_from=f_id AND" .
1548add1155SRico Sonntag            " children.l_type='CHIL' AND" .
1558add1155SRico Sonntag            " children.l_to=mchildren.l_from AND" .
1568add1155SRico Sonntag            " mchildren.l_type='FAMS' AND" .
1578add1155SRico Sonntag            " mchildren.l_to=gchildren.l_from AND" .
1588add1155SRico Sonntag            " gchildren.l_type='CHIL'" .
1598add1155SRico Sonntag            " GROUP BY id" .
1608add1155SRico Sonntag            " ORDER BY tot DESC" .
1618add1155SRico Sonntag            " LIMIT " . $total
1628add1155SRico Sonntag        );
1638add1155SRico Sonntag
1648add1155SRico Sonntag        if (!isset($rows[0])) {
1658add1155SRico Sonntag            return [];
1668add1155SRico Sonntag        }
1678add1155SRico Sonntag
1688add1155SRico Sonntag        $top10 = [];
1698add1155SRico Sonntag
1708add1155SRico Sonntag        foreach ($rows as $row) {
1718add1155SRico Sonntag            $family = Family::getInstance($row->id, $this->tree);
1728add1155SRico Sonntag
1738add1155SRico Sonntag            if ($family && $family->canShow()) {
1748add1155SRico Sonntag                $total = (int) $row->tot;
1758add1155SRico Sonntag
1768add1155SRico Sonntag                $top10[] = [
1778add1155SRico Sonntag                    'family' => $family,
1788add1155SRico Sonntag                    'count'  => $total,
1798add1155SRico Sonntag                ];
1808add1155SRico Sonntag            }
1818add1155SRico Sonntag        }
1828add1155SRico Sonntag
1838add1155SRico Sonntag        // TODO
1848add1155SRico Sonntag        //        if (I18N::direction() === 'rtl') {
1858add1155SRico Sonntag        //            $top10 = str_replace([
1868add1155SRico Sonntag        //                '[',
1878add1155SRico Sonntag        //                ']',
1888add1155SRico Sonntag        //                '(',
1898add1155SRico Sonntag        //                ')',
1908add1155SRico Sonntag        //                '+',
1918add1155SRico Sonntag        //            ], [
1928add1155SRico Sonntag        //                '&rlm;[',
1938add1155SRico Sonntag        //                '&rlm;]',
1948add1155SRico Sonntag        //                '&rlm;(',
1958add1155SRico Sonntag        //                '&rlm;)',
1968add1155SRico Sonntag        //                '&rlm;+',
1978add1155SRico Sonntag        //            ], $top10);
1988add1155SRico Sonntag        //        }
1998add1155SRico Sonntag
2008add1155SRico Sonntag        return $top10;
2018add1155SRico Sonntag    }
2028add1155SRico Sonntag
2038add1155SRico Sonntag    /**
2048add1155SRico Sonntag     * Find the couple with the most grandchildren.
2058add1155SRico Sonntag     *
2068add1155SRico Sonntag     * @param int $total
2078add1155SRico Sonntag     *
2088add1155SRico Sonntag     * @return string
2098add1155SRico Sonntag     */
2108add1155SRico Sonntag    public function topTenLargestGrandFamily(int $total = 10): string
2118add1155SRico Sonntag    {
2128add1155SRico Sonntag        $records = $this->topTenGrandFamilyQuery($total);
2138add1155SRico Sonntag
2148add1155SRico Sonntag        return view(
2158add1155SRico Sonntag            'statistics/families/top10-nolist-grand',
2168add1155SRico Sonntag            [
2178add1155SRico Sonntag                'records' => $records,
2188add1155SRico Sonntag            ]
2198add1155SRico Sonntag        );
2208add1155SRico Sonntag    }
2218add1155SRico Sonntag
2228add1155SRico Sonntag    /**
2238add1155SRico Sonntag     * Find the couple with the most grandchildren.
2248add1155SRico Sonntag     *
2258add1155SRico Sonntag     * @param int $total
2268add1155SRico Sonntag     *
2278add1155SRico Sonntag     * @return string
2288add1155SRico Sonntag     */
2298add1155SRico Sonntag    public function topTenLargestGrandFamilyList(int $total = 10): string
2308add1155SRico Sonntag    {
2318add1155SRico Sonntag        $records = $this->topTenGrandFamilyQuery($total);
2328add1155SRico Sonntag
2338add1155SRico Sonntag        return view(
2348add1155SRico Sonntag            'statistics/families/top10-list-grand',
2358add1155SRico Sonntag            [
2368add1155SRico Sonntag                'records' => $records,
2378add1155SRico Sonntag            ]
2388add1155SRico Sonntag        );
2398add1155SRico Sonntag    }
2408add1155SRico Sonntag
2418add1155SRico Sonntag    /**
2428add1155SRico Sonntag     * Find the families with no children.
2438add1155SRico Sonntag     *
2448add1155SRico Sonntag     * @return int
2458add1155SRico Sonntag     */
2468add1155SRico Sonntag    private function noChildrenFamiliesQuery(): int
2478add1155SRico Sonntag    {
2488add1155SRico Sonntag        $rows = $this->runSql(
2498add1155SRico Sonntag            " SELECT COUNT(*) AS tot" .
2508add1155SRico Sonntag            " FROM  `##families`" .
2518add1155SRico Sonntag            " WHERE f_numchil = 0 AND f_file = {$this->tree->id()}"
2528add1155SRico Sonntag        );
2538add1155SRico Sonntag
2548add1155SRico Sonntag        return (int) $rows[0]->tot;
2558add1155SRico Sonntag    }
2568add1155SRico Sonntag
2578add1155SRico Sonntag    /**
2588add1155SRico Sonntag     * Find the families with no children.
2598add1155SRico Sonntag     *
2608add1155SRico Sonntag     * @return string
2618add1155SRico Sonntag     */
2628add1155SRico Sonntag    public function noChildrenFamilies(): string
2638add1155SRico Sonntag    {
2648add1155SRico Sonntag        return I18N::number($this->noChildrenFamiliesQuery());
2658add1155SRico Sonntag    }
2668add1155SRico Sonntag
2678add1155SRico Sonntag    /**
2688add1155SRico Sonntag     * Find the families with no children.
2698add1155SRico Sonntag     *
2708add1155SRico Sonntag     * @param string $type
2718add1155SRico Sonntag     *
2728add1155SRico Sonntag     * @return string
2738add1155SRico Sonntag     */
2748add1155SRico Sonntag    public function noChildrenFamiliesList($type = 'list'): string
2758add1155SRico Sonntag    {
2768add1155SRico Sonntag        $rows = $this->runSql(
2778add1155SRico Sonntag            " SELECT f_id AS family" .
2788add1155SRico Sonntag            " FROM `##families` AS fam" .
2798add1155SRico Sonntag            " WHERE f_numchil = 0 AND fam.f_file = {$this->tree->id()}"
2808add1155SRico Sonntag        );
2818add1155SRico Sonntag
2828add1155SRico Sonntag        if (!isset($rows[0])) {
2838add1155SRico Sonntag            return '';
2848add1155SRico Sonntag        }
2858add1155SRico Sonntag
2868add1155SRico Sonntag        $top10 = [];
2878add1155SRico Sonntag        foreach ($rows as $row) {
2888add1155SRico Sonntag            $family = Family::getInstance($row->family, $this->tree);
2898add1155SRico Sonntag            if ($family->canShow()) {
2908add1155SRico Sonntag                if ($type === 'list') {
2918add1155SRico Sonntag                    $top10[] = '<li><a href="' . e($family->url()) . '">' . $family->getFullName() . '</a></li>';
2928add1155SRico Sonntag                } else {
2938add1155SRico Sonntag                    $top10[] = '<a href="' . e($family->url()) . '">' . $family->getFullName() . '</a>';
2948add1155SRico Sonntag                }
2958add1155SRico Sonntag            }
2968add1155SRico Sonntag        }
2978add1155SRico Sonntag
2988add1155SRico Sonntag        if ($type === 'list') {
2998add1155SRico Sonntag            $top10 = implode('', $top10);
3008add1155SRico Sonntag        } else {
3018add1155SRico Sonntag            $top10 = implode('; ', $top10);
3028add1155SRico Sonntag        }
3038add1155SRico Sonntag
3048add1155SRico Sonntag        if (I18N::direction() === 'rtl') {
3058add1155SRico Sonntag            $top10 = str_replace([
3068add1155SRico Sonntag                '[',
3078add1155SRico Sonntag                ']',
3088add1155SRico Sonntag                '(',
3098add1155SRico Sonntag                ')',
3108add1155SRico Sonntag                '+',
3118add1155SRico Sonntag            ], [
3128add1155SRico Sonntag                '&rlm;[',
3138add1155SRico Sonntag                '&rlm;]',
3148add1155SRico Sonntag                '&rlm;(',
3158add1155SRico Sonntag                '&rlm;)',
3168add1155SRico Sonntag                '&rlm;+',
3178add1155SRico Sonntag            ], $top10);
3188add1155SRico Sonntag        }
3198add1155SRico Sonntag        if ($type === 'list') {
3208add1155SRico Sonntag            return '<ul>' . $top10 . '</ul>';
3218add1155SRico Sonntag        }
3228add1155SRico Sonntag
3238add1155SRico Sonntag        return $top10;
3248add1155SRico Sonntag    }
3258add1155SRico Sonntag
3268add1155SRico Sonntag    /**
3278add1155SRico Sonntag     * Create a chart of children with no families.
3288add1155SRico Sonntag     *
3298add1155SRico Sonntag     * @param int $year1
3308add1155SRico Sonntag     * @param int $year2
3318add1155SRico Sonntag     *
3328add1155SRico Sonntag     * @return string
3338add1155SRico Sonntag     */
33488de55fdSRico Sonntag    public function chartNoChildrenFamilies(int $year1 = -1, int $year2 = -1): string
3358add1155SRico Sonntag    {
3368add1155SRico Sonntag        $no_child_fam = $this->noChildrenFamiliesQuery();
3378add1155SRico Sonntag
3388add1155SRico Sonntag        return (new ChartNoChildrenFamilies($this->tree))
33988de55fdSRico Sonntag            ->chartNoChildrenFamilies($no_child_fam, $year1, $year2);
3408add1155SRico Sonntag    }
3418add1155SRico Sonntag
3428add1155SRico Sonntag    /**
3438add1155SRico Sonntag     * Returns the ages between siblings.
3448add1155SRico Sonntag     *
3458add1155SRico Sonntag     * @param int $total The total number of records to query
3468add1155SRico Sonntag     *
3478add1155SRico Sonntag     * @return array
3488add1155SRico Sonntag     */
3498add1155SRico Sonntag    private function ageBetweenSiblingsQuery(int $total): array
3508add1155SRico Sonntag    {
3518add1155SRico Sonntag        $rows = $this->runSql(
3528add1155SRico Sonntag            " SELECT DISTINCT" .
3538add1155SRico Sonntag            " link1.l_from AS family," .
3548add1155SRico Sonntag            " link1.l_to AS ch1," .
3558add1155SRico Sonntag            " link2.l_to AS ch2," .
3568add1155SRico Sonntag            " child1.d_julianday2-child2.d_julianday2 AS age" .
3578add1155SRico Sonntag            " FROM `##link` AS link1" .
3588add1155SRico Sonntag            " LEFT JOIN `##dates` AS child1 ON child1.d_file = {$this->tree->id()}" .
3598add1155SRico Sonntag            " LEFT JOIN `##dates` AS child2 ON child2.d_file = {$this->tree->id()}" .
3608add1155SRico Sonntag            " LEFT JOIN `##link` AS link2 ON link2.l_file = {$this->tree->id()}" .
3618add1155SRico Sonntag            " WHERE" .
3628add1155SRico Sonntag            " link1.l_file = {$this->tree->id()} AND" .
3638add1155SRico Sonntag            " link1.l_from = link2.l_from AND" .
3648add1155SRico Sonntag            " link1.l_type = 'CHIL' AND" .
3658add1155SRico Sonntag            " child1.d_gid = link1.l_to AND" .
3668add1155SRico Sonntag            " child1.d_fact = 'BIRT' AND" .
3678add1155SRico Sonntag            " link2.l_type = 'CHIL' AND" .
3688add1155SRico Sonntag            " child2.d_gid = link2.l_to AND" .
3698add1155SRico Sonntag            " child2.d_fact = 'BIRT' AND" .
3708add1155SRico Sonntag            " child1.d_julianday2 > child2.d_julianday2 AND" .
3718add1155SRico Sonntag            " child2.d_julianday2 <> 0 AND" .
3728add1155SRico Sonntag            " child1.d_gid <> child2.d_gid" .
3738add1155SRico Sonntag            " ORDER BY age DESC" .
3748add1155SRico Sonntag            " LIMIT " . $total
3758add1155SRico Sonntag        );
3768add1155SRico Sonntag
3778add1155SRico Sonntag        if (!isset($rows[0])) {
3788add1155SRico Sonntag            return [];
3798add1155SRico Sonntag        }
3808add1155SRico Sonntag
3818add1155SRico Sonntag        return $rows;
3828add1155SRico Sonntag    }
3838add1155SRico Sonntag
3848add1155SRico Sonntag    /**
3858add1155SRico Sonntag     * Returns the calculated age the time of event.
3868add1155SRico Sonntag     *
3878add1155SRico Sonntag     * @param int $age The age from the database record
3888add1155SRico Sonntag     *
3898add1155SRico Sonntag     * @return string
3908add1155SRico Sonntag     */
3918add1155SRico Sonntag    private function calculateAge(int $age): string
3928add1155SRico Sonntag    {
3938add1155SRico Sonntag        if ((int) ($age / 365.25) > 0) {
3948add1155SRico Sonntag            $result = (int) ($age / 365.25) . 'y';
3958add1155SRico Sonntag        } elseif ((int) ($age / 30.4375) > 0) {
3968add1155SRico Sonntag            $result = (int) ($age / 30.4375) . 'm';
3978add1155SRico Sonntag        } else {
3988add1155SRico Sonntag            $result = $age . 'd';
3998add1155SRico Sonntag        }
4008add1155SRico Sonntag
4018add1155SRico Sonntag        return FunctionsDate::getAgeAtEvent($result);
4028add1155SRico Sonntag    }
4038add1155SRico Sonntag
4048add1155SRico Sonntag    /**
4058add1155SRico Sonntag     * Find the ages between siblings.
4068add1155SRico Sonntag     *
4078add1155SRico Sonntag     * @param int $total The total number of records to query
4088add1155SRico Sonntag     *
4098add1155SRico Sonntag     * @return array
4108add1155SRico Sonntag     * @throws \Exception
4118add1155SRico Sonntag     */
4128add1155SRico Sonntag    private function ageBetweenSiblingsNoList(int $total): array
4138add1155SRico Sonntag    {
4148add1155SRico Sonntag        $rows = $this->ageBetweenSiblingsQuery($total);
4158add1155SRico Sonntag
4168add1155SRico Sonntag        foreach ($rows as $fam) {
4178add1155SRico Sonntag            $family = Family::getInstance($fam->family, $this->tree);
4188add1155SRico Sonntag            $child1 = Individual::getInstance($fam->ch1, $this->tree);
4198add1155SRico Sonntag            $child2 = Individual::getInstance($fam->ch2, $this->tree);
4208add1155SRico Sonntag
4218add1155SRico Sonntag            if ($child1->canShow() && $child2->canShow()) {
4228add1155SRico Sonntag                // ! Single array (no list)
4238add1155SRico Sonntag                return [
4248add1155SRico Sonntag                    'child1' => $child1,
4258add1155SRico Sonntag                    'child2' => $child2,
4268add1155SRico Sonntag                    'family' => $family,
4278add1155SRico Sonntag                    'age'    => $this->calculateAge((int) $fam->age),
4288add1155SRico Sonntag                ];
4298add1155SRico Sonntag            }
4308add1155SRico Sonntag        }
4318add1155SRico Sonntag
4328add1155SRico Sonntag        return [];
4338add1155SRico Sonntag    }
4348add1155SRico Sonntag
4358add1155SRico Sonntag    /**
4368add1155SRico Sonntag     * Find the ages between siblings.
4378add1155SRico Sonntag     *
4388add1155SRico Sonntag     * @param int  $total The total number of records to query
4398add1155SRico Sonntag     * @param bool $one   Include each family only once if true
4408add1155SRico Sonntag     *
4418add1155SRico Sonntag     * @return array
4428add1155SRico Sonntag     * @throws \Exception
4438add1155SRico Sonntag     */
4448add1155SRico Sonntag    private function ageBetweenSiblingsList(int $total, bool $one): array
4458add1155SRico Sonntag    {
4468add1155SRico Sonntag        $rows  = $this->ageBetweenSiblingsQuery($total);
4478add1155SRico Sonntag        $top10 = [];
4488add1155SRico Sonntag        $dist  = [];
4498add1155SRico Sonntag
4508add1155SRico Sonntag        foreach ($rows as $fam) {
4518add1155SRico Sonntag            $family = Family::getInstance($fam->family, $this->tree);
4528add1155SRico Sonntag            $child1 = Individual::getInstance($fam->ch1, $this->tree);
4538add1155SRico Sonntag            $child2 = Individual::getInstance($fam->ch2, $this->tree);
4548add1155SRico Sonntag
4558add1155SRico Sonntag            $age = $this->calculateAge((int) $fam->age);
4568add1155SRico Sonntag
4578add1155SRico Sonntag            if ($one && !\in_array($fam->family, $dist, true)) {
4588add1155SRico Sonntag                if ($child1->canShow() && $child2->canShow()) {
4598add1155SRico Sonntag                    $top10[] = [
4608add1155SRico Sonntag                        'child1' => $child1,
4618add1155SRico Sonntag                        'child2' => $child2,
4628add1155SRico Sonntag                        'family' => $family,
4638add1155SRico Sonntag                        'age'    => $age,
4648add1155SRico Sonntag                    ];
4658add1155SRico Sonntag
4668add1155SRico Sonntag                    $dist[] = $fam->family;
4678add1155SRico Sonntag                }
4688add1155SRico Sonntag            } elseif (!$one && $child1->canShow() && $child2->canShow()) {
4698add1155SRico Sonntag                $top10[] = [
4708add1155SRico Sonntag                    'child1' => $child1,
4718add1155SRico Sonntag                    'child2' => $child2,
4728add1155SRico Sonntag                    'family' => $family,
4738add1155SRico Sonntag                    'age'    => $age,
4748add1155SRico Sonntag                ];
4758add1155SRico Sonntag            }
4768add1155SRico Sonntag        }
4778add1155SRico Sonntag
4788add1155SRico Sonntag        // TODO
4798add1155SRico Sonntag        //        if (I18N::direction() === 'rtl') {
4808add1155SRico Sonntag        //            $top10 = str_replace([
4818add1155SRico Sonntag        //                '[',
4828add1155SRico Sonntag        //                ']',
4838add1155SRico Sonntag        //                '(',
4848add1155SRico Sonntag        //                ')',
4858add1155SRico Sonntag        //                '+',
4868add1155SRico Sonntag        //            ], [
4878add1155SRico Sonntag        //                '&rlm;[',
4888add1155SRico Sonntag        //                '&rlm;]',
4898add1155SRico Sonntag        //                '&rlm;(',
4908add1155SRico Sonntag        //                '&rlm;)',
4918add1155SRico Sonntag        //                '&rlm;+',
4928add1155SRico Sonntag        //            ], $top10);
4938add1155SRico Sonntag        //        }
4948add1155SRico Sonntag
4958add1155SRico Sonntag        return $top10;
4968add1155SRico Sonntag    }
4978add1155SRico Sonntag
4988add1155SRico Sonntag    /**
4998add1155SRico Sonntag     * Find the ages between siblings.
5008add1155SRico Sonntag     *
5018add1155SRico Sonntag     * @param int $total The total number of records to query
5028add1155SRico Sonntag     *
5038add1155SRico Sonntag     * @return string
5048add1155SRico Sonntag     */
5058add1155SRico Sonntag    private function ageBetweenSiblingsAge(int $total): string
5068add1155SRico Sonntag    {
5078add1155SRico Sonntag        $rows = $this->ageBetweenSiblingsQuery($total);
5088add1155SRico Sonntag
5098add1155SRico Sonntag        foreach ($rows as $fam) {
5108add1155SRico Sonntag            return $this->calculateAge((int) $fam->age);
5118add1155SRico Sonntag        }
5128add1155SRico Sonntag
5138add1155SRico Sonntag        return '';
5148add1155SRico Sonntag    }
5158add1155SRico Sonntag
5168add1155SRico Sonntag    /**
5178add1155SRico Sonntag     * Find the ages between siblings.
5188add1155SRico Sonntag     *
5198add1155SRico Sonntag     * @param int $total The total number of records to query
5208add1155SRico Sonntag     *
5218add1155SRico Sonntag     * @return string
5228add1155SRico Sonntag     * @throws \Exception
5238add1155SRico Sonntag     */
5248add1155SRico Sonntag    private function ageBetweenSiblingsName(int $total): string
5258add1155SRico Sonntag    {
5268add1155SRico Sonntag        $rows = $this->ageBetweenSiblingsQuery($total);
5278add1155SRico Sonntag
5288add1155SRico Sonntag        foreach ($rows as $fam) {
5298add1155SRico Sonntag            $family = Family::getInstance($fam->family, $this->tree);
5308add1155SRico Sonntag            $child1 = Individual::getInstance($fam->ch1, $this->tree);
5318add1155SRico Sonntag            $child2 = Individual::getInstance($fam->ch2, $this->tree);
5328add1155SRico Sonntag
5338add1155SRico Sonntag            if ($child1->canShow() && $child2->canShow()) {
5348add1155SRico Sonntag                $return = '<a href="' . e($child2->url()) . '">' . $child2->getFullName() . '</a> ';
5358add1155SRico Sonntag                $return .= I18N::translate('and') . ' ';
5368add1155SRico Sonntag                $return .= '<a href="' . e($child1->url()) . '">' . $child1->getFullName() . '</a>';
5378add1155SRico Sonntag                $return .= ' <a href="' . e($family->url()) . '">[' . I18N::translate('View this family') . ']</a>';
5388add1155SRico Sonntag            } else {
5398add1155SRico Sonntag                $return = I18N::translate('This information is private and cannot be shown.');
5408add1155SRico Sonntag            }
5418add1155SRico Sonntag
5428add1155SRico Sonntag            return $return;
5438add1155SRico Sonntag        }
5448add1155SRico Sonntag
5458add1155SRico Sonntag        return '';
5468add1155SRico Sonntag    }
5478add1155SRico Sonntag
5488add1155SRico Sonntag    /**
5498add1155SRico Sonntag     * Find the names of siblings with the widest age gap.
5508add1155SRico Sonntag     *
5518add1155SRico Sonntag     * @param int $total
5528add1155SRico Sonntag     *
5538add1155SRico Sonntag     * @return string
5548add1155SRico Sonntag     */
5558add1155SRico Sonntag    public function topAgeBetweenSiblingsName(int $total = 10): string
5568add1155SRico Sonntag    {
5578add1155SRico Sonntag        return $this->ageBetweenSiblingsName($total);
5588add1155SRico Sonntag    }
5598add1155SRico Sonntag
5608add1155SRico Sonntag    /**
5618add1155SRico Sonntag     * Find the widest age gap between siblings.
5628add1155SRico Sonntag     *
5638add1155SRico Sonntag     * @param int $total
5648add1155SRico Sonntag     *
5658add1155SRico Sonntag     * @return string
5668add1155SRico Sonntag     */
5678add1155SRico Sonntag    public function topAgeBetweenSiblings(int $total = 10): string
5688add1155SRico Sonntag    {
5698add1155SRico Sonntag        return $this->ageBetweenSiblingsAge($total);
5708add1155SRico Sonntag    }
5718add1155SRico Sonntag
5728add1155SRico Sonntag    /**
5738add1155SRico Sonntag     * Find the name of siblings with the widest age gap.
5748add1155SRico Sonntag     *
5758add1155SRico Sonntag     * @param int $total
5768add1155SRico Sonntag     *
5778add1155SRico Sonntag     * @return string
5788add1155SRico Sonntag     */
5798add1155SRico Sonntag    public function topAgeBetweenSiblingsFullName(int $total = 10): string
5808add1155SRico Sonntag    {
5818add1155SRico Sonntag        $record = $this->ageBetweenSiblingsNoList($total);
5828add1155SRico Sonntag
583cb2263dcSGreg Roach        if (empty($record)) {
584dd7dd2a1SRico Sonntag            return I18N::translate('This information is not available.');
585cb2263dcSGreg Roach        }
586cb2263dcSGreg Roach
587cb2263dcSGreg Roach        return view('statistics/families/top10-nolist-age', [
5888add1155SRico Sonntag            'record' => $record,
589cb2263dcSGreg Roach        ]);
5908add1155SRico Sonntag    }
5918add1155SRico Sonntag
5928add1155SRico Sonntag    /**
5938add1155SRico Sonntag     * Find the siblings with the widest age gaps.
5948add1155SRico Sonntag     *
5958add1155SRico Sonntag     * @param int    $total
5968add1155SRico Sonntag     * @param string $one
5978add1155SRico Sonntag     *
5988add1155SRico Sonntag     * @return string
5998add1155SRico Sonntag     */
6008add1155SRico Sonntag    public function topAgeBetweenSiblingsList(int $total = 10, string $one = ''): string
6018add1155SRico Sonntag    {
6028add1155SRico Sonntag        $records = $this->ageBetweenSiblingsList($total, (bool) $one);
6038add1155SRico Sonntag
604cb2263dcSGreg Roach        return view('statistics/families/top10-list-age', [
6058add1155SRico Sonntag            'records' => $records,
606cb2263dcSGreg Roach        ]);
6078add1155SRico Sonntag    }
6088add1155SRico Sonntag
6098add1155SRico Sonntag    /**
6108add1155SRico Sonntag     * General query on familes/children.
6118add1155SRico Sonntag     *
6128add1155SRico Sonntag     * @param string $sex
6138add1155SRico Sonntag     * @param int    $year1
6148add1155SRico Sonntag     * @param int    $year2
6158add1155SRico Sonntag     *
6168add1155SRico Sonntag     * @return stdClass[]
6178add1155SRico Sonntag     */
6188add1155SRico Sonntag    public function statsChildrenQuery(string $sex = 'BOTH', int $year1 = -1, int $year2 = -1): array
6198add1155SRico Sonntag    {
6208add1155SRico Sonntag        if ($sex === 'M') {
6218add1155SRico Sonntag            $sql =
6228add1155SRico Sonntag                "SELECT num, COUNT(*) AS total FROM " .
6238add1155SRico Sonntag                "(SELECT count(i_sex) AS num FROM `##link` " .
6248add1155SRico Sonntag                "LEFT OUTER JOIN `##individuals` " .
6258add1155SRico Sonntag                "ON l_from=i_id AND l_file=i_file AND i_sex='M' AND l_type='FAMC' " .
6268add1155SRico Sonntag                "JOIN `##families` ON f_file=l_file AND f_id=l_to WHERE f_file={$this->tree->id()} GROUP BY l_to" .
6278add1155SRico Sonntag                ") boys" .
6288add1155SRico Sonntag                " GROUP BY num" .
6298add1155SRico Sonntag                " ORDER BY num";
6308add1155SRico Sonntag        } elseif ($sex === 'F') {
6318add1155SRico Sonntag            $sql =
6328add1155SRico Sonntag                "SELECT num, COUNT(*) AS total FROM " .
6338add1155SRico Sonntag                "(SELECT count(i_sex) AS num FROM `##link` " .
6348add1155SRico Sonntag                "LEFT OUTER JOIN `##individuals` " .
6358add1155SRico Sonntag                "ON l_from=i_id AND l_file=i_file AND i_sex='F' AND l_type='FAMC' " .
6368add1155SRico Sonntag                "JOIN `##families` ON f_file=l_file AND f_id=l_to WHERE f_file={$this->tree->id()} GROUP BY l_to" .
6378add1155SRico Sonntag                ") girls" .
6388add1155SRico Sonntag                " GROUP BY num" .
6398add1155SRico Sonntag                " ORDER BY num";
6408add1155SRico Sonntag        } else {
6418add1155SRico Sonntag            $sql = "SELECT f_numchil, COUNT(*) AS total FROM `##families` ";
6428add1155SRico Sonntag
6438add1155SRico Sonntag            if ($year1 >= 0 && $year2 >= 0) {
6448add1155SRico Sonntag                $sql .=
6458add1155SRico Sonntag                    "AS fam LEFT JOIN `##dates` AS married ON married.d_file = {$this->tree->id()}"
6468add1155SRico Sonntag                    . " WHERE"
6478add1155SRico Sonntag                    . " married.d_gid = fam.f_id AND"
6488add1155SRico Sonntag                    . " fam.f_file = {$this->tree->id()} AND"
6498add1155SRico Sonntag                    . " married.d_fact = 'MARR' AND"
6508add1155SRico Sonntag                    . " married.d_year BETWEEN '{$year1}' AND '{$year2}'";
6518add1155SRico Sonntag            } else {
6528add1155SRico Sonntag                $sql .= "WHERE f_file={$this->tree->id()}";
6538add1155SRico Sonntag            }
6548add1155SRico Sonntag
6558add1155SRico Sonntag            $sql .= ' GROUP BY f_numchil';
6568add1155SRico Sonntag        }
6578add1155SRico Sonntag
6588add1155SRico Sonntag        return $this->runSql($sql);
6598add1155SRico Sonntag    }
6608add1155SRico Sonntag
6618add1155SRico Sonntag    /**
6628add1155SRico Sonntag     * Genearl query on families/children.
6638add1155SRico Sonntag     *
6648add1155SRico Sonntag     * @return string
6658add1155SRico Sonntag     */
66688de55fdSRico Sonntag    public function statsChildren(): string
6678add1155SRico Sonntag    {
6688add1155SRico Sonntag        return (new ChartChildren($this->tree))
66988de55fdSRico Sonntag            ->chartChildren();
6708add1155SRico Sonntag    }
6718add1155SRico Sonntag
6728add1155SRico Sonntag    /**
6738add1155SRico Sonntag     * Count the total children.
6748add1155SRico Sonntag     *
6758add1155SRico Sonntag     * @return string
6768add1155SRico Sonntag     */
6778add1155SRico Sonntag    public function totalChildren(): string
6788add1155SRico Sonntag    {
6798add1155SRico Sonntag        $total = (int) Database::prepare(
6808add1155SRico Sonntag            "SELECT SUM(f_numchil) FROM `##families` WHERE f_file = :tree_id"
6818add1155SRico Sonntag        )->execute([
6828add1155SRico Sonntag            'tree_id' => $this->tree->id(),
6838add1155SRico Sonntag        ])->fetchOne();
6848add1155SRico Sonntag
6858add1155SRico Sonntag        return I18N::number($total);
6868add1155SRico Sonntag    }
6878add1155SRico Sonntag
6888add1155SRico Sonntag    /**
6898add1155SRico Sonntag     * Find the average number of children in families.
6908add1155SRico Sonntag     *
6918add1155SRico Sonntag     * @return string
6928add1155SRico Sonntag     */
6938add1155SRico Sonntag    public function averageChildren(): string
6948add1155SRico Sonntag    {
6958add1155SRico Sonntag        $average = (float) Database::prepare(
6968add1155SRico Sonntag            "SELECT AVG(f_numchil) AS tot FROM `##families` WHERE f_file = :tree_id"
6978add1155SRico Sonntag        )->execute([
6988add1155SRico Sonntag            'tree_id' => $this->tree->id(),
6998add1155SRico Sonntag        ])->fetchOne();
7008add1155SRico Sonntag
7018add1155SRico Sonntag        return I18N::number($average, 2);
7028add1155SRico Sonntag    }
7038add1155SRico Sonntag
7048add1155SRico Sonntag    /**
7058add1155SRico Sonntag     * General query on families.
7068add1155SRico Sonntag     *
7078add1155SRico Sonntag     * @param int $total
7088add1155SRico Sonntag     *
7098add1155SRico Sonntag     * @return array
7108add1155SRico Sonntag     */
7118add1155SRico Sonntag    private function topTenFamilyQuery(int $total): array
7128add1155SRico Sonntag    {
7138add1155SRico Sonntag        $rows = $this->runSql(
7148add1155SRico Sonntag            "SELECT f_numchil AS tot, f_id AS id" .
7158add1155SRico Sonntag            " FROM `##families`" .
7168add1155SRico Sonntag            " WHERE" .
7178add1155SRico Sonntag            " f_file={$this->tree->id()}" .
7188add1155SRico Sonntag            " ORDER BY tot DESC" .
7198add1155SRico Sonntag            " LIMIT " . $total
7208add1155SRico Sonntag        );
7218add1155SRico Sonntag
7228add1155SRico Sonntag        if (empty($rows)) {
7238add1155SRico Sonntag            return [];
7248add1155SRico Sonntag        }
7258add1155SRico Sonntag
7268add1155SRico Sonntag        $top10 = [];
7278add1155SRico Sonntag        foreach ($rows as $row) {
7288add1155SRico Sonntag            $family = Family::getInstance($row->id, $this->tree);
7298add1155SRico Sonntag
7308add1155SRico Sonntag            if ($family && $family->canShow()) {
7318add1155SRico Sonntag                $top10[] = [
7328add1155SRico Sonntag                    'family' => $family,
7338add1155SRico Sonntag                    'count'  => (int) $row->tot,
7348add1155SRico Sonntag                ];
7358add1155SRico Sonntag            }
7368add1155SRico Sonntag        }
7378add1155SRico Sonntag
7388add1155SRico Sonntag        // TODO
7398add1155SRico Sonntag        //        if (I18N::direction() === 'rtl') {
7408add1155SRico Sonntag        //            $top10 = str_replace([
7418add1155SRico Sonntag        //                '[',
7428add1155SRico Sonntag        //                ']',
7438add1155SRico Sonntag        //                '(',
7448add1155SRico Sonntag        //                ')',
7458add1155SRico Sonntag        //                '+',
7468add1155SRico Sonntag        //            ], [
7478add1155SRico Sonntag        //                '&rlm;[',
7488add1155SRico Sonntag        //                '&rlm;]',
7498add1155SRico Sonntag        //                '&rlm;(',
7508add1155SRico Sonntag        //                '&rlm;)',
7518add1155SRico Sonntag        //                '&rlm;+',
7528add1155SRico Sonntag        //            ], $top10);
7538add1155SRico Sonntag        //        }
7548add1155SRico Sonntag
7558add1155SRico Sonntag        return $top10;
7568add1155SRico Sonntag    }
7578add1155SRico Sonntag
7588add1155SRico Sonntag    /**
7598add1155SRico Sonntag     * The the families with the most children.
7608add1155SRico Sonntag     *
7618add1155SRico Sonntag     * @param int $total
7628add1155SRico Sonntag     *
7638add1155SRico Sonntag     * @return string
7648add1155SRico Sonntag     */
7658add1155SRico Sonntag    public function topTenLargestFamily(int $total = 10): string
7668add1155SRico Sonntag    {
7678add1155SRico Sonntag        $records = $this->topTenFamilyQuery($total);
7688add1155SRico Sonntag
7698add1155SRico Sonntag        return view(
7708add1155SRico Sonntag            'statistics/families/top10-nolist',
7718add1155SRico Sonntag            [
7728add1155SRico Sonntag                'records' => $records,
7738add1155SRico Sonntag            ]
7748add1155SRico Sonntag        );
7758add1155SRico Sonntag    }
7768add1155SRico Sonntag
7778add1155SRico Sonntag    /**
7788add1155SRico Sonntag     * Find the families with the most children.
7798add1155SRico Sonntag     *
7808add1155SRico Sonntag     * @param int $total
7818add1155SRico Sonntag     *
7828add1155SRico Sonntag     * @return string
7838add1155SRico Sonntag     */
7848add1155SRico Sonntag    public function topTenLargestFamilyList(int $total = 10): string
7858add1155SRico Sonntag    {
7868add1155SRico Sonntag        $records = $this->topTenFamilyQuery($total);
7878add1155SRico Sonntag
7888add1155SRico Sonntag        return view(
7898add1155SRico Sonntag            'statistics/families/top10-list',
7908add1155SRico Sonntag            [
7918add1155SRico Sonntag                'records' => $records,
7928add1155SRico Sonntag            ]
7938add1155SRico Sonntag        );
7948add1155SRico Sonntag    }
7958add1155SRico Sonntag
7968add1155SRico Sonntag    /**
7978add1155SRico Sonntag     * Create a chart of the largest families.
7988add1155SRico Sonntag     *
7998add1155SRico Sonntag     * @param string|null $color_from
8008add1155SRico Sonntag     * @param string|null $color_to
8018add1155SRico Sonntag     * @param int         $total
8028add1155SRico Sonntag     *
8038add1155SRico Sonntag     * @return string
8048add1155SRico Sonntag     */
8058add1155SRico Sonntag    public function chartLargestFamilies(
8068add1155SRico Sonntag        string $color_from = null,
8078add1155SRico Sonntag        string $color_to = null,
8088add1155SRico Sonntag        int $total = 10
809e2cbf57aSGreg Roach    ): string {
8108add1155SRico Sonntag        return (new ChartFamilyLargest($this->tree))
81188de55fdSRico Sonntag            ->chartLargestFamilies($color_from, $color_to, $total);
8128add1155SRico Sonntag    }
8138add1155SRico Sonntag
8148add1155SRico Sonntag    /**
8158add1155SRico Sonntag     * Find the month in the year of the birth of the first child.
8168add1155SRico Sonntag     *
8178add1155SRico Sonntag     * @param bool $sex
8188add1155SRico Sonntag     *
8198add1155SRico Sonntag     * @return stdClass[]
8208add1155SRico Sonntag     */
8218add1155SRico Sonntag    public function monthFirstChildQuery(bool $sex = false): array
8228add1155SRico Sonntag    {
8238add1155SRico Sonntag        if ($sex) {
8248add1155SRico Sonntag            $sql_sex1 = ', i_sex';
8258add1155SRico Sonntag            $sql_sex2 = " JOIN `##individuals` AS child ON child1.d_file = i_file AND child1.d_gid = child.i_id ";
8268add1155SRico Sonntag        } else {
8278add1155SRico Sonntag            $sql_sex1 = '';
8288add1155SRico Sonntag            $sql_sex2 = '';
8298add1155SRico Sonntag        }
8308add1155SRico Sonntag
8318add1155SRico Sonntag        $sql =
8328add1155SRico Sonntag            "SELECT d_month{$sql_sex1}, COUNT(*) AS total " .
8338add1155SRico Sonntag            "FROM (" .
8348add1155SRico Sonntag            " SELECT family{$sql_sex1}, MIN(date) AS d_date, d_month" .
8358add1155SRico Sonntag            " FROM (" .
8368add1155SRico Sonntag            "  SELECT" .
8378add1155SRico Sonntag            "  link1.l_from AS family," .
8388add1155SRico Sonntag            "  link1.l_to AS child," .
8398add1155SRico Sonntag            "  child1.d_julianday2 AS date," .
8408add1155SRico Sonntag            "  child1.d_month as d_month" .
8418add1155SRico Sonntag            $sql_sex1 .
8428add1155SRico Sonntag            "  FROM `##link` AS link1" .
8438add1155SRico Sonntag            "  LEFT JOIN `##dates` AS child1 ON child1.d_file = {$this->tree->id()}" .
8448add1155SRico Sonntag            $sql_sex2 .
8458add1155SRico Sonntag            "  WHERE" .
8468add1155SRico Sonntag            "  link1.l_file = {$this->tree->id()} AND" .
8478add1155SRico Sonntag            "  link1.l_type = 'CHIL' AND" .
8488add1155SRico Sonntag            "  child1.d_gid = link1.l_to AND" .
8498add1155SRico Sonntag            "  child1.d_fact = 'BIRT' AND" .
8508add1155SRico Sonntag            "  child1.d_month IN ('JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC')" .
8518add1155SRico Sonntag            "  ORDER BY date" .
8528add1155SRico Sonntag            " ) AS children" .
8538add1155SRico Sonntag            " GROUP BY family, d_month{$sql_sex1}" .
8548add1155SRico Sonntag            ") AS first_child " .
8558add1155SRico Sonntag            "GROUP BY d_month";
8568add1155SRico Sonntag
8578add1155SRico Sonntag        if ($sex) {
8588add1155SRico Sonntag            $sql .= ', i_sex';
8598add1155SRico Sonntag        }
8608add1155SRico Sonntag
8618add1155SRico Sonntag        return $this->runSql($sql);
8628add1155SRico Sonntag    }
8638add1155SRico Sonntag
8648add1155SRico Sonntag    /**
8658add1155SRico Sonntag     * Number of husbands.
8668add1155SRico Sonntag     *
8678add1155SRico Sonntag     * @return string
8688add1155SRico Sonntag     */
8698add1155SRico Sonntag    public function totalMarriedMales(): string
8708add1155SRico Sonntag    {
8718add1155SRico Sonntag        $n = (int) Database::prepare(
8728add1155SRico Sonntag            "SELECT COUNT(DISTINCT f_husb) FROM `##families` WHERE f_file = :tree_id AND f_gedcom LIKE '%\\n1 MARR%'"
8738add1155SRico Sonntag        )->execute([
8748add1155SRico Sonntag            'tree_id' => $this->tree->id(),
8758add1155SRico Sonntag        ])->fetchOne();
8768add1155SRico Sonntag
8778add1155SRico Sonntag        return I18N::number($n);
8788add1155SRico Sonntag    }
8798add1155SRico Sonntag
8808add1155SRico Sonntag    /**
8818add1155SRico Sonntag     * Number of wives.
8828add1155SRico Sonntag     *
8838add1155SRico Sonntag     * @return string
8848add1155SRico Sonntag     */
8858add1155SRico Sonntag    public function totalMarriedFemales(): string
8868add1155SRico Sonntag    {
8878add1155SRico Sonntag        $n = (int) Database::prepare(
8888add1155SRico Sonntag            "SELECT COUNT(DISTINCT f_wife) FROM `##families` WHERE f_file = :tree_id AND f_gedcom LIKE '%\\n1 MARR%'"
8898add1155SRico Sonntag        )->execute([
8908add1155SRico Sonntag            'tree_id' => $this->tree->id(),
8918add1155SRico Sonntag        ])->fetchOne();
8928add1155SRico Sonntag
8938add1155SRico Sonntag        return I18N::number($n);
8948add1155SRico Sonntag    }
8958add1155SRico Sonntag
8968add1155SRico Sonntag    /**
8978add1155SRico Sonntag     * General query on parents.
8988add1155SRico Sonntag     *
8998add1155SRico Sonntag     * @param string $type
9008add1155SRico Sonntag     * @param string $age_dir
9018add1155SRico Sonntag     * @param string $sex
9028add1155SRico Sonntag     * @param bool   $show_years
9038add1155SRico Sonntag     *
9048add1155SRico Sonntag     * @return string
9058add1155SRico Sonntag     */
9068add1155SRico Sonntag    private function parentsQuery(string $type, string $age_dir, string $sex, bool $show_years): string
9078add1155SRico Sonntag    {
9088add1155SRico Sonntag        if ($sex === 'F') {
9098add1155SRico Sonntag            $sex_field = 'WIFE';
9108add1155SRico Sonntag        } else {
9118add1155SRico Sonntag            $sex_field = 'HUSB';
9128add1155SRico Sonntag        }
9138add1155SRico Sonntag
9148add1155SRico Sonntag        if ($age_dir !== 'ASC') {
9158add1155SRico Sonntag            $age_dir = 'DESC';
9168add1155SRico Sonntag        }
9178add1155SRico Sonntag
9188add1155SRico Sonntag        $rows = $this->runSql(
9198add1155SRico Sonntag            " SELECT" .
9208add1155SRico Sonntag            " parentfamily.l_to AS id," .
9218add1155SRico Sonntag            " childbirth.d_julianday2-birth.d_julianday1 AS age" .
9228add1155SRico Sonntag            " FROM `##link` AS parentfamily" .
9238add1155SRico Sonntag            " JOIN `##link` AS childfamily ON childfamily.l_file = {$this->tree->id()}" .
9248add1155SRico Sonntag            " JOIN `##dates` AS birth ON birth.d_file = {$this->tree->id()}" .
9258add1155SRico Sonntag            " JOIN `##dates` AS childbirth ON childbirth.d_file = {$this->tree->id()}" .
9268add1155SRico Sonntag            " WHERE" .
9278add1155SRico Sonntag            " birth.d_gid = parentfamily.l_to AND" .
9288add1155SRico Sonntag            " childfamily.l_to = childbirth.d_gid AND" .
9298add1155SRico Sonntag            " childfamily.l_type = 'CHIL' AND" .
9308add1155SRico Sonntag            " parentfamily.l_type = '{$sex_field}' AND" .
9318add1155SRico Sonntag            " childfamily.l_from = parentfamily.l_from AND" .
9328add1155SRico Sonntag            " parentfamily.l_file = {$this->tree->id()} AND" .
9338add1155SRico Sonntag            " birth.d_fact = 'BIRT' AND" .
9348add1155SRico Sonntag            " childbirth.d_fact = 'BIRT' AND" .
9358add1155SRico Sonntag            " birth.d_julianday1 <> 0 AND" .
9368add1155SRico Sonntag            " childbirth.d_julianday2 > birth.d_julianday1" .
9378add1155SRico Sonntag            " ORDER BY age {$age_dir} LIMIT 1"
9388add1155SRico Sonntag        );
9398add1155SRico Sonntag
9408add1155SRico Sonntag        if (!isset($rows[0])) {
9418add1155SRico Sonntag            return '';
9428add1155SRico Sonntag        }
9438add1155SRico Sonntag
9448add1155SRico Sonntag        $row = $rows[0];
9458add1155SRico Sonntag        if (isset($row->id)) {
9468add1155SRico Sonntag            $person = Individual::getInstance($row->id, $this->tree);
9478add1155SRico Sonntag        }
9488add1155SRico Sonntag
9498add1155SRico Sonntag        switch ($type) {
9508add1155SRico Sonntag            default:
9518add1155SRico Sonntag            case 'full':
9528add1155SRico Sonntag                if ($person && $person->canShow()) {
9538add1155SRico Sonntag                    $result = $person->formatList();
9548add1155SRico Sonntag                } else {
9558add1155SRico Sonntag                    $result = I18N::translate('This information is private and cannot be shown.');
9568add1155SRico Sonntag                }
9578add1155SRico Sonntag                break;
9588add1155SRico Sonntag
9598add1155SRico Sonntag            case 'name':
9608add1155SRico Sonntag                $result = '<a href="' . e($person->url()) . '">' . $person->getFullName() . '</a>';
9618add1155SRico Sonntag                break;
9628add1155SRico Sonntag
9638add1155SRico Sonntag            case 'age':
9648add1155SRico Sonntag                $age = $row->age;
9658add1155SRico Sonntag
9668add1155SRico Sonntag                if ($show_years) {
9678add1155SRico Sonntag                    $result = $this->calculateAge((int) $row->age);
9688add1155SRico Sonntag                } else {
9698add1155SRico Sonntag                    $result = (string) floor($age / 365.25);
9708add1155SRico Sonntag                }
9718add1155SRico Sonntag
9728add1155SRico Sonntag                break;
9738add1155SRico Sonntag        }
9748add1155SRico Sonntag
9758add1155SRico Sonntag        return $result;
9768add1155SRico Sonntag    }
9778add1155SRico Sonntag
9788add1155SRico Sonntag    /**
9798add1155SRico Sonntag     * Find the youngest mother
9808add1155SRico Sonntag     *
9818add1155SRico Sonntag     * @return string
9828add1155SRico Sonntag     */
9838add1155SRico Sonntag    public function youngestMother(): string
9848add1155SRico Sonntag    {
9858add1155SRico Sonntag        return $this->parentsQuery('full', 'ASC', 'F', false);
9868add1155SRico Sonntag    }
9878add1155SRico Sonntag
9888add1155SRico Sonntag    /**
9898add1155SRico Sonntag     * Find the name of the youngest mother.
9908add1155SRico Sonntag     *
9918add1155SRico Sonntag     * @return string
9928add1155SRico Sonntag     */
9938add1155SRico Sonntag    public function youngestMotherName(): string
9948add1155SRico Sonntag    {
9958add1155SRico Sonntag        return $this->parentsQuery('name', 'ASC', 'F', false);
9968add1155SRico Sonntag    }
9978add1155SRico Sonntag
9988add1155SRico Sonntag    /**
9998add1155SRico Sonntag     * Find the age of the youngest mother.
10008add1155SRico Sonntag     *
10018add1155SRico Sonntag     * @param string $show_years
10028add1155SRico Sonntag     *
10038add1155SRico Sonntag     * @return string
10048add1155SRico Sonntag     */
10058add1155SRico Sonntag    public function youngestMotherAge(string $show_years = ''): string
10068add1155SRico Sonntag    {
10078add1155SRico Sonntag        return $this->parentsQuery('age', 'ASC', 'F', (bool) $show_years);
10088add1155SRico Sonntag    }
10098add1155SRico Sonntag
10108add1155SRico Sonntag    /**
10118add1155SRico Sonntag     * Find the oldest mother.
10128add1155SRico Sonntag     *
10138add1155SRico Sonntag     * @return string
10148add1155SRico Sonntag     */
10158add1155SRico Sonntag    public function oldestMother(): string
10168add1155SRico Sonntag    {
10178add1155SRico Sonntag        return $this->parentsQuery('full', 'DESC', 'F', false);
10188add1155SRico Sonntag    }
10198add1155SRico Sonntag
10208add1155SRico Sonntag    /**
10218add1155SRico Sonntag     * Find the name of the oldest mother.
10228add1155SRico Sonntag     *
10238add1155SRico Sonntag     * @return string
10248add1155SRico Sonntag     */
10258add1155SRico Sonntag    public function oldestMotherName(): string
10268add1155SRico Sonntag    {
10278add1155SRico Sonntag        return $this->parentsQuery('name', 'DESC', 'F', false);
10288add1155SRico Sonntag    }
10298add1155SRico Sonntag
10308add1155SRico Sonntag    /**
10318add1155SRico Sonntag     * Find the age of the oldest mother.
10328add1155SRico Sonntag     *
10338add1155SRico Sonntag     * @param string $show_years
10348add1155SRico Sonntag     *
10358add1155SRico Sonntag     * @return string
10368add1155SRico Sonntag     */
10378add1155SRico Sonntag    public function oldestMotherAge(string $show_years = ''): string
10388add1155SRico Sonntag    {
10398add1155SRico Sonntag        return $this->parentsQuery('age', 'DESC', 'F', (bool) $show_years);
10408add1155SRico Sonntag    }
10418add1155SRico Sonntag
10428add1155SRico Sonntag    /**
10438add1155SRico Sonntag     * Find the youngest father.
10448add1155SRico Sonntag     *
10458add1155SRico Sonntag     * @return string
10468add1155SRico Sonntag     */
10478add1155SRico Sonntag    public function youngestFather(): string
10488add1155SRico Sonntag    {
10498add1155SRico Sonntag        return $this->parentsQuery('full', 'ASC', 'M', false);
10508add1155SRico Sonntag    }
10518add1155SRico Sonntag
10528add1155SRico Sonntag    /**
10538add1155SRico Sonntag     * Find the name of the youngest father.
10548add1155SRico Sonntag     *
10558add1155SRico Sonntag     * @return string
10568add1155SRico Sonntag     */
10578add1155SRico Sonntag    public function youngestFatherName(): string
10588add1155SRico Sonntag    {
10598add1155SRico Sonntag        return $this->parentsQuery('name', 'ASC', 'M', false);
10608add1155SRico Sonntag    }
10618add1155SRico Sonntag
10628add1155SRico Sonntag    /**
10638add1155SRico Sonntag     * Find the age of the youngest father.
10648add1155SRico Sonntag     *
10658add1155SRico Sonntag     * @param string $show_years
10668add1155SRico Sonntag     *
10678add1155SRico Sonntag     * @return string
10688add1155SRico Sonntag     */
10698add1155SRico Sonntag    public function youngestFatherAge(string $show_years = ''): string
10708add1155SRico Sonntag    {
10718add1155SRico Sonntag        return $this->parentsQuery('age', 'ASC', 'M', (bool) $show_years);
10728add1155SRico Sonntag    }
10738add1155SRico Sonntag
10748add1155SRico Sonntag    /**
10758add1155SRico Sonntag     * Find the oldest father.
10768add1155SRico Sonntag     *
10778add1155SRico Sonntag     * @return string
10788add1155SRico Sonntag     */
10798add1155SRico Sonntag    public function oldestFather(): string
10808add1155SRico Sonntag    {
10818add1155SRico Sonntag        return $this->parentsQuery('full', 'DESC', 'M', false);
10828add1155SRico Sonntag    }
10838add1155SRico Sonntag
10848add1155SRico Sonntag    /**
10858add1155SRico Sonntag     * Find the name of the oldest father.
10868add1155SRico Sonntag     *
10878add1155SRico Sonntag     * @return string
10888add1155SRico Sonntag     */
10898add1155SRico Sonntag    public function oldestFatherName(): string
10908add1155SRico Sonntag    {
10918add1155SRico Sonntag        return $this->parentsQuery('name', 'DESC', 'M', false);
10928add1155SRico Sonntag    }
10938add1155SRico Sonntag
10948add1155SRico Sonntag    /**
10958add1155SRico Sonntag     * Find the age of the oldest father.
10968add1155SRico Sonntag     *
10978add1155SRico Sonntag     * @param string $show_years
10988add1155SRico Sonntag     *
10998add1155SRico Sonntag     * @return string
11008add1155SRico Sonntag     */
11018add1155SRico Sonntag    public function oldestFatherAge(string $show_years = ''): string
11028add1155SRico Sonntag    {
11038add1155SRico Sonntag        return $this->parentsQuery('age', 'DESC', 'M', (bool) $show_years);
11048add1155SRico Sonntag    }
11058add1155SRico Sonntag
11068add1155SRico Sonntag    /**
11078add1155SRico Sonntag     * General query on age at marriage.
11088add1155SRico Sonntag     *
11098add1155SRico Sonntag     * @param string $type
11108add1155SRico Sonntag     * @param string $age_dir
11118add1155SRico Sonntag     * @param int    $total
11128add1155SRico Sonntag     *
11138add1155SRico Sonntag     * @return string
11148add1155SRico Sonntag     */
11158add1155SRico Sonntag    private function ageOfMarriageQuery(string $type, string $age_dir, int $total): string
11168add1155SRico Sonntag    {
11178add1155SRico Sonntag        if ($age_dir !== 'ASC') {
11188add1155SRico Sonntag            $age_dir = 'DESC';
11198add1155SRico Sonntag        }
11208add1155SRico Sonntag
11218add1155SRico Sonntag        $hrows = $this->runSql(
11228add1155SRico Sonntag            " SELECT DISTINCT fam.f_id AS family, MIN(husbdeath.d_julianday2-married.d_julianday1) AS age" .
11238add1155SRico Sonntag            " FROM `##families` AS fam" .
11248add1155SRico Sonntag            " LEFT JOIN `##dates` AS married ON married.d_file = {$this->tree->id()}" .
11258add1155SRico Sonntag            " LEFT JOIN `##dates` AS husbdeath ON husbdeath.d_file = {$this->tree->id()}" .
11268add1155SRico Sonntag            " WHERE" .
11278add1155SRico Sonntag            " fam.f_file = {$this->tree->id()} AND" .
11288add1155SRico Sonntag            " husbdeath.d_gid = fam.f_husb AND" .
11298add1155SRico Sonntag            " husbdeath.d_fact = 'DEAT' AND" .
11308add1155SRico Sonntag            " married.d_gid = fam.f_id AND" .
11318add1155SRico Sonntag            " married.d_fact = 'MARR' AND" .
11328add1155SRico Sonntag            " married.d_julianday1 < husbdeath.d_julianday2 AND" .
11338add1155SRico Sonntag            " married.d_julianday1 <> 0" .
11348add1155SRico Sonntag            " GROUP BY family" .
11358add1155SRico Sonntag            " ORDER BY age {$age_dir}"
11368add1155SRico Sonntag        );
11378add1155SRico Sonntag
11388add1155SRico Sonntag        $wrows = $this->runSql(
11398add1155SRico Sonntag            " SELECT DISTINCT fam.f_id AS family, MIN(wifedeath.d_julianday2-married.d_julianday1) AS age" .
11408add1155SRico Sonntag            " FROM `##families` AS fam" .
11418add1155SRico Sonntag            " LEFT JOIN `##dates` AS married ON married.d_file = {$this->tree->id()}" .
11428add1155SRico Sonntag            " LEFT JOIN `##dates` AS wifedeath ON wifedeath.d_file = {$this->tree->id()}" .
11438add1155SRico Sonntag            " WHERE" .
11448add1155SRico Sonntag            " fam.f_file = {$this->tree->id()} AND" .
11458add1155SRico Sonntag            " wifedeath.d_gid = fam.f_wife AND" .
11468add1155SRico Sonntag            " wifedeath.d_fact = 'DEAT' AND" .
11478add1155SRico Sonntag            " married.d_gid = fam.f_id AND" .
11488add1155SRico Sonntag            " married.d_fact = 'MARR' AND" .
11498add1155SRico Sonntag            " married.d_julianday1 < wifedeath.d_julianday2 AND" .
11508add1155SRico Sonntag            " married.d_julianday1 <> 0" .
11518add1155SRico Sonntag            " GROUP BY family" .
11528add1155SRico Sonntag            " ORDER BY age {$age_dir}"
11538add1155SRico Sonntag        );
11548add1155SRico Sonntag
11558add1155SRico Sonntag        $drows = $this->runSql(
11568add1155SRico Sonntag            " SELECT DISTINCT fam.f_id AS family, MIN(divorced.d_julianday2-married.d_julianday1) AS age" .
11578add1155SRico Sonntag            " FROM `##families` AS fam" .
11588add1155SRico Sonntag            " LEFT JOIN `##dates` AS married ON married.d_file = {$this->tree->id()}" .
11598add1155SRico Sonntag            " LEFT JOIN `##dates` AS divorced ON divorced.d_file = {$this->tree->id()}" .
11608add1155SRico Sonntag            " WHERE" .
11618add1155SRico Sonntag            " fam.f_file = {$this->tree->id()} AND" .
11628add1155SRico Sonntag            " married.d_gid = fam.f_id AND" .
11638add1155SRico Sonntag            " married.d_fact = 'MARR' AND" .
11648add1155SRico Sonntag            " divorced.d_gid = fam.f_id AND" .
11658add1155SRico Sonntag            " divorced.d_fact IN ('DIV', 'ANUL', '_SEPR', '_DETS') AND" .
11668add1155SRico Sonntag            " married.d_julianday1 < divorced.d_julianday2 AND" .
11678add1155SRico Sonntag            " married.d_julianday1 <> 0" .
11688add1155SRico Sonntag            " GROUP BY family" .
11698add1155SRico Sonntag            " ORDER BY age {$age_dir}"
11708add1155SRico Sonntag        );
11718add1155SRico Sonntag
11728add1155SRico Sonntag        $rows = [];
11738add1155SRico Sonntag        foreach ($drows as $family) {
11748add1155SRico Sonntag            $rows[$family->family] = $family->age;
11758add1155SRico Sonntag        }
11768add1155SRico Sonntag
11778add1155SRico Sonntag        foreach ($hrows as $family) {
11788add1155SRico Sonntag            if (!isset($rows[$family->family])) {
11798add1155SRico Sonntag                $rows[$family->family] = $family->age;
11808add1155SRico Sonntag            }
11818add1155SRico Sonntag        }
11828add1155SRico Sonntag
11838add1155SRico Sonntag        foreach ($wrows as $family) {
11848add1155SRico Sonntag            if (!isset($rows[$family->family])) {
11858add1155SRico Sonntag                $rows[$family->family] = $family->age;
11868add1155SRico Sonntag            } elseif ($rows[$family->family] > $family->age) {
11878add1155SRico Sonntag                $rows[$family->family] = $family->age;
11888add1155SRico Sonntag            }
11898add1155SRico Sonntag        }
11908add1155SRico Sonntag
11918add1155SRico Sonntag        if ($age_dir === 'DESC') {
11928add1155SRico Sonntag            arsort($rows);
11938add1155SRico Sonntag        } else {
11948add1155SRico Sonntag            asort($rows);
11958add1155SRico Sonntag        }
11968add1155SRico Sonntag
11978add1155SRico Sonntag        $top10 = [];
11988add1155SRico Sonntag        $i     = 0;
11998add1155SRico Sonntag        foreach ($rows as $fam => $age) {
12008add1155SRico Sonntag            $family = Family::getInstance($fam, $this->tree);
12018add1155SRico Sonntag            if ($type === 'name') {
12028add1155SRico Sonntag                return $family->formatList();
12038add1155SRico Sonntag            }
12048add1155SRico Sonntag
12058add1155SRico Sonntag            $age = $this->calculateAge((int) $age);
12068add1155SRico Sonntag
12078add1155SRico Sonntag            if ($type === 'age') {
12088add1155SRico Sonntag                return $age;
12098add1155SRico Sonntag            }
12108add1155SRico Sonntag
12118add1155SRico Sonntag            $husb = $family->getHusband();
12128add1155SRico Sonntag            $wife = $family->getWife();
12138add1155SRico Sonntag
12148add1155SRico Sonntag            if (($husb && ($husb->getAllDeathDates() || !$husb->isDead()))
12158add1155SRico Sonntag                && ($wife && ($wife->getAllDeathDates() || !$wife->isDead()))
12168add1155SRico Sonntag            ) {
12178add1155SRico Sonntag                if ($family && $family->canShow()) {
12188add1155SRico Sonntag                    if ($type === 'list') {
12198add1155SRico Sonntag                        $top10[] = '<li><a href="' . e($family->url()) . '">' . $family->getFullName() . '</a> (' . $age . ')' . '</li>';
12208add1155SRico Sonntag                    } else {
12218add1155SRico Sonntag                        $top10[] = '<a href="' . e($family->url()) . '">' . $family->getFullName() . '</a> (' . $age . ')';
12228add1155SRico Sonntag                    }
12238add1155SRico Sonntag                }
12248add1155SRico Sonntag                if (++$i === $total) {
12258add1155SRico Sonntag                    break;
12268add1155SRico Sonntag                }
12278add1155SRico Sonntag            }
12288add1155SRico Sonntag        }
12298add1155SRico Sonntag
12308add1155SRico Sonntag        if ($type === 'list') {
12318add1155SRico Sonntag            $top10 = implode('', $top10);
12328add1155SRico Sonntag        } else {
12338add1155SRico Sonntag            $top10 = implode('; ', $top10);
12348add1155SRico Sonntag        }
12358add1155SRico Sonntag
12368add1155SRico Sonntag        if (I18N::direction() === 'rtl') {
12378add1155SRico Sonntag            $top10 = str_replace([
12388add1155SRico Sonntag                '[',
12398add1155SRico Sonntag                ']',
12408add1155SRico Sonntag                '(',
12418add1155SRico Sonntag                ')',
12428add1155SRico Sonntag                '+',
12438add1155SRico Sonntag            ], [
12448add1155SRico Sonntag                '&rlm;[',
12458add1155SRico Sonntag                '&rlm;]',
12468add1155SRico Sonntag                '&rlm;(',
12478add1155SRico Sonntag                '&rlm;)',
12488add1155SRico Sonntag                '&rlm;+',
12498add1155SRico Sonntag            ], $top10);
12508add1155SRico Sonntag        }
12518add1155SRico Sonntag
12528add1155SRico Sonntag        if ($type === 'list') {
12538add1155SRico Sonntag            return '<ul>' . $top10 . '</ul>';
12548add1155SRico Sonntag        }
12558add1155SRico Sonntag
12568add1155SRico Sonntag        return $top10;
12578add1155SRico Sonntag    }
12588add1155SRico Sonntag
12598add1155SRico Sonntag    /**
12608add1155SRico Sonntag     * General query on marriage ages.
12618add1155SRico Sonntag     *
12628add1155SRico Sonntag     * @return string
12638add1155SRico Sonntag     */
12648add1155SRico Sonntag    public function topAgeOfMarriageFamily(): string
12658add1155SRico Sonntag    {
12668add1155SRico Sonntag        return $this->ageOfMarriageQuery('name', 'DESC', 1);
12678add1155SRico Sonntag    }
12688add1155SRico Sonntag
12698add1155SRico Sonntag    /**
12708add1155SRico Sonntag     * General query on marriage ages.
12718add1155SRico Sonntag     *
12728add1155SRico Sonntag     * @return string
12738add1155SRico Sonntag     */
12748add1155SRico Sonntag    public function topAgeOfMarriage(): string
12758add1155SRico Sonntag    {
12768add1155SRico Sonntag        return $this->ageOfMarriageQuery('age', 'DESC', 1);
12778add1155SRico Sonntag    }
12788add1155SRico Sonntag
12798add1155SRico Sonntag    /**
12808add1155SRico Sonntag     * General query on marriage ages.
12818add1155SRico Sonntag     *
12828add1155SRico Sonntag     * @param int $total
12838add1155SRico Sonntag     *
12848add1155SRico Sonntag     * @return string
12858add1155SRico Sonntag     */
12868add1155SRico Sonntag    public function topAgeOfMarriageFamilies(int $total = 10): string
12878add1155SRico Sonntag    {
12888add1155SRico Sonntag        return $this->ageOfMarriageQuery('nolist', 'DESC', $total);
12898add1155SRico Sonntag    }
12908add1155SRico Sonntag
12918add1155SRico Sonntag    /**
12928add1155SRico Sonntag     * General query on marriage ages.
12938add1155SRico Sonntag     *
12948add1155SRico Sonntag     * @param int $total
12958add1155SRico Sonntag     *
12968add1155SRico Sonntag     * @return string
12978add1155SRico Sonntag     */
12988add1155SRico Sonntag    public function topAgeOfMarriageFamiliesList(int $total = 10): string
12998add1155SRico Sonntag    {
13008add1155SRico Sonntag        return $this->ageOfMarriageQuery('list', 'DESC', $total);
13018add1155SRico Sonntag    }
13028add1155SRico Sonntag
13038add1155SRico Sonntag    /**
13048add1155SRico Sonntag     * General query on marriage ages.
13058add1155SRico Sonntag     *
13068add1155SRico Sonntag     * @return string
13078add1155SRico Sonntag     */
13088add1155SRico Sonntag    public function minAgeOfMarriageFamily(): string
13098add1155SRico Sonntag    {
13108add1155SRico Sonntag        return $this->ageOfMarriageQuery('name', 'ASC', 1);
13118add1155SRico Sonntag    }
13128add1155SRico Sonntag
13138add1155SRico Sonntag    /**
13148add1155SRico Sonntag     * General query on marriage ages.
13158add1155SRico Sonntag     *
13168add1155SRico Sonntag     * @return string
13178add1155SRico Sonntag     */
13188add1155SRico Sonntag    public function minAgeOfMarriage(): string
13198add1155SRico Sonntag    {
13208add1155SRico Sonntag        return $this->ageOfMarriageQuery('age', 'ASC', 1);
13218add1155SRico Sonntag    }
13228add1155SRico Sonntag
13238add1155SRico Sonntag    /**
13248add1155SRico Sonntag     * General query on marriage ages.
13258add1155SRico Sonntag     *
13268add1155SRico Sonntag     * @param int $total
13278add1155SRico Sonntag     *
13288add1155SRico Sonntag     * @return string
13298add1155SRico Sonntag     */
13308add1155SRico Sonntag    public function minAgeOfMarriageFamilies(int $total = 10): string
13318add1155SRico Sonntag    {
13328add1155SRico Sonntag        return $this->ageOfMarriageQuery('nolist', 'ASC', $total);
13338add1155SRico Sonntag    }
13348add1155SRico Sonntag
13358add1155SRico Sonntag    /**
13368add1155SRico Sonntag     * General query on marriage ages.
13378add1155SRico Sonntag     *
13388add1155SRico Sonntag     * @param int $total
13398add1155SRico Sonntag     *
13408add1155SRico Sonntag     * @return string
13418add1155SRico Sonntag     */
13428add1155SRico Sonntag    public function minAgeOfMarriageFamiliesList(int $total = 10): string
13438add1155SRico Sonntag    {
13448add1155SRico Sonntag        return $this->ageOfMarriageQuery('list', 'ASC', $total);
13458add1155SRico Sonntag    }
13468add1155SRico Sonntag
13478add1155SRico Sonntag    /**
13488add1155SRico Sonntag     * Find the ages between spouses.
13498add1155SRico Sonntag     *
13508add1155SRico Sonntag     * @param string $age_dir
13518add1155SRico Sonntag     * @param int    $total
13528add1155SRico Sonntag     *
13538add1155SRico Sonntag     * @return array
13548add1155SRico Sonntag     */
13558add1155SRico Sonntag    private function ageBetweenSpousesQuery(string $age_dir, int $total): array
13568add1155SRico Sonntag    {
13578add1155SRico Sonntag        if ($age_dir === 'DESC') {
13588add1155SRico Sonntag            $sql =
13598add1155SRico Sonntag                "SELECT f_id AS xref, MIN(wife.d_julianday2-husb.d_julianday1) AS age" .
13608add1155SRico Sonntag                " FROM `##families`" .
13618add1155SRico Sonntag                " JOIN `##dates` AS wife ON wife.d_gid = f_wife AND wife.d_file = f_file" .
13628add1155SRico Sonntag                " JOIN `##dates` AS husb ON husb.d_gid = f_husb AND husb.d_file = f_file" .
13638add1155SRico Sonntag                " WHERE f_file = :tree_id" .
13648add1155SRico Sonntag                " AND husb.d_fact = 'BIRT'" .
13658add1155SRico Sonntag                " AND wife.d_fact = 'BIRT'" .
13668add1155SRico Sonntag                " AND wife.d_julianday2 >= husb.d_julianday1 AND husb.d_julianday1 <> 0" .
13678add1155SRico Sonntag                " GROUP BY xref" .
13688add1155SRico Sonntag                " ORDER BY age DESC" .
13698add1155SRico Sonntag                " LIMIT :limit";
13708add1155SRico Sonntag        } else {
13718add1155SRico Sonntag            $sql =
13728add1155SRico Sonntag                "SELECT f_id AS xref, MIN(husb.d_julianday2-wife.d_julianday1) AS age" .
13738add1155SRico Sonntag                " FROM `##families`" .
13748add1155SRico Sonntag                " JOIN `##dates` AS wife ON wife.d_gid = f_wife AND wife.d_file = f_file" .
13758add1155SRico Sonntag                " JOIN `##dates` AS husb ON husb.d_gid = f_husb AND husb.d_file = f_file" .
13768add1155SRico Sonntag                " WHERE f_file = :tree_id" .
13778add1155SRico Sonntag                " AND husb.d_fact = 'BIRT'" .
13788add1155SRico Sonntag                " AND wife.d_fact = 'BIRT'" .
13798add1155SRico Sonntag                " AND husb.d_julianday2 >= wife.d_julianday1 AND wife.d_julianday1 <> 0" .
13808add1155SRico Sonntag                " GROUP BY xref" .
13818add1155SRico Sonntag                " ORDER BY age DESC" .
13828add1155SRico Sonntag                " LIMIT :limit";
13838add1155SRico Sonntag        }
13848add1155SRico Sonntag
13858add1155SRico Sonntag        $rows = Database::prepare(
13868add1155SRico Sonntag            $sql
13878add1155SRico Sonntag        )->execute([
13888add1155SRico Sonntag            'tree_id' => $this->tree->id(),
13898add1155SRico Sonntag            'limit'   => $total,
13908add1155SRico Sonntag        ])->fetchAll();
13918add1155SRico Sonntag
13928add1155SRico Sonntag        $top10 = [];
13938add1155SRico Sonntag
13948add1155SRico Sonntag        foreach ($rows as $fam) {
13958add1155SRico Sonntag            $family = Family::getInstance($fam->xref, $this->tree);
13968add1155SRico Sonntag
13978add1155SRico Sonntag            if ($fam->age < 0) {
13988add1155SRico Sonntag                break;
13998add1155SRico Sonntag            }
14008add1155SRico Sonntag
14018add1155SRico Sonntag            if ($family->canShow()) {
14028add1155SRico Sonntag                $top10[] = [
14038add1155SRico Sonntag                    'family' => $family,
14048add1155SRico Sonntag                    'age'    => $this->calculateAge((int) $fam->age),
14058add1155SRico Sonntag                ];
14068add1155SRico Sonntag            }
14078add1155SRico Sonntag        }
14088add1155SRico Sonntag
14098add1155SRico Sonntag        return $top10;
14108add1155SRico Sonntag    }
14118add1155SRico Sonntag
14128add1155SRico Sonntag    /**
14138add1155SRico Sonntag     * Find the age between husband and wife.
14148add1155SRico Sonntag     *
14158add1155SRico Sonntag     * @param int $total
14168add1155SRico Sonntag     *
14178add1155SRico Sonntag     * @return string
14188add1155SRico Sonntag     */
14198add1155SRico Sonntag    public function ageBetweenSpousesMF(int $total = 10): string
14208add1155SRico Sonntag    {
14218add1155SRico Sonntag        $records = $this->ageBetweenSpousesQuery('DESC', $total);
14228add1155SRico Sonntag
14238add1155SRico Sonntag        return view(
14248add1155SRico Sonntag            'statistics/families/top10-nolist-spouses',
14258add1155SRico Sonntag            [
14268add1155SRico Sonntag                'records' => $records,
14278add1155SRico Sonntag            ]
14288add1155SRico Sonntag        );
14298add1155SRico Sonntag    }
14308add1155SRico Sonntag
14318add1155SRico Sonntag    /**
14328add1155SRico Sonntag     * Find the age between husband and wife.
14338add1155SRico Sonntag     *
14348add1155SRico Sonntag     * @param int $total
14358add1155SRico Sonntag     *
14368add1155SRico Sonntag     * @return string
14378add1155SRico Sonntag     */
14388add1155SRico Sonntag    public function ageBetweenSpousesMFList(int $total = 10): string
14398add1155SRico Sonntag    {
14408add1155SRico Sonntag        $records = $this->ageBetweenSpousesQuery('DESC', $total);
14418add1155SRico Sonntag
14428add1155SRico Sonntag        return view(
14438add1155SRico Sonntag            'statistics/families/top10-list-spouses',
14448add1155SRico Sonntag            [
14458add1155SRico Sonntag                'records' => $records,
14468add1155SRico Sonntag            ]
14478add1155SRico Sonntag        );
14488add1155SRico Sonntag    }
14498add1155SRico Sonntag
14508add1155SRico Sonntag    /**
14518add1155SRico Sonntag     * Find the age between wife and husband..
14528add1155SRico Sonntag     *
14538add1155SRico Sonntag     * @param int $total
14548add1155SRico Sonntag     *
14558add1155SRico Sonntag     * @return string
14568add1155SRico Sonntag     */
14578add1155SRico Sonntag    public function ageBetweenSpousesFM(int $total = 10): string
14588add1155SRico Sonntag    {
14598add1155SRico Sonntag        $records = $this->ageBetweenSpousesQuery('ASC', $total);
14608add1155SRico Sonntag
14618add1155SRico Sonntag        return view(
14628add1155SRico Sonntag            'statistics/families/top10-nolist-spouses',
14638add1155SRico Sonntag            [
14648add1155SRico Sonntag                'records' => $records,
14658add1155SRico Sonntag            ]
14668add1155SRico Sonntag        );
14678add1155SRico Sonntag    }
14688add1155SRico Sonntag
14698add1155SRico Sonntag    /**
14708add1155SRico Sonntag     * Find the age between wife and husband..
14718add1155SRico Sonntag     *
14728add1155SRico Sonntag     * @param int $total
14738add1155SRico Sonntag     *
14748add1155SRico Sonntag     * @return string
14758add1155SRico Sonntag     */
14768add1155SRico Sonntag    public function ageBetweenSpousesFMList(int $total = 10): string
14778add1155SRico Sonntag    {
14788add1155SRico Sonntag        $records = $this->ageBetweenSpousesQuery('ASC', $total);
14798add1155SRico Sonntag
14808add1155SRico Sonntag        return view(
14818add1155SRico Sonntag            'statistics/families/top10-list-spouses',
14828add1155SRico Sonntag            [
14838add1155SRico Sonntag                'records' => $records,
14848add1155SRico Sonntag            ]
14858add1155SRico Sonntag        );
14868add1155SRico Sonntag    }
14878add1155SRico Sonntag
14888add1155SRico Sonntag    /**
14898add1155SRico Sonntag     * General query on ages at marriage.
14908add1155SRico Sonntag     *
14918add1155SRico Sonntag     * @param string $sex
14928add1155SRico Sonntag     * @param int    $year1
14938add1155SRico Sonntag     * @param int    $year2
14948add1155SRico Sonntag     *
14958add1155SRico Sonntag     * @return array
14968add1155SRico Sonntag     */
14978add1155SRico Sonntag    public function statsMarrAgeQuery($sex = 'M', $year1 = -1, $year2 = -1): array
14988add1155SRico Sonntag    {
14998add1155SRico Sonntag        if ($year1 >= 0 && $year2 >= 0) {
15008add1155SRico Sonntag            $years = " married.d_year BETWEEN {$year1} AND {$year2} AND ";
15018add1155SRico Sonntag        } else {
15028add1155SRico Sonntag            $years = '';
15038add1155SRico Sonntag        }
15048add1155SRico Sonntag
15058add1155SRico Sonntag        $rows = $this->runSql(
15068add1155SRico Sonntag            "SELECT " .
15078add1155SRico Sonntag            " fam.f_id, " .
15088add1155SRico Sonntag            " birth.d_gid, " .
15098add1155SRico Sonntag            " married.d_julianday2-birth.d_julianday1 AS age " .
15108add1155SRico Sonntag            "FROM `##dates` AS married " .
15118add1155SRico Sonntag            "JOIN `##families` AS fam ON (married.d_gid=fam.f_id AND married.d_file=fam.f_file) " .
15128add1155SRico Sonntag            "JOIN `##dates` AS birth ON (birth.d_gid=fam.f_husb AND birth.d_file=fam.f_file) " .
15138add1155SRico Sonntag            "WHERE " .
15148add1155SRico Sonntag            " '{$sex}' IN ('M', 'BOTH') AND {$years} " .
15158add1155SRico Sonntag            " married.d_file={$this->tree->id()} AND married.d_type IN ('@#DGREGORIAN@', '@#DJULIAN@') AND married.d_fact='MARR' AND " .
15168add1155SRico Sonntag            " birth.d_type IN ('@#DGREGORIAN@', '@#DJULIAN@') AND birth.d_fact='BIRT' AND " .
15178add1155SRico Sonntag            " married.d_julianday1>birth.d_julianday1 AND birth.d_julianday1<>0 " .
15188add1155SRico Sonntag            "UNION ALL " .
15198add1155SRico Sonntag            "SELECT " .
15208add1155SRico Sonntag            " fam.f_id, " .
15218add1155SRico Sonntag            " birth.d_gid, " .
15228add1155SRico Sonntag            " married.d_julianday2-birth.d_julianday1 AS age " .
15238add1155SRico Sonntag            "FROM `##dates` AS married " .
15248add1155SRico Sonntag            "JOIN `##families` AS fam ON (married.d_gid=fam.f_id AND married.d_file=fam.f_file) " .
15258add1155SRico Sonntag            "JOIN `##dates` AS birth ON (birth.d_gid=fam.f_wife AND birth.d_file=fam.f_file) " .
15268add1155SRico Sonntag            "WHERE " .
15278add1155SRico Sonntag            " '{$sex}' IN ('F', 'BOTH') AND {$years} " .
15288add1155SRico Sonntag            " married.d_file={$this->tree->id()} AND married.d_type IN ('@#DGREGORIAN@', '@#DJULIAN@') AND married.d_fact='MARR' AND " .
15298add1155SRico Sonntag            " birth.d_type IN ('@#DGREGORIAN@', '@#DJULIAN@') AND birth.d_fact='BIRT' AND " .
15308add1155SRico Sonntag            " married.d_julianday1>birth.d_julianday1 AND birth.d_julianday1<>0 "
15318add1155SRico Sonntag        );
15328add1155SRico Sonntag
15338add1155SRico Sonntag        foreach ($rows as $row) {
15348add1155SRico Sonntag            $row->age = (int) $row->age;
15358add1155SRico Sonntag        }
15368add1155SRico Sonntag
15378add1155SRico Sonntag        return $rows;
15388add1155SRico Sonntag    }
15398add1155SRico Sonntag
15408add1155SRico Sonntag    /**
15418add1155SRico Sonntag     * General query on marriage ages.
15428add1155SRico Sonntag     *
15438add1155SRico Sonntag     * @return string
15448add1155SRico Sonntag     */
154588de55fdSRico Sonntag    public function statsMarrAge(): string
15468add1155SRico Sonntag    {
15478add1155SRico Sonntag        return (new ChartMarriageAge($this->tree))
154888de55fdSRico Sonntag            ->chartMarriageAge();
15498add1155SRico Sonntag    }
15508add1155SRico Sonntag
15518add1155SRico Sonntag    /**
15528add1155SRico Sonntag     * Query the database for marriage tags.
15538add1155SRico Sonntag     *
15548add1155SRico Sonntag     * @param string $type
15558add1155SRico Sonntag     * @param string $age_dir
15568add1155SRico Sonntag     * @param string $sex
15578add1155SRico Sonntag     * @param bool   $show_years
15588add1155SRico Sonntag     *
15598add1155SRico Sonntag     * @return string
15608add1155SRico Sonntag     */
15618add1155SRico Sonntag    private function marriageQuery(string $type, string $age_dir, string $sex, bool $show_years): string
15628add1155SRico Sonntag    {
15638add1155SRico Sonntag        if ($sex === 'F') {
15648add1155SRico Sonntag            $sex_field = 'f_wife';
15658add1155SRico Sonntag        } else {
15668add1155SRico Sonntag            $sex_field = 'f_husb';
15678add1155SRico Sonntag        }
15688add1155SRico Sonntag
15698add1155SRico Sonntag        if ($age_dir !== 'ASC') {
15708add1155SRico Sonntag            $age_dir = 'DESC';
15718add1155SRico Sonntag        }
15728add1155SRico Sonntag
15738add1155SRico Sonntag        $rows = $this->runSql(
15748add1155SRico Sonntag            " SELECT fam.f_id AS famid, fam.{$sex_field}, married.d_julianday2-birth.d_julianday1 AS age, indi.i_id AS i_id" .
15758add1155SRico Sonntag            " FROM `##families` AS fam" .
15768add1155SRico Sonntag            " LEFT JOIN `##dates` AS birth ON birth.d_file = {$this->tree->id()}" .
15778add1155SRico Sonntag            " LEFT JOIN `##dates` AS married ON married.d_file = {$this->tree->id()}" .
15788add1155SRico Sonntag            " LEFT JOIN `##individuals` AS indi ON indi.i_file = {$this->tree->id()}" .
15798add1155SRico Sonntag            " WHERE" .
15808add1155SRico Sonntag            " birth.d_gid = indi.i_id AND" .
15818add1155SRico Sonntag            " married.d_gid = fam.f_id AND" .
15828add1155SRico Sonntag            " indi.i_id = fam.{$sex_field} AND" .
15838add1155SRico Sonntag            " fam.f_file = {$this->tree->id()} AND" .
15848add1155SRico Sonntag            " birth.d_fact = 'BIRT' AND" .
15858add1155SRico Sonntag            " married.d_fact = 'MARR' AND" .
15868add1155SRico Sonntag            " birth.d_julianday1 <> 0 AND" .
15878add1155SRico Sonntag            " married.d_julianday2 > birth.d_julianday1 AND" .
15888add1155SRico Sonntag            " i_sex='{$sex}'" .
15898add1155SRico Sonntag            " ORDER BY" .
15908add1155SRico Sonntag            " married.d_julianday2-birth.d_julianday1 {$age_dir} LIMIT 1"
15918add1155SRico Sonntag        );
15928add1155SRico Sonntag
15938add1155SRico Sonntag        if (!isset($rows[0])) {
15948add1155SRico Sonntag            return '';
15958add1155SRico Sonntag        }
15968add1155SRico Sonntag
15978add1155SRico Sonntag        $row = $rows[0];
15988add1155SRico Sonntag        if (isset($row->famid)) {
15998add1155SRico Sonntag            $family = Family::getInstance($row->famid, $this->tree);
16008add1155SRico Sonntag        }
16018add1155SRico Sonntag
16028add1155SRico Sonntag        if (isset($row->i_id)) {
16038add1155SRico Sonntag            $person = Individual::getInstance($row->i_id, $this->tree);
16048add1155SRico Sonntag        }
16058add1155SRico Sonntag
16068add1155SRico Sonntag        switch ($type) {
16078add1155SRico Sonntag            default:
16088add1155SRico Sonntag            case 'full':
16098add1155SRico Sonntag                if ($family && $family->canShow()) {
16108add1155SRico Sonntag                    $result = $family->formatList();
16118add1155SRico Sonntag                } else {
16128add1155SRico Sonntag                    $result = I18N::translate('This information is private and cannot be shown.');
16138add1155SRico Sonntag                }
16148add1155SRico Sonntag                break;
16158add1155SRico Sonntag
16168add1155SRico Sonntag            case 'name':
16178add1155SRico Sonntag                $result = '<a href="' . e($family->url()) . '">' . $person->getFullName() . '</a>';
16188add1155SRico Sonntag                break;
16198add1155SRico Sonntag
16208add1155SRico Sonntag            case 'age':
16218add1155SRico Sonntag                $age = $row->age;
16228add1155SRico Sonntag
16238add1155SRico Sonntag                if ($show_years) {
16248add1155SRico Sonntag                    $result = $this->calculateAge((int) $row->age);
16258add1155SRico Sonntag                } else {
16268add1155SRico Sonntag                    $result = I18N::number((int) ($age / 365.25));
16278add1155SRico Sonntag                }
16288add1155SRico Sonntag
16298add1155SRico Sonntag                break;
16308add1155SRico Sonntag        }
16318add1155SRico Sonntag
16328add1155SRico Sonntag        return $result;
16338add1155SRico Sonntag    }
16348add1155SRico Sonntag
16358add1155SRico Sonntag    /**
16368add1155SRico Sonntag     * Find the youngest wife.
16378add1155SRico Sonntag     *
16388add1155SRico Sonntag     * @return string
16398add1155SRico Sonntag     */
16408add1155SRico Sonntag    public function youngestMarriageFemale(): string
16418add1155SRico Sonntag    {
16428add1155SRico Sonntag        return $this->marriageQuery('full', 'ASC', 'F', false);
16438add1155SRico Sonntag    }
16448add1155SRico Sonntag
16458add1155SRico Sonntag    /**
16468add1155SRico Sonntag     * Find the name of the youngest wife.
16478add1155SRico Sonntag     *
16488add1155SRico Sonntag     * @return string
16498add1155SRico Sonntag     */
16508add1155SRico Sonntag    public function youngestMarriageFemaleName(): string
16518add1155SRico Sonntag    {
16528add1155SRico Sonntag        return $this->marriageQuery('name', 'ASC', 'F', false);
16538add1155SRico Sonntag    }
16548add1155SRico Sonntag
16558add1155SRico Sonntag    /**
16568add1155SRico Sonntag     * Find the age of the youngest wife.
16578add1155SRico Sonntag     *
16588add1155SRico Sonntag     * @param string $show_years
16598add1155SRico Sonntag     *
16608add1155SRico Sonntag     * @return string
16618add1155SRico Sonntag     */
16628add1155SRico Sonntag    public function youngestMarriageFemaleAge(string $show_years = ''): string
16638add1155SRico Sonntag    {
16648add1155SRico Sonntag        return $this->marriageQuery('age', 'ASC', 'F', (bool) $show_years);
16658add1155SRico Sonntag    }
16668add1155SRico Sonntag
16678add1155SRico Sonntag    /**
16688add1155SRico Sonntag     * Find the oldest wife.
16698add1155SRico Sonntag     *
16708add1155SRico Sonntag     * @return string
16718add1155SRico Sonntag     */
16728add1155SRico Sonntag    public function oldestMarriageFemale(): string
16738add1155SRico Sonntag    {
16748add1155SRico Sonntag        return $this->marriageQuery('full', 'DESC', 'F', false);
16758add1155SRico Sonntag    }
16768add1155SRico Sonntag
16778add1155SRico Sonntag    /**
16788add1155SRico Sonntag     * Find the name of the oldest wife.
16798add1155SRico Sonntag     *
16808add1155SRico Sonntag     * @return string
16818add1155SRico Sonntag     */
16828add1155SRico Sonntag    public function oldestMarriageFemaleName(): string
16838add1155SRico Sonntag    {
16848add1155SRico Sonntag        return $this->marriageQuery('name', 'DESC', 'F', false);
16858add1155SRico Sonntag    }
16868add1155SRico Sonntag
16878add1155SRico Sonntag    /**
16888add1155SRico Sonntag     * Find the age of the oldest wife.
16898add1155SRico Sonntag     *
16908add1155SRico Sonntag     * @param string $show_years
16918add1155SRico Sonntag     *
16928add1155SRico Sonntag     * @return string
16938add1155SRico Sonntag     */
16948add1155SRico Sonntag    public function oldestMarriageFemaleAge(string $show_years = ''): string
16958add1155SRico Sonntag    {
16968add1155SRico Sonntag        return $this->marriageQuery('age', 'DESC', 'F', (bool) $show_years);
16978add1155SRico Sonntag    }
16988add1155SRico Sonntag
16998add1155SRico Sonntag    /**
17008add1155SRico Sonntag     * Find the youngest husband.
17018add1155SRico Sonntag     *
17028add1155SRico Sonntag     * @return string
17038add1155SRico Sonntag     */
17048add1155SRico Sonntag    public function youngestMarriageMale(): string
17058add1155SRico Sonntag    {
17068add1155SRico Sonntag        return $this->marriageQuery('full', 'ASC', 'M', false);
17078add1155SRico Sonntag    }
17088add1155SRico Sonntag
17098add1155SRico Sonntag    /**
17108add1155SRico Sonntag     * Find the name of the youngest husband.
17118add1155SRico Sonntag     *
17128add1155SRico Sonntag     * @return string
17138add1155SRico Sonntag     */
17148add1155SRico Sonntag    public function youngestMarriageMaleName(): string
17158add1155SRico Sonntag    {
17168add1155SRico Sonntag        return $this->marriageQuery('name', 'ASC', 'M', false);
17178add1155SRico Sonntag    }
17188add1155SRico Sonntag
17198add1155SRico Sonntag    /**
17208add1155SRico Sonntag     * Find the age of the youngest husband.
17218add1155SRico Sonntag     *
17228add1155SRico Sonntag     * @param string $show_years
17238add1155SRico Sonntag     *
17248add1155SRico Sonntag     * @return string
17258add1155SRico Sonntag     */
17268add1155SRico Sonntag    public function youngestMarriageMaleAge(string $show_years = ''): string
17278add1155SRico Sonntag    {
17288add1155SRico Sonntag        return $this->marriageQuery('age', 'ASC', 'M', (bool) $show_years);
17298add1155SRico Sonntag    }
17308add1155SRico Sonntag
17318add1155SRico Sonntag    /**
17328add1155SRico Sonntag     * Find the oldest husband.
17338add1155SRico Sonntag     *
17348add1155SRico Sonntag     * @return string
17358add1155SRico Sonntag     */
17368add1155SRico Sonntag    public function oldestMarriageMale(): string
17378add1155SRico Sonntag    {
17388add1155SRico Sonntag        return $this->marriageQuery('full', 'DESC', 'M', false);
17398add1155SRico Sonntag    }
17408add1155SRico Sonntag
17418add1155SRico Sonntag    /**
17428add1155SRico Sonntag     * Find the name of the oldest husband.
17438add1155SRico Sonntag     *
17448add1155SRico Sonntag     * @return string
17458add1155SRico Sonntag     */
17468add1155SRico Sonntag    public function oldestMarriageMaleName(): string
17478add1155SRico Sonntag    {
17488add1155SRico Sonntag        return $this->marriageQuery('name', 'DESC', 'M', false);
17498add1155SRico Sonntag    }
17508add1155SRico Sonntag
17518add1155SRico Sonntag    /**
17528add1155SRico Sonntag     * Find the age of the oldest husband.
17538add1155SRico Sonntag     *
17548add1155SRico Sonntag     * @param string $show_years
17558add1155SRico Sonntag     *
17568add1155SRico Sonntag     * @return string
17578add1155SRico Sonntag     */
17588add1155SRico Sonntag    public function oldestMarriageMaleAge(string $show_years = ''): string
17598add1155SRico Sonntag    {
17608add1155SRico Sonntag        return $this->marriageQuery('age', 'DESC', 'M', (bool) $show_years);
17618add1155SRico Sonntag    }
17628add1155SRico Sonntag
17638add1155SRico Sonntag    /**
17648add1155SRico Sonntag     * General query on marriages.
17658add1155SRico Sonntag     *
17668add1155SRico Sonntag     * @param bool $first
17678add1155SRico Sonntag     * @param int  $year1
17688add1155SRico Sonntag     * @param int  $year2
17698add1155SRico Sonntag     *
17708add1155SRico Sonntag     * @return array
17718add1155SRico Sonntag     */
17728add1155SRico Sonntag    public function statsMarrQuery(bool $first = false, int $year1 = -1, int $year2 = -1): array
17738add1155SRico Sonntag    {
17748add1155SRico Sonntag        if ($first) {
17758add1155SRico Sonntag            $years = '';
17768add1155SRico Sonntag
17778add1155SRico Sonntag            if ($year1 >= 0 && $year2 >= 0) {
17788add1155SRico Sonntag                $years = " married.d_year BETWEEN '{$year1}' AND '{$year2}' AND";
17798add1155SRico Sonntag            }
17808add1155SRico Sonntag
17818add1155SRico Sonntag            $sql =
17828add1155SRico Sonntag                " SELECT fam.f_id AS fams, fam.f_husb, fam.f_wife, married.d_julianday2 AS age, married.d_month AS month, indi.i_id AS indi" .
17838add1155SRico Sonntag                " FROM `##families` AS fam" .
17848add1155SRico Sonntag                " LEFT JOIN `##dates` AS married ON married.d_file = {$this->tree->id()}" .
17858add1155SRico Sonntag                " LEFT JOIN `##individuals` AS indi ON indi.i_file = {$this->tree->id()}" .
17868add1155SRico Sonntag                " WHERE" .
17878add1155SRico Sonntag                " married.d_gid = fam.f_id AND" .
17888add1155SRico Sonntag                " fam.f_file = {$this->tree->id()} AND" .
17898add1155SRico Sonntag                " married.d_fact = 'MARR' AND" .
17908add1155SRico Sonntag                " married.d_julianday2 <> 0 AND" .
17918add1155SRico Sonntag                $years .
17928add1155SRico Sonntag                " (indi.i_id = fam.f_husb OR indi.i_id = fam.f_wife)" .
17938add1155SRico Sonntag                " ORDER BY fams, indi, age ASC";
17948add1155SRico Sonntag        } else {
17958add1155SRico Sonntag            $sql =
17968add1155SRico Sonntag                "SELECT d_month, COUNT(*) AS total" .
17978add1155SRico Sonntag                " FROM `##dates`" .
17988add1155SRico Sonntag                " WHERE d_file={$this->tree->id()} AND d_fact='MARR'";
17998add1155SRico Sonntag
18008add1155SRico Sonntag            if ($year1 >= 0 && $year2 >= 0) {
18018add1155SRico Sonntag                $sql .= " AND d_year BETWEEN '{$year1}' AND '{$year2}'";
18028add1155SRico Sonntag            }
18038add1155SRico Sonntag
18048add1155SRico Sonntag            $sql .= " GROUP BY d_month";
18058add1155SRico Sonntag        }
18068add1155SRico Sonntag
18078add1155SRico Sonntag        return $this->runSql($sql);
18088add1155SRico Sonntag    }
18098add1155SRico Sonntag
18108add1155SRico Sonntag    /**
18118add1155SRico Sonntag     * General query on marriages.
18128add1155SRico Sonntag     *
18138add1155SRico Sonntag     * @param string|null $color_from
18148add1155SRico Sonntag     * @param string|null $color_to
18158add1155SRico Sonntag     *
18168add1155SRico Sonntag     * @return string
18178add1155SRico Sonntag     */
181888de55fdSRico Sonntag    public function statsMarr(string $color_from = null, string $color_to = null): string
18198add1155SRico Sonntag    {
18208add1155SRico Sonntag        return (new ChartMarriage($this->tree))
182188de55fdSRico Sonntag            ->chartMarriage($color_from, $color_to);
18228add1155SRico Sonntag    }
18238add1155SRico Sonntag
18248add1155SRico Sonntag    /**
18258add1155SRico Sonntag     * General divorce query.
18268add1155SRico Sonntag     *
18278add1155SRico Sonntag     * @param string|null $color_from
18288add1155SRico Sonntag     * @param string|null $color_to
18298add1155SRico Sonntag     *
18308add1155SRico Sonntag     * @return string
18318add1155SRico Sonntag     */
183288de55fdSRico Sonntag    public function statsDiv(string $color_from = null, string $color_to = null): string
18338add1155SRico Sonntag    {
18348add1155SRico Sonntag        return (new ChartDivorce($this->tree))
183588de55fdSRico Sonntag            ->chartDivorce($color_from, $color_to);
18368add1155SRico Sonntag    }
18378add1155SRico Sonntag}
1838