16fcafd02SGreg Roach<?php 26fcafd02SGreg Roach 36fcafd02SGreg Roach/** 46fcafd02SGreg Roach * webtrees: online genealogy 55bfc6897SGreg Roach * Copyright (C) 2022 webtrees development team 66fcafd02SGreg Roach * This program is free software: you can redistribute it and/or modify 76fcafd02SGreg Roach * it under the terms of the GNU General Public License as published by 86fcafd02SGreg Roach * the Free Software Foundation, either version 3 of the License, or 96fcafd02SGreg Roach * (at your option) any later version. 106fcafd02SGreg Roach * This program is distributed in the hope that it will be useful, 116fcafd02SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of 126fcafd02SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 136fcafd02SGreg Roach * GNU General Public License for more details. 146fcafd02SGreg Roach * You should have received a copy of the GNU General Public License 156fcafd02SGreg Roach * along with this program. If not, see <https://www.gnu.org/licenses/>. 166fcafd02SGreg Roach */ 176fcafd02SGreg Roach 186fcafd02SGreg Roachdeclare(strict_types=1); 196fcafd02SGreg Roach 206fcafd02SGreg Roachnamespace Fisharebest\Webtrees; 216fcafd02SGreg Roach 226fcafd02SGreg Roachuse Closure; 23665e281aSGreg Roachuse Fisharebest\Webtrees\Elements\PedigreeLinkageType; 24665e281aSGreg Roach 256fcafd02SGreg Roachuse function abs; 266fcafd02SGreg Roachuse function array_slice; 276fcafd02SGreg Roachuse function count; 286fcafd02SGreg Roachuse function in_array; 296fcafd02SGreg Roachuse function intdiv; 306fcafd02SGreg Roachuse function min; 317573bc3dSGreg Roachuse function var_dump; 326fcafd02SGreg Roach 336fcafd02SGreg Roach/** 346fcafd02SGreg Roach * Class Relationship - define a relationship for a language. 356fcafd02SGreg Roach */ 366fcafd02SGreg Roachclass Relationship 376fcafd02SGreg Roach{ 386fcafd02SGreg Roach // The basic components of a relationship. 396fcafd02SGreg Roach // These strings are needed for compatibility with the legacy algorithm. 406fcafd02SGreg Roach // Once that has been replaced, it may be more efficient to use integers here. 416fcafd02SGreg Roach public const SISTER = 'sis'; 426fcafd02SGreg Roach public const BROTHER = 'bro'; 436fcafd02SGreg Roach public const SIBLING = 'sib'; 446fcafd02SGreg Roach public const MOTHER = 'mot'; 456fcafd02SGreg Roach public const FATHER = 'fat'; 466fcafd02SGreg Roach public const PARENT = 'par'; 476fcafd02SGreg Roach public const DAUGHTER = 'dau'; 486fcafd02SGreg Roach public const SON = 'son'; 496fcafd02SGreg Roach public const CHILD = 'chi'; 506fcafd02SGreg Roach public const WIFE = 'wif'; 516fcafd02SGreg Roach public const HUSBAND = 'hus'; 526fcafd02SGreg Roach public const SPOUSE = 'spo'; 536fcafd02SGreg Roach 546fcafd02SGreg Roach public const SIBLINGS = ['F' => self::SISTER, 'M' => self::BROTHER, 'U' => self::SIBLING]; 556fcafd02SGreg Roach public const PARENTS = ['F' => self::MOTHER, 'M' => self::FATHER, 'U' => self::PARENT]; 566fcafd02SGreg Roach public const CHILDREN = ['F' => self::DAUGHTER, 'M' => self::SON, 'U' => self::CHILD]; 576fcafd02SGreg Roach public const SPOUSES = ['F' => self::WIFE, 'M' => self::HUSBAND, 'U' => self::SPOUSE]; 586fcafd02SGreg Roach 596fcafd02SGreg Roach // Generates a name from the matched relationship. 606fcafd02SGreg Roach private Closure $callback; 616fcafd02SGreg Roach 62696d9d5cSGreg Roach /** @var array<Closure> List of rules that need to match */ 636fcafd02SGreg Roach private array $matchers; 646fcafd02SGreg Roach 656fcafd02SGreg Roach /** 666fcafd02SGreg Roach * Relationship constructor. 676fcafd02SGreg Roach * 686fcafd02SGreg Roach * @param Closure $callback 696fcafd02SGreg Roach */ 706fcafd02SGreg Roach private function __construct(Closure $callback) 716fcafd02SGreg Roach { 726fcafd02SGreg Roach $this->callback = $callback; 736fcafd02SGreg Roach $this->matchers = []; 746fcafd02SGreg Roach } 756fcafd02SGreg Roach 766fcafd02SGreg Roach /** 776fcafd02SGreg Roach * Allow fluent constructor. 786fcafd02SGreg Roach * 796fcafd02SGreg Roach * @param string $nominative 806fcafd02SGreg Roach * @param string $genitive 816fcafd02SGreg Roach * 826fcafd02SGreg Roach * @return Relationship 836fcafd02SGreg Roach */ 846fcafd02SGreg Roach public static function fixed(string $nominative, string $genitive): Relationship 856fcafd02SGreg Roach { 86696d9d5cSGreg Roach return new self(fn () => [$nominative, $genitive]); 876fcafd02SGreg Roach } 886fcafd02SGreg Roach 896fcafd02SGreg Roach /** 906fcafd02SGreg Roach * Allow fluent constructor. 916fcafd02SGreg Roach * 926fcafd02SGreg Roach * @param Closure $callback 936fcafd02SGreg Roach * 946fcafd02SGreg Roach * @return Relationship 956fcafd02SGreg Roach */ 966fcafd02SGreg Roach public static function dynamic(Closure $callback): Relationship 976fcafd02SGreg Roach { 98696d9d5cSGreg Roach return new self($callback); 996fcafd02SGreg Roach } 1006fcafd02SGreg Roach 1016fcafd02SGreg Roach /** 1026fcafd02SGreg Roach * Does this relationship match the pattern? 1036fcafd02SGreg Roach * 1046fcafd02SGreg Roach * @param array<Individual|Family> $nodes 1056fcafd02SGreg Roach * @param array<string> $patterns 1066fcafd02SGreg Roach * 107696d9d5cSGreg Roach * @return array<string>|null [nominative, genitive] or null 1086fcafd02SGreg Roach */ 1096fcafd02SGreg Roach public function match(array $nodes, array $patterns): ?array 1106fcafd02SGreg Roach { 1116fcafd02SGreg Roach $captures = []; 1126fcafd02SGreg Roach 1136fcafd02SGreg Roach foreach ($this->matchers as $matcher) { 1146fcafd02SGreg Roach if (!$matcher($nodes, $patterns, $captures)) { 1156fcafd02SGreg Roach return null; 1166fcafd02SGreg Roach } 1176fcafd02SGreg Roach } 1186fcafd02SGreg Roach 1196fcafd02SGreg Roach if ($patterns === []) { 1206fcafd02SGreg Roach return ($this->callback)(...$captures); 1216fcafd02SGreg Roach } 1226fcafd02SGreg Roach 1236fcafd02SGreg Roach return null; 1246fcafd02SGreg Roach } 1256fcafd02SGreg Roach 1266fcafd02SGreg Roach /** 1276fcafd02SGreg Roach * @return Relationship 1286fcafd02SGreg Roach */ 1296fcafd02SGreg Roach public function adopted(): Relationship 1306fcafd02SGreg Roach { 13105babb96SGreg Roach $this->matchers[] = static fn (array $nodes): bool => count($nodes) > 2 && $nodes[2] 1326fcafd02SGreg Roach ->facts(['FAMC'], false, Auth::PRIV_HIDE) 133*88a03560SGreg Roach ->contains(fn (Fact $fact): bool => $fact->value() === '@' . $nodes[1]->xref() . '@' && $fact->attribute('PEDI') === PedigreeLinkageType::VALUE_ADOPTED); 1346fcafd02SGreg Roach 1356fcafd02SGreg Roach return $this; 1366fcafd02SGreg Roach } 1376fcafd02SGreg Roach 1386fcafd02SGreg Roach /** 1396fcafd02SGreg Roach * @return Relationship 1406fcafd02SGreg Roach */ 1416fcafd02SGreg Roach public function adoptive(): Relationship 1426fcafd02SGreg Roach { 14305babb96SGreg Roach $this->matchers[] = static fn (array $nodes): bool => $nodes[0] 1446fcafd02SGreg Roach ->facts(['FAMC'], false, Auth::PRIV_HIDE) 145*88a03560SGreg Roach ->contains(fn (Fact $fact): bool => $fact->value() === '@' . $nodes[1]->xref() . '@' && $fact->attribute('PEDI') === PedigreeLinkageType::VALUE_ADOPTED); 1466fcafd02SGreg Roach 1476fcafd02SGreg Roach return $this; 1486fcafd02SGreg Roach } 1496fcafd02SGreg Roach 1506fcafd02SGreg Roach /** 1516fcafd02SGreg Roach * @return Relationship 1526fcafd02SGreg Roach */ 1536fcafd02SGreg Roach public function brother(): Relationship 1546fcafd02SGreg Roach { 1556fcafd02SGreg Roach return $this->relation([self::BROTHER]); 1566fcafd02SGreg Roach } 1576fcafd02SGreg Roach 1586fcafd02SGreg Roach /** 1596fcafd02SGreg Roach * Match the next relationship in the path. 1606fcafd02SGreg Roach * 1616fcafd02SGreg Roach * @param array<string> $relationships 1626fcafd02SGreg Roach * 1636fcafd02SGreg Roach * @return Relationship 1646fcafd02SGreg Roach */ 1656fcafd02SGreg Roach protected function relation(array $relationships): Relationship 1666fcafd02SGreg Roach { 1676fcafd02SGreg Roach $this->matchers[] = static function (array &$nodes, array &$patterns) use ($relationships): bool { 1686fcafd02SGreg Roach if (in_array($patterns[0] ?? '', $relationships, true)) { 1696fcafd02SGreg Roach $nodes = array_slice($nodes, 2); 1706fcafd02SGreg Roach $patterns = array_slice($patterns, 1); 1716fcafd02SGreg Roach 1726fcafd02SGreg Roach return true; 1736fcafd02SGreg Roach } 1746fcafd02SGreg Roach 1756fcafd02SGreg Roach return false; 1766fcafd02SGreg Roach }; 1776fcafd02SGreg Roach 1786fcafd02SGreg Roach return $this; 1796fcafd02SGreg Roach } 1806fcafd02SGreg Roach 1816fcafd02SGreg Roach /** 1826fcafd02SGreg Roach * The number of ancestors may be different to the number of descendants 1836fcafd02SGreg Roach * 1846fcafd02SGreg Roach * @return Relationship 1856fcafd02SGreg Roach */ 186a004d330SGreg Roach public function cousin(): Relationship 1876fcafd02SGreg Roach { 1886fcafd02SGreg Roach return $this->ancestor()->sibling()->descendant(); 1896fcafd02SGreg Roach } 1906fcafd02SGreg Roach 1916fcafd02SGreg Roach /** 1926fcafd02SGreg Roach * @return Relationship 1936fcafd02SGreg Roach */ 1946fcafd02SGreg Roach public function descendant(): Relationship 1956fcafd02SGreg Roach { 1966fcafd02SGreg Roach return $this->repeatedRelationship(self::CHILDREN); 1976fcafd02SGreg Roach } 1986fcafd02SGreg Roach 1996fcafd02SGreg Roach /** 2006fcafd02SGreg Roach * Match a repeated number of the same type of component 2016fcafd02SGreg Roach * 2026fcafd02SGreg Roach * @param array<string> $relationships 2036fcafd02SGreg Roach * 2046fcafd02SGreg Roach * @return Relationship 2056fcafd02SGreg Roach */ 2066fcafd02SGreg Roach protected function repeatedRelationship(array $relationships): Relationship 2076fcafd02SGreg Roach { 2086fcafd02SGreg Roach $this->matchers[] = static function (array &$nodes, array &$patterns, array &$captures) use ($relationships): bool { 2096fcafd02SGreg Roach $limit = min(intdiv(count($nodes), 2), count($patterns)); 2106fcafd02SGreg Roach 2116fcafd02SGreg Roach for ($generations = 0; $generations < $limit; ++$generations) { 2126fcafd02SGreg Roach if (!in_array($patterns[$generations], $relationships, true)) { 2136fcafd02SGreg Roach break; 2146fcafd02SGreg Roach } 2156fcafd02SGreg Roach } 2166fcafd02SGreg Roach 2176fcafd02SGreg Roach if ($generations > 0) { 2186fcafd02SGreg Roach $nodes = array_slice($nodes, 2 * $generations); 2196fcafd02SGreg Roach $patterns = array_slice($patterns, $generations); 2206fcafd02SGreg Roach $captures[] = $generations; 2216fcafd02SGreg Roach 2226fcafd02SGreg Roach return true; 2236fcafd02SGreg Roach } 2246fcafd02SGreg Roach 2256fcafd02SGreg Roach return false; 2266fcafd02SGreg Roach }; 2276fcafd02SGreg Roach 2286fcafd02SGreg Roach return $this; 2296fcafd02SGreg Roach } 2306fcafd02SGreg Roach 2316fcafd02SGreg Roach /** 2326fcafd02SGreg Roach * @return Relationship 2336fcafd02SGreg Roach */ 2346fcafd02SGreg Roach public function sibling(): Relationship 2356fcafd02SGreg Roach { 2366fcafd02SGreg Roach return $this->relation(self::SIBLINGS); 2376fcafd02SGreg Roach } 2386fcafd02SGreg Roach 2396fcafd02SGreg Roach /** 2406fcafd02SGreg Roach * @return Relationship 2416fcafd02SGreg Roach */ 2426fcafd02SGreg Roach public function ancestor(): Relationship 2436fcafd02SGreg Roach { 2446fcafd02SGreg Roach return $this->repeatedRelationship(self::PARENTS); 2456fcafd02SGreg Roach } 2466fcafd02SGreg Roach 2476fcafd02SGreg Roach /** 2486fcafd02SGreg Roach * @return Relationship 2496fcafd02SGreg Roach */ 2506fcafd02SGreg Roach public function child(): Relationship 2516fcafd02SGreg Roach { 2526fcafd02SGreg Roach return $this->relation(self::CHILDREN); 2536fcafd02SGreg Roach } 2546fcafd02SGreg Roach 2556fcafd02SGreg Roach /** 2566fcafd02SGreg Roach * @return Relationship 2576fcafd02SGreg Roach */ 2586fcafd02SGreg Roach public function daughter(): Relationship 2596fcafd02SGreg Roach { 2606fcafd02SGreg Roach return $this->relation([self::DAUGHTER]); 2616fcafd02SGreg Roach } 2626fcafd02SGreg Roach 2636fcafd02SGreg Roach /** 2646fcafd02SGreg Roach * @return Relationship 2656fcafd02SGreg Roach */ 2666fcafd02SGreg Roach public function divorced(): Relationship 2676fcafd02SGreg Roach { 2686fcafd02SGreg Roach return $this->marriageStatus('DIV'); 2696fcafd02SGreg Roach } 2706fcafd02SGreg Roach 2716fcafd02SGreg Roach /** 2726fcafd02SGreg Roach * Match a marriage status 2736fcafd02SGreg Roach * 2746fcafd02SGreg Roach * @param string $status 2756fcafd02SGreg Roach * 2766fcafd02SGreg Roach * @return Relationship 2776fcafd02SGreg Roach */ 2786fcafd02SGreg Roach protected function marriageStatus(string $status): Relationship 2796fcafd02SGreg Roach { 2806fcafd02SGreg Roach $this->matchers[] = static function (array $nodes) use ($status): bool { 2816fcafd02SGreg Roach $family = $nodes[1] ?? null; 2826fcafd02SGreg Roach 2836fcafd02SGreg Roach if ($family instanceof Family) { 2846fcafd02SGreg Roach $fact = $family->facts(['ENGA', 'MARR', 'DIV', 'ANUL'], true, Auth::PRIV_HIDE)->last(); 2856fcafd02SGreg Roach 2866fcafd02SGreg Roach if ($fact instanceof Fact) { 2876fcafd02SGreg Roach switch ($status) { 2886fcafd02SGreg Roach case 'MARR': 2896fcafd02SGreg Roach return $fact->tag() === 'FAM:MARR'; 2906fcafd02SGreg Roach 2916fcafd02SGreg Roach case 'DIV': 2926fcafd02SGreg Roach return $fact->tag() === 'FAM:DIV' || $fact->tag() === 'FAM:ANUL'; 2936fcafd02SGreg Roach 2946fcafd02SGreg Roach case 'ENGA': 2956fcafd02SGreg Roach return $fact->tag() === 'FAM:ENGA'; 2966fcafd02SGreg Roach } 2976fcafd02SGreg Roach } 2986fcafd02SGreg Roach } 2996fcafd02SGreg Roach 3006fcafd02SGreg Roach return false; 3016fcafd02SGreg Roach }; 3026fcafd02SGreg Roach 3036fcafd02SGreg Roach return $this; 3046fcafd02SGreg Roach } 3056fcafd02SGreg Roach 3066fcafd02SGreg Roach /** 3076fcafd02SGreg Roach * @return Relationship 3086fcafd02SGreg Roach */ 3096fcafd02SGreg Roach public function engaged(): Relationship 3106fcafd02SGreg Roach { 3116fcafd02SGreg Roach return $this->marriageStatus('ENGA'); 3126fcafd02SGreg Roach } 3136fcafd02SGreg Roach 3146fcafd02SGreg Roach /** 3156fcafd02SGreg Roach * @return Relationship 3166fcafd02SGreg Roach */ 3176fcafd02SGreg Roach public function father(): Relationship 3186fcafd02SGreg Roach { 3196fcafd02SGreg Roach return $this->relation([self::FATHER]); 3206fcafd02SGreg Roach } 3216fcafd02SGreg Roach 3226fcafd02SGreg Roach /** 3236fcafd02SGreg Roach * @return Relationship 3246fcafd02SGreg Roach */ 3256fcafd02SGreg Roach public function female(): Relationship 3266fcafd02SGreg Roach { 3276fcafd02SGreg Roach return $this->sex('F'); 3286fcafd02SGreg Roach } 3296fcafd02SGreg Roach 3306fcafd02SGreg Roach /** 3316fcafd02SGreg Roach * Match the sex of the current individual 3326fcafd02SGreg Roach * 3336fcafd02SGreg Roach * @param string $sex 3346fcafd02SGreg Roach * 3356fcafd02SGreg Roach * @return Relationship 3366fcafd02SGreg Roach */ 3376fcafd02SGreg Roach protected function sex(string $sex): Relationship 3386fcafd02SGreg Roach { 3396fcafd02SGreg Roach $this->matchers[] = static function (array $nodes) use ($sex): bool { 3406fcafd02SGreg Roach return $nodes[0]->sex() === $sex; 3416fcafd02SGreg Roach }; 3426fcafd02SGreg Roach 3436fcafd02SGreg Roach return $this; 3446fcafd02SGreg Roach } 3456fcafd02SGreg Roach 3466fcafd02SGreg Roach /** 3476fcafd02SGreg Roach * @return Relationship 3486fcafd02SGreg Roach */ 3496fcafd02SGreg Roach public function fostered(): Relationship 3506fcafd02SGreg Roach { 35105babb96SGreg Roach $this->matchers[] = static fn (array $nodes): bool => count($nodes) > 2 && $nodes[2] 3526fcafd02SGreg Roach ->facts(['FAMC'], false, Auth::PRIV_HIDE) 353*88a03560SGreg Roach ->contains(fn (Fact $fact): bool => $fact->value() === '@' . $nodes[1]->xref() . '@' && $fact->attribute('PEDI') === PedigreeLinkageType::VALUE_FOSTER); 3546fcafd02SGreg Roach 3556fcafd02SGreg Roach return $this; 3566fcafd02SGreg Roach } 3576fcafd02SGreg Roach 3586fcafd02SGreg Roach /** 3596fcafd02SGreg Roach * @return Relationship 3606fcafd02SGreg Roach */ 3616fcafd02SGreg Roach public function fostering(): Relationship 3626fcafd02SGreg Roach { 36305babb96SGreg Roach $this->matchers[] = static fn (array $nodes): bool => $nodes[0] 3646fcafd02SGreg Roach ->facts(['FAMC'], false, Auth::PRIV_HIDE) 365*88a03560SGreg Roach ->contains(fn (Fact $fact): bool => $fact->value() === '@' . $nodes[1]->xref() . '@' && $fact->attribute('PEDI') === PedigreeLinkageType::VALUE_FOSTER); 3666fcafd02SGreg Roach 3676fcafd02SGreg Roach return $this; 3686fcafd02SGreg Roach } 3696fcafd02SGreg Roach 3706fcafd02SGreg Roach /** 3716fcafd02SGreg Roach * @return Relationship 3726fcafd02SGreg Roach */ 3736fcafd02SGreg Roach public function husband(): Relationship 3746fcafd02SGreg Roach { 3756fcafd02SGreg Roach return $this->married()->relation([self::HUSBAND]); 3766fcafd02SGreg Roach } 3776fcafd02SGreg Roach 3786fcafd02SGreg Roach /** 3796fcafd02SGreg Roach * @return Relationship 3806fcafd02SGreg Roach */ 3816fcafd02SGreg Roach public function married(): Relationship 3826fcafd02SGreg Roach { 3836fcafd02SGreg Roach return $this->marriageStatus('MARR'); 3846fcafd02SGreg Roach } 3856fcafd02SGreg Roach 3866fcafd02SGreg Roach /** 3876fcafd02SGreg Roach * @return Relationship 3886fcafd02SGreg Roach */ 3896fcafd02SGreg Roach public function male(): Relationship 3906fcafd02SGreg Roach { 3916fcafd02SGreg Roach return $this->sex('M'); 3926fcafd02SGreg Roach } 3936fcafd02SGreg Roach 3946fcafd02SGreg Roach /** 3956fcafd02SGreg Roach * @return Relationship 3966fcafd02SGreg Roach */ 3976fcafd02SGreg Roach public function mother(): Relationship 3986fcafd02SGreg Roach { 3996fcafd02SGreg Roach return $this->relation([self::MOTHER]); 4006fcafd02SGreg Roach } 4016fcafd02SGreg Roach 4026fcafd02SGreg Roach /** 4036fcafd02SGreg Roach * @return Relationship 4046fcafd02SGreg Roach */ 4056fcafd02SGreg Roach public function older(): Relationship 4066fcafd02SGreg Roach { 4076fcafd02SGreg Roach $this->matchers[] = static function (array $nodes): bool { 4086fcafd02SGreg Roach $date1 = $nodes[0]->facts(['BIRT'], false, Auth::PRIV_HIDE)->map(fn (Fact $fact): Date => $fact->date())->first() ?? new Date(''); 4096fcafd02SGreg Roach $date2 = $nodes[2]->facts(['BIRT'], false, Auth::PRIV_HIDE)->map(fn (Fact $fact): Date => $fact->date())->first() ?? new Date(''); 4106fcafd02SGreg Roach 411d8a5ab6eSJonathan Jaubart return Date::compare($date1, $date2) > 0; 4126fcafd02SGreg Roach }; 4136fcafd02SGreg Roach 4146fcafd02SGreg Roach return $this; 4156fcafd02SGreg Roach } 4166fcafd02SGreg Roach 4176fcafd02SGreg Roach /** 4186fcafd02SGreg Roach * @return Relationship 4196fcafd02SGreg Roach */ 4206fcafd02SGreg Roach public function parent(): Relationship 4216fcafd02SGreg Roach { 4226fcafd02SGreg Roach return $this->relation(self::PARENTS); 4236fcafd02SGreg Roach } 4246fcafd02SGreg Roach 4256fcafd02SGreg Roach /** 4266fcafd02SGreg Roach * @return Relationship 4276fcafd02SGreg Roach */ 4286fcafd02SGreg Roach public function sister(): Relationship 4296fcafd02SGreg Roach { 4306fcafd02SGreg Roach return $this->relation([self::SISTER]); 4316fcafd02SGreg Roach } 4326fcafd02SGreg Roach 4336fcafd02SGreg Roach /** 4346fcafd02SGreg Roach * @return Relationship 4356fcafd02SGreg Roach */ 4366fcafd02SGreg Roach public function son(): Relationship 4376fcafd02SGreg Roach { 4386fcafd02SGreg Roach return $this->relation([self::SON]); 4396fcafd02SGreg Roach } 4406fcafd02SGreg Roach 4416fcafd02SGreg Roach /** 4426fcafd02SGreg Roach * @return Relationship 4436fcafd02SGreg Roach */ 4446fcafd02SGreg Roach public function spouse(): Relationship 4456fcafd02SGreg Roach { 4466fcafd02SGreg Roach return $this->married()->partner(); 4476fcafd02SGreg Roach } 4486fcafd02SGreg Roach 4496fcafd02SGreg Roach /** 4506fcafd02SGreg Roach * @return Relationship 4516fcafd02SGreg Roach */ 4526fcafd02SGreg Roach public function partner(): Relationship 4536fcafd02SGreg Roach { 4546fcafd02SGreg Roach return $this->relation(self::SPOUSES); 4556fcafd02SGreg Roach } 4566fcafd02SGreg Roach 4576fcafd02SGreg Roach /** 4586fcafd02SGreg Roach * The number of ancestors must be the same as the number of descendants 4596fcafd02SGreg Roach * 4606fcafd02SGreg Roach * @return Relationship 4616fcafd02SGreg Roach */ 4626fcafd02SGreg Roach public function symmetricCousin(): Relationship 4636fcafd02SGreg Roach { 4646fcafd02SGreg Roach $this->matchers[] = static function (array &$nodes, array &$patterns, array &$captures): bool { 4656fcafd02SGreg Roach $count = count($patterns); 4666fcafd02SGreg Roach 4676fcafd02SGreg Roach $n = 0; 4686fcafd02SGreg Roach 4696fcafd02SGreg Roach // Ancestors 4706fcafd02SGreg Roach while ($n < $count && in_array($patterns[$n], Relationship::PARENTS, true)) { 4716fcafd02SGreg Roach $n++; 4726fcafd02SGreg Roach } 4736fcafd02SGreg Roach 4746fcafd02SGreg Roach // No ancestors? Not enough path left for descendants? 4756fcafd02SGreg Roach if ($n === 0 || $n * 2 + 1 !== $count) { 4766fcafd02SGreg Roach return false; 4776fcafd02SGreg Roach } 4786fcafd02SGreg Roach 4796fcafd02SGreg Roach // Siblings 4806fcafd02SGreg Roach if (!in_array($patterns[$n], Relationship::SIBLINGS, true)) { 4816fcafd02SGreg Roach return false; 4826fcafd02SGreg Roach } 4836fcafd02SGreg Roach 4846fcafd02SGreg Roach // Descendants 4856fcafd02SGreg Roach for ($descendants = $n + 1; $descendants < $count; ++$descendants) { 4866fcafd02SGreg Roach if (!in_array($patterns[$descendants], Relationship::CHILDREN, true)) { 4876fcafd02SGreg Roach return false; 4886fcafd02SGreg Roach } 4896fcafd02SGreg Roach } 4906fcafd02SGreg Roach 4916fcafd02SGreg Roach 492d8a5ab6eSJonathan Jaubart $nodes = array_slice($nodes, 2 * (2 * $n + 1)); 4936fcafd02SGreg Roach $patterns = []; 4946fcafd02SGreg Roach $captures[] = $n; 4956fcafd02SGreg Roach 4966fcafd02SGreg Roach return true; 4976fcafd02SGreg Roach }; 4986fcafd02SGreg Roach 4996fcafd02SGreg Roach return $this; 5006fcafd02SGreg Roach } 5016fcafd02SGreg Roach 5026fcafd02SGreg Roach /** 5036fcafd02SGreg Roach * @return Relationship 5046fcafd02SGreg Roach */ 5056fcafd02SGreg Roach public function twin(): Relationship 5066fcafd02SGreg Roach { 5076fcafd02SGreg Roach $this->matchers[] = static function (array $nodes): bool { 5086fcafd02SGreg Roach $date1 = $nodes[0]->facts(['BIRT'], false, Auth::PRIV_HIDE)->map(fn (Fact $fact): Date => $fact->date())->first() ?? new Date(''); 5096fcafd02SGreg Roach $date2 = $nodes[2]->facts(['BIRT'], false, Auth::PRIV_HIDE)->map(fn (Fact $fact): Date => $fact->date())->first() ?? new Date(''); 5106fcafd02SGreg Roach 5116fcafd02SGreg Roach return 5126fcafd02SGreg Roach $date1->isOK() && 5136fcafd02SGreg Roach $date2->isOK() && 5146fcafd02SGreg Roach abs($date1->julianDay() - $date2->julianDay()) < 2 && 5156fcafd02SGreg Roach $date1->minimumDate()->day > 0 && 5166fcafd02SGreg Roach $date2->minimumDate()->day > 0; 5176fcafd02SGreg Roach }; 5186fcafd02SGreg Roach 5196fcafd02SGreg Roach return $this; 5206fcafd02SGreg Roach } 5216fcafd02SGreg Roach 5226fcafd02SGreg Roach /** 5236fcafd02SGreg Roach * @return Relationship 5246fcafd02SGreg Roach */ 5256fcafd02SGreg Roach public function wife(): Relationship 5266fcafd02SGreg Roach { 5276fcafd02SGreg Roach return $this->married()->relation([self::WIFE]); 5286fcafd02SGreg Roach } 5296fcafd02SGreg Roach 5306fcafd02SGreg Roach /** 5316fcafd02SGreg Roach * @return Relationship 5326fcafd02SGreg Roach */ 5336fcafd02SGreg Roach public function younger(): Relationship 5346fcafd02SGreg Roach { 5356fcafd02SGreg Roach $this->matchers[] = static function (array $nodes): bool { 5366fcafd02SGreg Roach $date1 = $nodes[0]->facts(['BIRT'], false, Auth::PRIV_HIDE)->map(fn (Fact $fact): Date => $fact->date())->first() ?? new Date(''); 5376fcafd02SGreg Roach $date2 = $nodes[2]->facts(['BIRT'], false, Auth::PRIV_HIDE)->map(fn (Fact $fact): Date => $fact->date())->first() ?? new Date(''); 5386fcafd02SGreg Roach 539d8a5ab6eSJonathan Jaubart return Date::compare($date1, $date2) < 0; 5406fcafd02SGreg Roach }; 5416fcafd02SGreg Roach 5426fcafd02SGreg Roach return $this; 5436fcafd02SGreg Roach } 5446fcafd02SGreg Roach} 545