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