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