1*6fcafd02SGreg Roach<?php 2*6fcafd02SGreg Roach 3*6fcafd02SGreg Roach/** 4*6fcafd02SGreg Roach * webtrees: online genealogy 5*6fcafd02SGreg Roach * Copyright (C) 2021 webtrees development team 6*6fcafd02SGreg Roach * This program is free software: you can redistribute it and/or modify 7*6fcafd02SGreg Roach * it under the terms of the GNU General Public License as published by 8*6fcafd02SGreg Roach * the Free Software Foundation, either version 3 of the License, or 9*6fcafd02SGreg Roach * (at your option) any later version. 10*6fcafd02SGreg Roach * This program is distributed in the hope that it will be useful, 11*6fcafd02SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of 12*6fcafd02SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13*6fcafd02SGreg Roach * GNU General Public License for more details. 14*6fcafd02SGreg Roach * You should have received a copy of the GNU General Public License 15*6fcafd02SGreg Roach * along with this program. If not, see <https://www.gnu.org/licenses/>. 16*6fcafd02SGreg Roach */ 17*6fcafd02SGreg Roach 18*6fcafd02SGreg Roachdeclare(strict_types=1); 19*6fcafd02SGreg Roach 20*6fcafd02SGreg Roachnamespace Fisharebest\Webtrees; 21*6fcafd02SGreg Roach 22*6fcafd02SGreg Roachuse Closure; 23*6fcafd02SGreg Roach 24*6fcafd02SGreg Roachuse function abs; 25*6fcafd02SGreg Roachuse function array_slice; 26*6fcafd02SGreg Roachuse function count; 27*6fcafd02SGreg Roachuse function in_array; 28*6fcafd02SGreg Roachuse function intdiv; 29*6fcafd02SGreg Roachuse function min; 30*6fcafd02SGreg Roach 31*6fcafd02SGreg Roach/** 32*6fcafd02SGreg Roach * Class Relationship - define a relationship for a language. 33*6fcafd02SGreg Roach */ 34*6fcafd02SGreg Roachclass Relationship 35*6fcafd02SGreg Roach{ 36*6fcafd02SGreg Roach // The basic components of a relationship. 37*6fcafd02SGreg Roach // These strings are needed for compatibility with the legacy algorithm. 38*6fcafd02SGreg Roach // Once that has been replaced, it may be more efficient to use integers here. 39*6fcafd02SGreg Roach public const SISTER = 'sis'; 40*6fcafd02SGreg Roach public const BROTHER = 'bro'; 41*6fcafd02SGreg Roach public const SIBLING = 'sib'; 42*6fcafd02SGreg Roach public const MOTHER = 'mot'; 43*6fcafd02SGreg Roach public const FATHER = 'fat'; 44*6fcafd02SGreg Roach public const PARENT = 'par'; 45*6fcafd02SGreg Roach public const DAUGHTER = 'dau'; 46*6fcafd02SGreg Roach public const SON = 'son'; 47*6fcafd02SGreg Roach public const CHILD = 'chi'; 48*6fcafd02SGreg Roach public const WIFE = 'wif'; 49*6fcafd02SGreg Roach public const HUSBAND = 'hus'; 50*6fcafd02SGreg Roach public const SPOUSE = 'spo'; 51*6fcafd02SGreg Roach 52*6fcafd02SGreg Roach public const SIBLINGS = ['F' => self::SISTER, 'M' => self::BROTHER, 'U' => self::SIBLING]; 53*6fcafd02SGreg Roach public const PARENTS = ['F' => self::MOTHER, 'M' => self::FATHER, 'U' => self::PARENT]; 54*6fcafd02SGreg Roach public const CHILDREN = ['F' => self::DAUGHTER, 'M' => self::SON, 'U' => self::CHILD]; 55*6fcafd02SGreg Roach public const SPOUSES = ['F' => self::WIFE, 'M' => self::HUSBAND, 'U' => self::SPOUSE]; 56*6fcafd02SGreg Roach 57*6fcafd02SGreg Roach // Generates a name from the matched relationship. 58*6fcafd02SGreg Roach private Closure $callback; 59*6fcafd02SGreg Roach 60*6fcafd02SGreg Roach // List of rules that need to match. 61*6fcafd02SGreg Roach private array $matchers; 62*6fcafd02SGreg Roach 63*6fcafd02SGreg Roach /** 64*6fcafd02SGreg Roach * Relationship constructor. 65*6fcafd02SGreg Roach * 66*6fcafd02SGreg Roach * @param Closure $callback 67*6fcafd02SGreg Roach */ 68*6fcafd02SGreg Roach private function __construct(Closure $callback) 69*6fcafd02SGreg Roach { 70*6fcafd02SGreg Roach $this->callback = $callback; 71*6fcafd02SGreg Roach $this->matchers = []; 72*6fcafd02SGreg Roach } 73*6fcafd02SGreg Roach 74*6fcafd02SGreg Roach /** 75*6fcafd02SGreg Roach * Allow fluent constructor. 76*6fcafd02SGreg Roach * 77*6fcafd02SGreg Roach * @param string $nominative 78*6fcafd02SGreg Roach * @param string $genitive 79*6fcafd02SGreg Roach * 80*6fcafd02SGreg Roach * @return Relationship 81*6fcafd02SGreg Roach */ 82*6fcafd02SGreg Roach public static function fixed(string $nominative, string $genitive): Relationship 83*6fcafd02SGreg Roach { 84*6fcafd02SGreg Roach return new static(fn () => [$nominative, $genitive]); 85*6fcafd02SGreg Roach } 86*6fcafd02SGreg Roach 87*6fcafd02SGreg Roach /** 88*6fcafd02SGreg Roach * Allow fluent constructor. 89*6fcafd02SGreg Roach * 90*6fcafd02SGreg Roach * @param Closure $callback 91*6fcafd02SGreg Roach * 92*6fcafd02SGreg Roach * @return Relationship 93*6fcafd02SGreg Roach */ 94*6fcafd02SGreg Roach public static function dynamic(Closure $callback): Relationship 95*6fcafd02SGreg Roach { 96*6fcafd02SGreg Roach return new static($callback); 97*6fcafd02SGreg Roach } 98*6fcafd02SGreg Roach 99*6fcafd02SGreg Roach /** 100*6fcafd02SGreg Roach * Does this relationship match the pattern? 101*6fcafd02SGreg Roach * 102*6fcafd02SGreg Roach * @param array<Individual|Family> $nodes 103*6fcafd02SGreg Roach * @param array<string> $patterns 104*6fcafd02SGreg Roach * 105*6fcafd02SGreg Roach * @return array [nominative, genitive] or null 106*6fcafd02SGreg Roach */ 107*6fcafd02SGreg Roach public function match(array $nodes, array $patterns): ?array 108*6fcafd02SGreg Roach { 109*6fcafd02SGreg Roach $captures = []; 110*6fcafd02SGreg Roach 111*6fcafd02SGreg Roach foreach ($this->matchers as $matcher) { 112*6fcafd02SGreg Roach if (!$matcher($nodes, $patterns, $captures)) { 113*6fcafd02SGreg Roach return null; 114*6fcafd02SGreg Roach } 115*6fcafd02SGreg Roach } 116*6fcafd02SGreg Roach 117*6fcafd02SGreg Roach if ($patterns === []) { 118*6fcafd02SGreg Roach return ($this->callback)(...$captures); 119*6fcafd02SGreg Roach } 120*6fcafd02SGreg Roach 121*6fcafd02SGreg Roach return null; 122*6fcafd02SGreg Roach } 123*6fcafd02SGreg Roach 124*6fcafd02SGreg Roach /** 125*6fcafd02SGreg Roach * @return Relationship 126*6fcafd02SGreg Roach */ 127*6fcafd02SGreg Roach public function adopted(): Relationship 128*6fcafd02SGreg Roach { 129*6fcafd02SGreg Roach $this->matchers[] = fn (array $nodes): bool => count($nodes) > 2 && $nodes[2] 130*6fcafd02SGreg Roach ->facts(['FAMC'], false, Auth::PRIV_HIDE) 131*6fcafd02SGreg Roach ->contains(fn (Fact $fact): bool => $fact->value() === '@' . $nodes[1]->xref() . '@' && $fact->attribute('PEDI') === 'adopted'); 132*6fcafd02SGreg Roach 133*6fcafd02SGreg Roach return $this; 134*6fcafd02SGreg Roach } 135*6fcafd02SGreg Roach 136*6fcafd02SGreg Roach /** 137*6fcafd02SGreg Roach * @return Relationship 138*6fcafd02SGreg Roach */ 139*6fcafd02SGreg Roach public function adoptive(): Relationship 140*6fcafd02SGreg Roach { 141*6fcafd02SGreg Roach $this->matchers[] = fn (array $nodes): bool => $nodes[0] 142*6fcafd02SGreg Roach ->facts(['FAMC'], false, Auth::PRIV_HIDE) 143*6fcafd02SGreg Roach ->contains(fn (Fact $fact): bool => $fact->value() === '@' . $nodes[1]->xref() . '@' && $fact->attribute('PEDI') === 'adopted'); 144*6fcafd02SGreg Roach 145*6fcafd02SGreg Roach return $this; 146*6fcafd02SGreg Roach } 147*6fcafd02SGreg Roach 148*6fcafd02SGreg Roach /** 149*6fcafd02SGreg Roach * @return Relationship 150*6fcafd02SGreg Roach */ 151*6fcafd02SGreg Roach public function brother(): Relationship 152*6fcafd02SGreg Roach { 153*6fcafd02SGreg Roach return $this->relation([self::BROTHER]); 154*6fcafd02SGreg Roach } 155*6fcafd02SGreg Roach 156*6fcafd02SGreg Roach /** 157*6fcafd02SGreg Roach * Match the next relationship in the path. 158*6fcafd02SGreg Roach * 159*6fcafd02SGreg Roach * @param array<string> $relationships 160*6fcafd02SGreg Roach * 161*6fcafd02SGreg Roach * @return Relationship 162*6fcafd02SGreg Roach */ 163*6fcafd02SGreg Roach protected function relation(array $relationships): Relationship 164*6fcafd02SGreg Roach { 165*6fcafd02SGreg Roach $this->matchers[] = static function (array &$nodes, array &$patterns) use ($relationships): bool { 166*6fcafd02SGreg Roach if (in_array($patterns[0] ?? '', $relationships, true)) { 167*6fcafd02SGreg Roach $nodes = array_slice($nodes, 2); 168*6fcafd02SGreg Roach $patterns = array_slice($patterns, 1); 169*6fcafd02SGreg Roach 170*6fcafd02SGreg Roach return true; 171*6fcafd02SGreg Roach } 172*6fcafd02SGreg Roach 173*6fcafd02SGreg Roach return false; 174*6fcafd02SGreg Roach }; 175*6fcafd02SGreg Roach 176*6fcafd02SGreg Roach return $this; 177*6fcafd02SGreg Roach } 178*6fcafd02SGreg Roach 179*6fcafd02SGreg Roach /** 180*6fcafd02SGreg Roach * The number of ancestors may be different to the number of descendants 181*6fcafd02SGreg Roach * 182*6fcafd02SGreg Roach * @return Relationship 183*6fcafd02SGreg Roach */ 184*6fcafd02SGreg Roach public function asymmetricCousin(): Relationship 185*6fcafd02SGreg Roach { 186*6fcafd02SGreg Roach return $this->ancestor()->sibling()->descendant(); 187*6fcafd02SGreg Roach } 188*6fcafd02SGreg Roach 189*6fcafd02SGreg Roach /** 190*6fcafd02SGreg Roach * @return Relationship 191*6fcafd02SGreg Roach */ 192*6fcafd02SGreg Roach public function descendant(): Relationship 193*6fcafd02SGreg Roach { 194*6fcafd02SGreg Roach return $this->repeatedRelationship(self::CHILDREN); 195*6fcafd02SGreg Roach } 196*6fcafd02SGreg Roach 197*6fcafd02SGreg Roach /** 198*6fcafd02SGreg Roach * Match a repeated number of the same type of component 199*6fcafd02SGreg Roach * 200*6fcafd02SGreg Roach * @param array<string> $relationships 201*6fcafd02SGreg Roach * 202*6fcafd02SGreg Roach * @return Relationship 203*6fcafd02SGreg Roach */ 204*6fcafd02SGreg Roach protected function repeatedRelationship(array $relationships): Relationship 205*6fcafd02SGreg Roach { 206*6fcafd02SGreg Roach $this->matchers[] = static function (array &$nodes, array &$patterns, array &$captures) use ($relationships): bool { 207*6fcafd02SGreg Roach $limit = min(intdiv(count($nodes), 2), count($patterns)); 208*6fcafd02SGreg Roach 209*6fcafd02SGreg Roach for ($generations = 0; $generations < $limit; ++$generations) { 210*6fcafd02SGreg Roach if (!in_array($patterns[$generations], $relationships, true)) { 211*6fcafd02SGreg Roach break; 212*6fcafd02SGreg Roach } 213*6fcafd02SGreg Roach } 214*6fcafd02SGreg Roach 215*6fcafd02SGreg Roach if ($generations > 0) { 216*6fcafd02SGreg Roach $nodes = array_slice($nodes, 2 * $generations); 217*6fcafd02SGreg Roach $patterns = array_slice($patterns, $generations); 218*6fcafd02SGreg Roach $captures[] = $generations; 219*6fcafd02SGreg Roach 220*6fcafd02SGreg Roach return true; 221*6fcafd02SGreg Roach } 222*6fcafd02SGreg Roach 223*6fcafd02SGreg Roach return false; 224*6fcafd02SGreg Roach }; 225*6fcafd02SGreg Roach 226*6fcafd02SGreg Roach return $this; 227*6fcafd02SGreg Roach } 228*6fcafd02SGreg Roach 229*6fcafd02SGreg Roach /** 230*6fcafd02SGreg Roach * @return Relationship 231*6fcafd02SGreg Roach */ 232*6fcafd02SGreg Roach public function sibling(): Relationship 233*6fcafd02SGreg Roach { 234*6fcafd02SGreg Roach return $this->relation(self::SIBLINGS); 235*6fcafd02SGreg Roach } 236*6fcafd02SGreg Roach 237*6fcafd02SGreg Roach /** 238*6fcafd02SGreg Roach * @return Relationship 239*6fcafd02SGreg Roach */ 240*6fcafd02SGreg Roach public function ancestor(): Relationship 241*6fcafd02SGreg Roach { 242*6fcafd02SGreg Roach return $this->repeatedRelationship(self::PARENTS); 243*6fcafd02SGreg Roach } 244*6fcafd02SGreg Roach 245*6fcafd02SGreg Roach /** 246*6fcafd02SGreg Roach * @return Relationship 247*6fcafd02SGreg Roach */ 248*6fcafd02SGreg Roach public function child(): Relationship 249*6fcafd02SGreg Roach { 250*6fcafd02SGreg Roach return $this->relation(self::CHILDREN); 251*6fcafd02SGreg Roach } 252*6fcafd02SGreg Roach 253*6fcafd02SGreg Roach /** 254*6fcafd02SGreg Roach * @return Relationship 255*6fcafd02SGreg Roach */ 256*6fcafd02SGreg Roach public function daughter(): Relationship 257*6fcafd02SGreg Roach { 258*6fcafd02SGreg Roach return $this->relation([self::DAUGHTER]); 259*6fcafd02SGreg Roach } 260*6fcafd02SGreg Roach 261*6fcafd02SGreg Roach /** 262*6fcafd02SGreg Roach * @return Relationship 263*6fcafd02SGreg Roach */ 264*6fcafd02SGreg Roach public function divorced(): Relationship 265*6fcafd02SGreg Roach { 266*6fcafd02SGreg Roach return $this->marriageStatus('DIV'); 267*6fcafd02SGreg Roach } 268*6fcafd02SGreg Roach 269*6fcafd02SGreg Roach /** 270*6fcafd02SGreg Roach * Match a marriage status 271*6fcafd02SGreg Roach * 272*6fcafd02SGreg Roach * @param string $status 273*6fcafd02SGreg Roach * 274*6fcafd02SGreg Roach * @return Relationship 275*6fcafd02SGreg Roach */ 276*6fcafd02SGreg Roach protected function marriageStatus(string $status): Relationship 277*6fcafd02SGreg Roach { 278*6fcafd02SGreg Roach $this->matchers[] = static function (array $nodes) use ($status): bool { 279*6fcafd02SGreg Roach $family = $nodes[1] ?? null; 280*6fcafd02SGreg Roach 281*6fcafd02SGreg Roach if ($family instanceof Family) { 282*6fcafd02SGreg Roach $fact = $family->facts(['ENGA', 'MARR', 'DIV', 'ANUL'], true, Auth::PRIV_HIDE)->last(); 283*6fcafd02SGreg Roach 284*6fcafd02SGreg Roach if ($fact instanceof Fact) { 285*6fcafd02SGreg Roach switch ($status) { 286*6fcafd02SGreg Roach case 'MARR': 287*6fcafd02SGreg Roach return $fact->tag() === 'FAM:MARR'; 288*6fcafd02SGreg Roach 289*6fcafd02SGreg Roach case 'DIV': 290*6fcafd02SGreg Roach return $fact->tag() === 'FAM:DIV' || $fact->tag() === 'FAM:ANUL'; 291*6fcafd02SGreg Roach 292*6fcafd02SGreg Roach case 'ENGA': 293*6fcafd02SGreg Roach return $fact->tag() === 'FAM:ENGA'; 294*6fcafd02SGreg Roach } 295*6fcafd02SGreg Roach } 296*6fcafd02SGreg Roach } 297*6fcafd02SGreg Roach 298*6fcafd02SGreg Roach return false; 299*6fcafd02SGreg Roach }; 300*6fcafd02SGreg Roach 301*6fcafd02SGreg Roach return $this; 302*6fcafd02SGreg Roach } 303*6fcafd02SGreg Roach 304*6fcafd02SGreg Roach /** 305*6fcafd02SGreg Roach * @return Relationship 306*6fcafd02SGreg Roach */ 307*6fcafd02SGreg Roach public function engaged(): Relationship 308*6fcafd02SGreg Roach { 309*6fcafd02SGreg Roach return $this->marriageStatus('ENGA'); 310*6fcafd02SGreg Roach } 311*6fcafd02SGreg Roach 312*6fcafd02SGreg Roach /** 313*6fcafd02SGreg Roach * @return Relationship 314*6fcafd02SGreg Roach */ 315*6fcafd02SGreg Roach public function father(): Relationship 316*6fcafd02SGreg Roach { 317*6fcafd02SGreg Roach return $this->relation([self::FATHER]); 318*6fcafd02SGreg Roach } 319*6fcafd02SGreg Roach 320*6fcafd02SGreg Roach /** 321*6fcafd02SGreg Roach * @return Relationship 322*6fcafd02SGreg Roach */ 323*6fcafd02SGreg Roach public function female(): Relationship 324*6fcafd02SGreg Roach { 325*6fcafd02SGreg Roach return $this->sex('F'); 326*6fcafd02SGreg Roach } 327*6fcafd02SGreg Roach 328*6fcafd02SGreg Roach /** 329*6fcafd02SGreg Roach * Match the sex of the current individual 330*6fcafd02SGreg Roach * 331*6fcafd02SGreg Roach * @param string $sex 332*6fcafd02SGreg Roach * 333*6fcafd02SGreg Roach * @return Relationship 334*6fcafd02SGreg Roach */ 335*6fcafd02SGreg Roach protected function sex(string $sex): Relationship 336*6fcafd02SGreg Roach { 337*6fcafd02SGreg Roach $this->matchers[] = static function (array $nodes) use ($sex): bool { 338*6fcafd02SGreg Roach return $nodes[0]->sex() === $sex; 339*6fcafd02SGreg Roach }; 340*6fcafd02SGreg Roach 341*6fcafd02SGreg Roach return $this; 342*6fcafd02SGreg Roach } 343*6fcafd02SGreg Roach 344*6fcafd02SGreg Roach /** 345*6fcafd02SGreg Roach * @return Relationship 346*6fcafd02SGreg Roach */ 347*6fcafd02SGreg Roach public function fostered(): Relationship 348*6fcafd02SGreg Roach { 349*6fcafd02SGreg Roach $this->matchers[] = fn (array $nodes): bool => count($nodes) > 2 && $nodes[2] 350*6fcafd02SGreg Roach ->facts(['FAMC'], false, Auth::PRIV_HIDE) 351*6fcafd02SGreg Roach ->contains(fn (Fact $fact): bool => $fact->value() === '@' . $nodes[1]->xref() . '@' && $fact->attribute('PEDI') === 'foster'); 352*6fcafd02SGreg Roach 353*6fcafd02SGreg Roach return $this; 354*6fcafd02SGreg Roach } 355*6fcafd02SGreg Roach 356*6fcafd02SGreg Roach /** 357*6fcafd02SGreg Roach * @return Relationship 358*6fcafd02SGreg Roach */ 359*6fcafd02SGreg Roach public function fostering(): Relationship 360*6fcafd02SGreg Roach { 361*6fcafd02SGreg Roach $this->matchers[] = fn (array $nodes): bool => $nodes[0] 362*6fcafd02SGreg Roach ->facts(['FAMC'], false, Auth::PRIV_HIDE) 363*6fcafd02SGreg Roach ->contains(fn (Fact $fact): bool => $fact->value() === '@' . $nodes[1]->xref() . '@' && $fact->attribute('PEDI') === 'foster'); 364*6fcafd02SGreg Roach 365*6fcafd02SGreg Roach return $this; 366*6fcafd02SGreg Roach } 367*6fcafd02SGreg Roach 368*6fcafd02SGreg Roach /** 369*6fcafd02SGreg Roach * @return Relationship 370*6fcafd02SGreg Roach */ 371*6fcafd02SGreg Roach public function husband(): Relationship 372*6fcafd02SGreg Roach { 373*6fcafd02SGreg Roach return $this->married()->relation([self::HUSBAND]); 374*6fcafd02SGreg Roach } 375*6fcafd02SGreg Roach 376*6fcafd02SGreg Roach /** 377*6fcafd02SGreg Roach * @return Relationship 378*6fcafd02SGreg Roach */ 379*6fcafd02SGreg Roach public function married(): Relationship 380*6fcafd02SGreg Roach { 381*6fcafd02SGreg Roach return $this->marriageStatus('MARR'); 382*6fcafd02SGreg Roach } 383*6fcafd02SGreg Roach 384*6fcafd02SGreg Roach /** 385*6fcafd02SGreg Roach * @return Relationship 386*6fcafd02SGreg Roach */ 387*6fcafd02SGreg Roach public function male(): Relationship 388*6fcafd02SGreg Roach { 389*6fcafd02SGreg Roach return $this->sex('M'); 390*6fcafd02SGreg Roach } 391*6fcafd02SGreg Roach 392*6fcafd02SGreg Roach /** 393*6fcafd02SGreg Roach * @return Relationship 394*6fcafd02SGreg Roach */ 395*6fcafd02SGreg Roach public function mother(): Relationship 396*6fcafd02SGreg Roach { 397*6fcafd02SGreg Roach return $this->relation([self::MOTHER]); 398*6fcafd02SGreg Roach } 399*6fcafd02SGreg Roach 400*6fcafd02SGreg Roach /** 401*6fcafd02SGreg Roach * @return Relationship 402*6fcafd02SGreg Roach */ 403*6fcafd02SGreg Roach public function older(): Relationship 404*6fcafd02SGreg Roach { 405*6fcafd02SGreg Roach $this->matchers[] = static function (array $nodes): bool { 406*6fcafd02SGreg Roach if (count($nodes) > 2) { 407*6fcafd02SGreg Roach $date1 = $nodes[0]->facts(['BIRT'], false, Auth::PRIV_HIDE)->map(fn (Fact $fact): Date => $fact->date())->first() ?? new Date(''); 408*6fcafd02SGreg Roach $date2 = $nodes[2]->facts(['BIRT'], false, Auth::PRIV_HIDE)->map(fn (Fact $fact): Date => $fact->date())->first() ?? new Date(''); 409*6fcafd02SGreg Roach 410*6fcafd02SGreg Roach return Date::compare($date1, $date2) < 0; 411*6fcafd02SGreg Roach } 412*6fcafd02SGreg Roach 413*6fcafd02SGreg Roach return false; 414*6fcafd02SGreg Roach }; 415*6fcafd02SGreg Roach 416*6fcafd02SGreg Roach return $this; 417*6fcafd02SGreg Roach } 418*6fcafd02SGreg Roach 419*6fcafd02SGreg Roach /** 420*6fcafd02SGreg Roach * @return Relationship 421*6fcafd02SGreg Roach */ 422*6fcafd02SGreg Roach public function parent(): Relationship 423*6fcafd02SGreg Roach { 424*6fcafd02SGreg Roach return $this->relation(self::PARENTS); 425*6fcafd02SGreg Roach } 426*6fcafd02SGreg Roach 427*6fcafd02SGreg Roach /** 428*6fcafd02SGreg Roach * @return Relationship 429*6fcafd02SGreg Roach */ 430*6fcafd02SGreg Roach public function sister(): Relationship 431*6fcafd02SGreg Roach { 432*6fcafd02SGreg Roach return $this->relation([self::SISTER]); 433*6fcafd02SGreg Roach } 434*6fcafd02SGreg Roach 435*6fcafd02SGreg Roach /** 436*6fcafd02SGreg Roach * @return Relationship 437*6fcafd02SGreg Roach */ 438*6fcafd02SGreg Roach public function son(): Relationship 439*6fcafd02SGreg Roach { 440*6fcafd02SGreg Roach return $this->relation([self::SON]); 441*6fcafd02SGreg Roach } 442*6fcafd02SGreg Roach 443*6fcafd02SGreg Roach /** 444*6fcafd02SGreg Roach * @return Relationship 445*6fcafd02SGreg Roach */ 446*6fcafd02SGreg Roach public function spouse(): Relationship 447*6fcafd02SGreg Roach { 448*6fcafd02SGreg Roach return $this->married()->partner(); 449*6fcafd02SGreg Roach } 450*6fcafd02SGreg Roach 451*6fcafd02SGreg Roach /** 452*6fcafd02SGreg Roach * @return Relationship 453*6fcafd02SGreg Roach */ 454*6fcafd02SGreg Roach public function partner(): Relationship 455*6fcafd02SGreg Roach { 456*6fcafd02SGreg Roach return $this->relation(self::SPOUSES); 457*6fcafd02SGreg Roach } 458*6fcafd02SGreg Roach 459*6fcafd02SGreg Roach /** 460*6fcafd02SGreg Roach * The number of ancestors must be the same as the number of descendants 461*6fcafd02SGreg Roach * 462*6fcafd02SGreg Roach * @return Relationship 463*6fcafd02SGreg Roach */ 464*6fcafd02SGreg Roach public function symmetricCousin(): Relationship 465*6fcafd02SGreg Roach { 466*6fcafd02SGreg Roach $this->matchers[] = static function (array &$nodes, array &$patterns, array &$captures): bool { 467*6fcafd02SGreg Roach $count = count($patterns); 468*6fcafd02SGreg Roach 469*6fcafd02SGreg Roach $n = 0; 470*6fcafd02SGreg Roach 471*6fcafd02SGreg Roach // Ancestors 472*6fcafd02SGreg Roach while ($n < $count && in_array($patterns[$n], Relationship::PARENTS, true)) { 473*6fcafd02SGreg Roach $n++; 474*6fcafd02SGreg Roach } 475*6fcafd02SGreg Roach 476*6fcafd02SGreg Roach // No ancestors? Not enough path left for descendants? 477*6fcafd02SGreg Roach if ($n === 0 || $n * 2 + 1 !== $count) { 478*6fcafd02SGreg Roach return false; 479*6fcafd02SGreg Roach } 480*6fcafd02SGreg Roach 481*6fcafd02SGreg Roach // Siblings 482*6fcafd02SGreg Roach if (!in_array($patterns[$n], Relationship::SIBLINGS, true)) { 483*6fcafd02SGreg Roach return false; 484*6fcafd02SGreg Roach } 485*6fcafd02SGreg Roach 486*6fcafd02SGreg Roach // Descendants 487*6fcafd02SGreg Roach for ($descendants = $n + 1; $descendants < $count; ++$descendants) { 488*6fcafd02SGreg Roach if (!in_array($patterns[$descendants], Relationship::CHILDREN, true)) { 489*6fcafd02SGreg Roach return false; 490*6fcafd02SGreg Roach } 491*6fcafd02SGreg Roach } 492*6fcafd02SGreg Roach 493*6fcafd02SGreg Roach 494*6fcafd02SGreg Roach $nodes = array_slice($nodes, 2 * $n); 495*6fcafd02SGreg Roach $patterns = []; 496*6fcafd02SGreg Roach $captures[] = $n; 497*6fcafd02SGreg Roach 498*6fcafd02SGreg Roach return true; 499*6fcafd02SGreg Roach }; 500*6fcafd02SGreg Roach 501*6fcafd02SGreg Roach return $this; 502*6fcafd02SGreg Roach } 503*6fcafd02SGreg Roach 504*6fcafd02SGreg Roach /** 505*6fcafd02SGreg Roach * @return Relationship 506*6fcafd02SGreg Roach */ 507*6fcafd02SGreg Roach public function twin(): Relationship 508*6fcafd02SGreg Roach { 509*6fcafd02SGreg Roach $this->matchers[] = static function (array $nodes): bool { 510*6fcafd02SGreg Roach if (count($nodes) > 2) { 511*6fcafd02SGreg Roach $date1 = $nodes[0]->facts(['BIRT'], false, Auth::PRIV_HIDE)->map(fn (Fact $fact): Date => $fact->date())->first() ?? new Date(''); 512*6fcafd02SGreg Roach $date2 = $nodes[2]->facts(['BIRT'], false, Auth::PRIV_HIDE)->map(fn (Fact $fact): Date => $fact->date())->first() ?? new Date(''); 513*6fcafd02SGreg Roach 514*6fcafd02SGreg Roach return 515*6fcafd02SGreg Roach $date1->isOK() && 516*6fcafd02SGreg Roach $date2->isOK() && 517*6fcafd02SGreg Roach abs($date1->julianDay() - $date2->julianDay()) < 2 && 518*6fcafd02SGreg Roach $date1->minimumDate()->day > 0 && 519*6fcafd02SGreg Roach $date2->minimumDate()->day > 0; 520*6fcafd02SGreg Roach } 521*6fcafd02SGreg Roach 522*6fcafd02SGreg Roach return false; 523*6fcafd02SGreg Roach }; 524*6fcafd02SGreg Roach 525*6fcafd02SGreg Roach return $this; 526*6fcafd02SGreg Roach } 527*6fcafd02SGreg Roach 528*6fcafd02SGreg Roach /** 529*6fcafd02SGreg Roach * @return Relationship 530*6fcafd02SGreg Roach */ 531*6fcafd02SGreg Roach public function wife(): Relationship 532*6fcafd02SGreg Roach { 533*6fcafd02SGreg Roach return $this->married()->relation([self::WIFE]); 534*6fcafd02SGreg Roach } 535*6fcafd02SGreg Roach 536*6fcafd02SGreg Roach /** 537*6fcafd02SGreg Roach * @return Relationship 538*6fcafd02SGreg Roach */ 539*6fcafd02SGreg Roach public function younger(): Relationship 540*6fcafd02SGreg Roach { 541*6fcafd02SGreg Roach $this->matchers[] = static function (array $nodes): bool { 542*6fcafd02SGreg Roach if (count($nodes) > 2) { 543*6fcafd02SGreg Roach $date1 = $nodes[0]->facts(['BIRT'], false, Auth::PRIV_HIDE)->map(fn (Fact $fact): Date => $fact->date())->first() ?? new Date(''); 544*6fcafd02SGreg Roach $date2 = $nodes[2]->facts(['BIRT'], false, Auth::PRIV_HIDE)->map(fn (Fact $fact): Date => $fact->date())->first() ?? new Date(''); 545*6fcafd02SGreg Roach 546*6fcafd02SGreg Roach return Date::compare($date1, $date2) > 0; 547*6fcafd02SGreg Roach } 548*6fcafd02SGreg Roach 549*6fcafd02SGreg Roach return false; 550*6fcafd02SGreg Roach }; 551*6fcafd02SGreg Roach 552*6fcafd02SGreg Roach return $this; 553*6fcafd02SGreg Roach } 554*6fcafd02SGreg Roach} 555