1<?php 2 3/** 4 * webtrees: online genealogy 5 * Copyright (C) 2021 webtrees development team 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation, either version 3 of the License, or 9 * (at your option) any later version. 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <https://www.gnu.org/licenses/>. 16 */ 17 18declare(strict_types=1); 19 20namespace Fisharebest\Webtrees\Module; 21 22use Fisharebest\Webtrees\Auth; 23use Fisharebest\Webtrees\Date; 24use Fisharebest\Webtrees\Fact; 25use Fisharebest\Webtrees\Family; 26use Fisharebest\Webtrees\I18N; 27use Fisharebest\Webtrees\Individual; 28use Fisharebest\Webtrees\Services\ClipboardService; 29use Fisharebest\Webtrees\Services\ModuleService; 30use Illuminate\Support\Collection; 31 32use function str_contains; 33 34/** 35 * Class IndividualFactsTabModule 36 */ 37class IndividualFactsTabModule extends AbstractModule implements ModuleTabInterface 38{ 39 use ModuleTabTrait; 40 41 /** @var ModuleService */ 42 private $module_service; 43 44 /** @var ClipboardService */ 45 private $clipboard_service; 46 47 /** 48 * IndividualFactsTabModule constructor. 49 * 50 * @param ModuleService $module_service 51 * @param ClipboardService $clipboard_service 52 */ 53 public function __construct(ModuleService $module_service, ClipboardService $clipboard_service) 54 { 55 $this->module_service = $module_service; 56 $this->clipboard_service = $clipboard_service; 57 } 58 59 /** 60 * How should this module be identified in the control panel, etc.? 61 * 62 * @return string 63 */ 64 public function title(): string 65 { 66 /* I18N: Name of a module/tab on the individual page. */ 67 return I18N::translate('Facts and events'); 68 } 69 70 /** 71 * A sentence describing what this module does. 72 * 73 * @return string 74 */ 75 public function description(): string 76 { 77 /* I18N: Description of the “Facts and events” module */ 78 return I18N::translate('A tab showing the facts and events of an individual.'); 79 } 80 81 /** 82 * The default position for this tab. It can be changed in the control panel. 83 * 84 * @return int 85 */ 86 public function defaultTabOrder(): int 87 { 88 return 1; 89 } 90 91 /** 92 * A greyed out tab has no actual content, but may perhaps have 93 * options to create content. 94 * 95 * @param Individual $individual 96 * 97 * @return bool 98 */ 99 public function isGrayedOut(Individual $individual): bool 100 { 101 return false; 102 } 103 104 /** 105 * Generate the HTML content of this tab. 106 * 107 * @param Individual $individual 108 * 109 * @return string 110 */ 111 public function getTabContent(Individual $individual): string 112 { 113 // Only include events of close relatives that are between birth and death 114 $min_date = $individual->getEstimatedBirthDate(); 115 $max_date = $individual->getEstimatedDeathDate(); 116 117 // Which facts and events are handled by other modules? 118 $sidebar_facts = $this->module_service 119 ->findByComponent(ModuleSidebarInterface::class, $individual->tree(), Auth::user()) 120 ->map(static function (ModuleSidebarInterface $sidebar): Collection { 121 return $sidebar->supportedFacts(); 122 }); 123 124 $tab_facts = $this->module_service 125 ->findByComponent(ModuleTabInterface::class, $individual->tree(), Auth::user()) 126 ->map(static function (ModuleTabInterface $sidebar): Collection { 127 return $sidebar->supportedFacts(); 128 }); 129 130 $exclude_facts = $sidebar_facts->merge($tab_facts)->flatten(); 131 132 // The individual’s own facts 133 $indifacts = $individual->facts() 134 ->filter(static function (Fact $fact) use ($exclude_facts): bool { 135 return !$exclude_facts->contains($fact->getTag()); 136 }); 137 138 // Add spouse-family facts 139 foreach ($individual->spouseFamilies() as $family) { 140 foreach ($family->facts() as $fact) { 141 if (!$exclude_facts->contains($fact->getTag()) && $fact->getTag() !== 'CHAN') { 142 $indifacts->push($fact); 143 } 144 } 145 146 $spouse = $family->spouse($individual); 147 148 if ($spouse instanceof Individual) { 149 $spouse_facts = $this->spouseFacts($individual, $spouse, $min_date, $max_date); 150 $indifacts = $indifacts->merge($spouse_facts); 151 } 152 153 $child_facts = $this->childFacts($individual, $family, '_CHIL', '', $min_date, $max_date); 154 $indifacts = $indifacts->merge($child_facts); 155 } 156 157 $parent_facts = $this->parentFacts($individual, 1, $min_date, $max_date); 158 $associate_facts = $this->associateFacts($individual); 159 $historical_facts = $this->historicalFacts($individual); 160 161 $indifacts = $indifacts 162 ->merge($parent_facts) 163 ->merge($associate_facts) 164 ->merge($historical_facts); 165 166 $indifacts = Fact::sortFacts($indifacts); 167 168 return view('modules/personal_facts/tab', [ 169 'can_edit' => $individual->canEdit(), 170 'clipboard_facts' => $this->clipboard_service->pastableFacts($individual, new Collection()), 171 'has_historical_facts' => $historical_facts !== [], 172 'individual' => $individual, 173 'facts' => $indifacts, 174 ]); 175 } 176 177 /** 178 * Does a relative event occur within a date range (i.e. the individual's lifetime)? 179 * 180 * @param Fact $fact 181 * @param Date $min_date 182 * @param Date $max_date 183 * 184 * @return bool 185 */ 186 private function includeFact(Fact $fact, Date $min_date, Date $max_date): bool 187 { 188 $fact_date = $fact->date(); 189 190 return $fact_date->isOK() && Date::compare($min_date, $fact_date) <= 0 && Date::compare($fact_date, $max_date) <= 0; 191 } 192 193 /** 194 * Is this tab empty? If so, we don't always need to display it. 195 * 196 * @param Individual $individual 197 * 198 * @return bool 199 */ 200 public function hasTabContent(Individual $individual): bool 201 { 202 return true; 203 } 204 205 /** 206 * Can this tab load asynchronously? 207 * 208 * @return bool 209 */ 210 public function canLoadAjax(): bool 211 { 212 return false; 213 } 214 215 /** 216 * Convert an event into a special "event of a close relative". 217 * 218 * @param Fact $fact 219 * @param string $type 220 * 221 * @return Fact 222 */ 223 private function convertEvent(Fact $fact, string $type): Fact 224 { 225 $gedcom = $fact->gedcom(); 226 $gedcom = preg_replace('/\n2 TYPE .*/', '', $gedcom); 227 $gedcom = preg_replace('/^1 .*/', "1 EVEN CLOSE_RELATIVE\n2 TYPE " . $type, $gedcom); 228 229 $converted = new Fact($gedcom, $fact->record(), $fact->id()); 230 231 if ($fact->isPendingAddition()) { 232 $converted->setPendingAddition(); 233 } 234 235 if ($fact->isPendingDeletion()) { 236 $converted->setPendingDeletion(); 237 } 238 239 return $converted; 240 } 241 242 /** 243 * Spouse facts that are shown on an individual’s page. 244 * 245 * @param Individual $individual Show events that occured during the lifetime of this individual 246 * @param Individual $spouse Show events of this individual 247 * @param Date $min_date 248 * @param Date $max_date 249 * 250 * @return Fact[] 251 */ 252 private function spouseFacts(Individual $individual, Individual $spouse, Date $min_date, Date $max_date): array 253 { 254 $SHOW_RELATIVES_EVENTS = $individual->tree()->getPreference('SHOW_RELATIVES_EVENTS'); 255 256 $death_of_a_spouse = [ 257 'DEAT' => [ 258 'M' => I18N::translate('Death of a husband'), 259 'F' => I18N::translate('Death of a wife'), 260 'U' => I18N::translate('Death of a spouse'), 261 ], 262 'BURI' => [ 263 'M' => I18N::translate('Burial of a husband'), 264 'F' => I18N::translate('Burial of a wife'), 265 'U' => I18N::translate('Burial of a spouse'), 266 ], 267 'CREM' => [ 268 'M' => I18N::translate('Cremation of a husband'), 269 'F' => I18N::translate('Cremation of a wife'), 270 'U' => I18N::translate('Cremation of a spouse'), 271 ], 272 ]; 273 274 $facts = []; 275 276 if (str_contains($SHOW_RELATIVES_EVENTS, '_DEAT_SPOU')) { 277 foreach ($spouse->facts(['DEAT', 'BURI', 'CREM']) as $fact) { 278 if ($this->includeFact($fact, $min_date, $max_date)) { 279 $facts[] = $this->convertEvent($fact, $death_of_a_spouse[$fact->getTag()][$fact->record()->sex()]); 280 } 281 } 282 } 283 284 return $facts; 285 } 286 287 /** 288 * Get the events of children and grandchildren. 289 * 290 * @param Individual $person 291 * @param Family $family 292 * @param string $option 293 * @param string $relation 294 * @param Date $min_date 295 * @param Date $max_date 296 * 297 * @return Fact[] 298 */ 299 private function childFacts(Individual $person, Family $family, string $option, string $relation, Date $min_date, Date $max_date): array 300 { 301 $SHOW_RELATIVES_EVENTS = $person->tree()->getPreference('SHOW_RELATIVES_EVENTS'); 302 303 $birth_of_a_child = [ 304 'BIRT' => [ 305 'M' => I18N::translate('Birth of a son'), 306 'F' => I18N::translate('Birth of a daughter'), 307 'U' => I18N::translate('Birth of a child'), 308 ], 309 'CHR' => [ 310 'M' => I18N::translate('Christening of a son'), 311 'F' => I18N::translate('Christening of a daughter'), 312 'U' => I18N::translate('Christening of a child'), 313 ], 314 'BAPM' => [ 315 'M' => I18N::translate('Baptism of a son'), 316 'F' => I18N::translate('Baptism of a daughter'), 317 'U' => I18N::translate('Baptism of a child'), 318 ], 319 'ADOP' => [ 320 'M' => I18N::translate('Adoption of a son'), 321 'F' => I18N::translate('Adoption of a daughter'), 322 'U' => I18N::translate('Adoption of a child'), 323 ], 324 ]; 325 326 $birth_of_a_sibling = [ 327 'BIRT' => [ 328 'M' => I18N::translate('Birth of a brother'), 329 'F' => I18N::translate('Birth of a sister'), 330 'U' => I18N::translate('Birth of a sibling'), 331 ], 332 'CHR' => [ 333 'M' => I18N::translate('Christening of a brother'), 334 'F' => I18N::translate('Christening of a sister'), 335 'U' => I18N::translate('Christening of a sibling'), 336 ], 337 'BAPM' => [ 338 'M' => I18N::translate('Baptism of a brother'), 339 'F' => I18N::translate('Baptism of a sister'), 340 'U' => I18N::translate('Baptism of a sibling'), 341 ], 342 'ADOP' => [ 343 'M' => I18N::translate('Adoption of a brother'), 344 'F' => I18N::translate('Adoption of a sister'), 345 'U' => I18N::translate('Adoption of a sibling'), 346 ], 347 ]; 348 349 $birth_of_a_half_sibling = [ 350 'BIRT' => [ 351 'M' => I18N::translate('Birth of a half-brother'), 352 'F' => I18N::translate('Birth of a half-sister'), 353 'U' => I18N::translate('Birth of a half-sibling'), 354 ], 355 'CHR' => [ 356 'M' => I18N::translate('Christening of a half-brother'), 357 'F' => I18N::translate('Christening of a half-sister'), 358 'U' => I18N::translate('Christening of a half-sibling'), 359 ], 360 'BAPM' => [ 361 'M' => I18N::translate('Baptism of a half-brother'), 362 'F' => I18N::translate('Baptism of a half-sister'), 363 'U' => I18N::translate('Baptism of a half-sibling'), 364 ], 365 'ADOP' => [ 366 'M' => I18N::translate('Adoption of a half-brother'), 367 'F' => I18N::translate('Adoption of a half-sister'), 368 'U' => I18N::translate('Adoption of a half-sibling'), 369 ], 370 ]; 371 372 $birth_of_a_grandchild = [ 373 'BIRT' => [ 374 'M' => I18N::translate('Birth of a grandson'), 375 'F' => I18N::translate('Birth of a granddaughter'), 376 'U' => I18N::translate('Birth of a grandchild'), 377 ], 378 'CHR' => [ 379 'M' => I18N::translate('Christening of a grandson'), 380 'F' => I18N::translate('Christening of a granddaughter'), 381 'U' => I18N::translate('Christening of a grandchild'), 382 ], 383 'BAPM' => [ 384 'M' => I18N::translate('Baptism of a grandson'), 385 'F' => I18N::translate('Baptism of a granddaughter'), 386 'U' => I18N::translate('Baptism of a grandchild'), 387 ], 388 'ADOP' => [ 389 'M' => I18N::translate('Adoption of a grandson'), 390 'F' => I18N::translate('Adoption of a granddaughter'), 391 'U' => I18N::translate('Adoption of a grandchild'), 392 ], 393 ]; 394 395 $birth_of_a_grandchild1 = [ 396 'BIRT' => [ 397 'M' => I18N::translateContext('daughter’s son', 'Birth of a grandson'), 398 'F' => I18N::translateContext('daughter’s daughter', 'Birth of a granddaughter'), 399 'U' => I18N::translate('Birth of a grandchild'), 400 ], 401 'CHR' => [ 402 'M' => I18N::translateContext('daughter’s son', 'Christening of a grandson'), 403 'F' => I18N::translateContext('daughter’s daughter', 'Christening of a granddaughter'), 404 'U' => I18N::translate('Christening of a grandchild'), 405 ], 406 'BAPM' => [ 407 'M' => I18N::translateContext('daughter’s son', 'Baptism of a grandson'), 408 'F' => I18N::translateContext('daughter’s daughter', 'Baptism of a granddaughter'), 409 'U' => I18N::translate('Baptism of a grandchild'), 410 ], 411 'ADOP' => [ 412 'M' => I18N::translateContext('daughter’s son', 'Adoption of a grandson'), 413 'F' => I18N::translateContext('daughter’s daughter', 'Adoption of a granddaughter'), 414 'U' => I18N::translate('Adoption of a grandchild'), 415 ], 416 ]; 417 418 $birth_of_a_grandchild2 = [ 419 'BIRT' => [ 420 'M' => I18N::translateContext('son’s son', 'Birth of a grandson'), 421 'F' => I18N::translateContext('son’s daughter', 'Birth of a granddaughter'), 422 'U' => I18N::translate('Birth of a grandchild'), 423 ], 424 'CHR' => [ 425 'M' => I18N::translateContext('son’s son', 'Christening of a grandson'), 426 'F' => I18N::translateContext('son’s daughter', 'Christening of a granddaughter'), 427 'U' => I18N::translate('Christening of a grandchild'), 428 ], 429 'BAPM' => [ 430 'M' => I18N::translateContext('son’s son', 'Baptism of a grandson'), 431 'F' => I18N::translateContext('son’s daughter', 'Baptism of a granddaughter'), 432 'U' => I18N::translate('Baptism of a grandchild'), 433 ], 434 'ADOP' => [ 435 'M' => I18N::translateContext('son’s son', 'Adoption of a grandson'), 436 'F' => I18N::translateContext('son’s daughter', 'Adoption of a granddaughter'), 437 'U' => I18N::translate('Adoption of a grandchild'), 438 ], 439 ]; 440 441 $death_of_a_child = [ 442 'DEAT' => [ 443 'M' => I18N::translate('Death of a son'), 444 'F' => I18N::translate('Death of a daughter'), 445 'U' => I18N::translate('Death of a child'), 446 ], 447 'BURI' => [ 448 'M' => I18N::translate('Burial of a son'), 449 'F' => I18N::translate('Burial of a daughter'), 450 'U' => I18N::translate('Burial of a child'), 451 ], 452 'CREM' => [ 453 'M' => I18N::translate('Cremation of a son'), 454 'F' => I18N::translate('Cremation of a daughter'), 455 'U' => I18N::translate('Cremation of a child'), 456 ], 457 ]; 458 459 $death_of_a_sibling = [ 460 'DEAT' => [ 461 'M' => I18N::translate('Death of a brother'), 462 'F' => I18N::translate('Death of a sister'), 463 'U' => I18N::translate('Death of a sibling'), 464 ], 465 'BURI' => [ 466 'M' => I18N::translate('Burial of a brother'), 467 'F' => I18N::translate('Burial of a sister'), 468 'U' => I18N::translate('Burial of a sibling'), 469 ], 470 'CREM' => [ 471 'M' => I18N::translate('Cremation of a brother'), 472 'F' => I18N::translate('Cremation of a sister'), 473 'U' => I18N::translate('Cremation of a sibling'), 474 ], 475 ]; 476 477 $death_of_a_half_sibling = [ 478 'DEAT' => [ 479 'M' => I18N::translate('Death of a half-brother'), 480 'F' => I18N::translate('Death of a half-sister'), 481 'U' => I18N::translate('Death of a half-sibling'), 482 ], 483 'BURI' => [ 484 'M' => I18N::translate('Burial of a half-brother'), 485 'F' => I18N::translate('Burial of a half-sister'), 486 'U' => I18N::translate('Burial of a half-sibling'), 487 ], 488 'CREM' => [ 489 'M' => I18N::translate('Cremation of a half-brother'), 490 'F' => I18N::translate('Cremation of a half-sister'), 491 'U' => I18N::translate('Cremation of a half-sibling'), 492 ], 493 ]; 494 495 $death_of_a_grandchild = [ 496 'DEAT' => [ 497 'M' => I18N::translate('Death of a grandson'), 498 'F' => I18N::translate('Death of a granddaughter'), 499 'U' => I18N::translate('Death of a grandchild'), 500 ], 501 'BURI' => [ 502 'M' => I18N::translate('Burial of a grandson'), 503 'F' => I18N::translate('Burial of a granddaughter'), 504 'U' => I18N::translate('Burial of a grandchild'), 505 ], 506 'CREM' => [ 507 'M' => I18N::translate('Cremation of a grandson'), 508 'F' => I18N::translate('Cremation of a granddaughter'), 509 'U' => I18N::translate('Baptism of a grandchild'), 510 ], 511 ]; 512 513 $death_of_a_grandchild1 = [ 514 'DEAT' => [ 515 'M' => I18N::translateContext('daughter’s son', 'Death of a grandson'), 516 'F' => I18N::translateContext('daughter’s daughter', 'Death of a granddaughter'), 517 'U' => I18N::translate('Death of a grandchild'), 518 ], 519 'BURI' => [ 520 'M' => I18N::translateContext('daughter’s son', 'Burial of a grandson'), 521 'F' => I18N::translateContext('daughter’s daughter', 'Burial of a granddaughter'), 522 'U' => I18N::translate('Burial of a grandchild'), 523 ], 524 'CREM' => [ 525 'M' => I18N::translateContext('daughter’s son', 'Cremation of a grandson'), 526 'F' => I18N::translateContext('daughter’s daughter', 'Cremation of a granddaughter'), 527 'U' => I18N::translate('Baptism of a grandchild'), 528 ], 529 ]; 530 531 $death_of_a_grandchild2 = [ 532 'DEAT' => [ 533 'M' => I18N::translateContext('son’s son', 'Death of a grandson'), 534 'F' => I18N::translateContext('son’s daughter', 'Death of a granddaughter'), 535 'U' => I18N::translate('Death of a grandchild'), 536 ], 537 'BURI' => [ 538 'M' => I18N::translateContext('son’s son', 'Burial of a grandson'), 539 'F' => I18N::translateContext('son’s daughter', 'Burial of a granddaughter'), 540 'U' => I18N::translate('Burial of a grandchild'), 541 ], 542 'CREM' => [ 543 'M' => I18N::translateContext('son’s son', 'Cremation of a grandson'), 544 'F' => I18N::translateContext('son’s daughter', 'Cremation of a granddaughter'), 545 'U' => I18N::translate('Cremation of a grandchild'), 546 ], 547 ]; 548 549 $marriage_of_a_child = [ 550 'M' => I18N::translate('Marriage of a son'), 551 'F' => I18N::translate('Marriage of a daughter'), 552 'U' => I18N::translate('Marriage of a child'), 553 ]; 554 555 $marriage_of_a_grandchild = [ 556 'M' => I18N::translate('Marriage of a grandson'), 557 'F' => I18N::translate('Marriage of a granddaughter'), 558 'U' => I18N::translate('Marriage of a grandchild'), 559 ]; 560 561 $marriage_of_a_grandchild1 = [ 562 'M' => I18N::translateContext('daughter’s son', 'Marriage of a grandson'), 563 'F' => I18N::translateContext('daughter’s daughter', 'Marriage of a granddaughter'), 564 'U' => I18N::translate('Marriage of a grandchild'), 565 ]; 566 567 $marriage_of_a_grandchild2 = [ 568 'M' => I18N::translateContext('son’s son', 'Marriage of a grandson'), 569 'F' => I18N::translateContext('son’s daughter', 'Marriage of a granddaughter'), 570 'U' => I18N::translate('Marriage of a grandchild'), 571 ]; 572 573 $marriage_of_a_sibling = [ 574 'M' => I18N::translate('Marriage of a brother'), 575 'F' => I18N::translate('Marriage of a sister'), 576 'U' => I18N::translate('Marriage of a sibling'), 577 ]; 578 579 $marriage_of_a_half_sibling = [ 580 'M' => I18N::translate('Marriage of a half-brother'), 581 'F' => I18N::translate('Marriage of a half-sister'), 582 'U' => I18N::translate('Marriage of a half-sibling'), 583 ]; 584 585 $facts = []; 586 587 // Deal with recursion. 588 switch ($option) { 589 case '_CHIL': 590 // Add grandchildren 591 foreach ($family->children() as $child) { 592 foreach ($child->spouseFamilies() as $cfamily) { 593 switch ($child->sex()) { 594 case 'M': 595 foreach ($this->childFacts($person, $cfamily, '_GCHI', 'son', $min_date, $max_date) as $fact) { 596 $facts[] = $fact; 597 } 598 break; 599 case 'F': 600 foreach ($this->childFacts($person, $cfamily, '_GCHI', 'dau', $min_date, $max_date) as $fact) { 601 $facts[] = $fact; 602 } 603 break; 604 default: 605 foreach ($this->childFacts($person, $cfamily, '_GCHI', 'chi', $min_date, $max_date) as $fact) { 606 $facts[] = $fact; 607 } 608 break; 609 } 610 } 611 } 612 break; 613 } 614 615 // For each child in the family 616 foreach ($family->children() as $child) { 617 if ($child->xref() === $person->xref()) { 618 // We are not our own sibling! 619 continue; 620 } 621 // add child’s birth 622 if (str_contains($SHOW_RELATIVES_EVENTS, '_BIRT' . str_replace('_HSIB', '_SIBL', $option))) { 623 foreach ($child->facts(['BIRT', 'CHR', 'BAPM', 'ADOP']) as $fact) { 624 // Always show _BIRT_CHIL, even if the dates are not known 625 if ($option === '_CHIL' || $this->includeFact($fact, $min_date, $max_date)) { 626 switch ($option) { 627 case '_GCHI': 628 switch ($relation) { 629 case 'dau': 630 $facts[] = $this->convertEvent($fact, $birth_of_a_grandchild1[$fact->getTag()][$fact->record()->sex()]); 631 break; 632 case 'son': 633 $facts[] = $this->convertEvent($fact, $birth_of_a_grandchild2[$fact->getTag()][$fact->record()->sex()]); 634 break; 635 case 'chil': 636 $facts[] = $this->convertEvent($fact, $birth_of_a_grandchild[$fact->getTag()][$fact->record()->sex()]); 637 break; 638 } 639 break; 640 case '_SIBL': 641 $facts[] = $this->convertEvent($fact, $birth_of_a_sibling[$fact->getTag()][$fact->record()->sex()]); 642 break; 643 case '_HSIB': 644 $facts[] = $this->convertEvent($fact, $birth_of_a_half_sibling[$fact->getTag()][$fact->record()->sex()]); 645 break; 646 case '_CHIL': 647 $facts[] = $this->convertEvent($fact, $birth_of_a_child[$fact->getTag()][$fact->record()->sex()]); 648 break; 649 } 650 } 651 } 652 } 653 // add child’s death 654 if (str_contains($SHOW_RELATIVES_EVENTS, '_DEAT' . str_replace('_HSIB', '_SIBL', $option))) { 655 foreach ($child->facts(['DEAT', 'BURI', 'CREM']) as $fact) { 656 if ($this->includeFact($fact, $min_date, $max_date)) { 657 switch ($option) { 658 case '_GCHI': 659 switch ($relation) { 660 case 'dau': 661 $facts[] = $this->convertEvent($fact, $death_of_a_grandchild1[$fact->getTag()][$fact->record()->sex()]); 662 break; 663 case 'son': 664 $facts[] = $this->convertEvent($fact, $death_of_a_grandchild2[$fact->getTag()][$fact->record()->sex()]); 665 break; 666 case 'chi': 667 $facts[] = $this->convertEvent($fact, $death_of_a_grandchild[$fact->getTag()][$fact->record()->sex()]); 668 break; 669 } 670 break; 671 case '_SIBL': 672 $facts[] = $this->convertEvent($fact, $death_of_a_sibling[$fact->getTag()][$fact->record()->sex()]); 673 break; 674 case '_HSIB': 675 $facts[] = $this->convertEvent($fact, $death_of_a_half_sibling[$fact->getTag()][$fact->record()->sex()]); 676 break; 677 case '_CHIL': 678 $facts[] = $this->convertEvent($fact, $death_of_a_child[$fact->getTag()][$fact->record()->sex()]); 679 break; 680 } 681 } 682 } 683 } 684 685 // add child’s marriage 686 if (str_contains($SHOW_RELATIVES_EVENTS, '_MARR' . str_replace('_HSIB', '_SIBL', $option))) { 687 foreach ($child->spouseFamilies() as $sfamily) { 688 foreach ($sfamily->facts(['MARR']) as $fact) { 689 if ($this->includeFact($fact, $min_date, $max_date)) { 690 switch ($option) { 691 case '_GCHI': 692 switch ($relation) { 693 case 'dau': 694 $facts[] = $this->convertEvent($fact, $marriage_of_a_grandchild1['F']); 695 break; 696 case 'son': 697 $facts[] = $this->convertEvent($fact, $marriage_of_a_grandchild2['M']); 698 break; 699 case 'chi': 700 $facts[] = $this->convertEvent($fact, $marriage_of_a_grandchild['U']); 701 break; 702 } 703 break; 704 case '_SIBL': 705 $facts[] = $this->convertEvent($fact, $marriage_of_a_sibling['U']); 706 break; 707 case '_HSIB': 708 $facts[] = $this->convertEvent($fact, $marriage_of_a_half_sibling['U']); 709 break; 710 case '_CHIL': 711 $facts[] = $this->convertEvent($fact, $marriage_of_a_child['U']); 712 break; 713 } 714 } 715 } 716 } 717 } 718 } 719 720 return $facts; 721 } 722 723 /** 724 * Get the events of parents and grandparents. 725 * 726 * @param Individual $person 727 * @param int $sosa 728 * @param Date $min_date 729 * @param Date $max_date 730 * 731 * @return Fact[] 732 */ 733 private function parentFacts(Individual $person, int $sosa, Date $min_date, Date $max_date): array 734 { 735 $SHOW_RELATIVES_EVENTS = $person->tree()->getPreference('SHOW_RELATIVES_EVENTS'); 736 737 $death_of_a_parent = [ 738 'DEAT' => [ 739 'M' => I18N::translate('Death of a father'), 740 'F' => I18N::translate('Death of a mother'), 741 'U' => I18N::translate('Death of a parent'), 742 ], 743 'BURI' => [ 744 'M' => I18N::translate('Burial of a father'), 745 'F' => I18N::translate('Burial of a mother'), 746 'U' => I18N::translate('Burial of a parent'), 747 ], 748 'CREM' => [ 749 'M' => I18N::translate('Cremation of a father'), 750 'F' => I18N::translate('Cremation of a mother'), 751 'U' => I18N::translate('Cremation of a parent'), 752 ], 753 ]; 754 755 $death_of_a_grandparent = [ 756 'DEAT' => [ 757 'M' => I18N::translate('Death of a grandfather'), 758 'F' => I18N::translate('Death of a grandmother'), 759 'U' => I18N::translate('Death of a grandparent'), 760 ], 761 'BURI' => [ 762 'M' => I18N::translate('Burial of a grandfather'), 763 'F' => I18N::translate('Burial of a grandmother'), 764 'U' => I18N::translate('Burial of a grandparent'), 765 ], 766 'CREM' => [ 767 'M' => I18N::translate('Cremation of a grandfather'), 768 'F' => I18N::translate('Cremation of a grandmother'), 769 'U' => I18N::translate('Cremation of a grandparent'), 770 ], 771 ]; 772 773 $death_of_a_maternal_grandparent = [ 774 'DEAT' => [ 775 'M' => I18N::translate('Death of a maternal grandfather'), 776 'F' => I18N::translate('Death of a maternal grandmother'), 777 'U' => I18N::translate('Death of a grandparent'), 778 ], 779 'BURI' => [ 780 'M' => I18N::translate('Burial of a maternal grandfather'), 781 'F' => I18N::translate('Burial of a maternal grandmother'), 782 'U' => I18N::translate('Burial of a grandparent'), 783 ], 784 'CREM' => [ 785 'M' => I18N::translate('Cremation of a maternal grandfather'), 786 'F' => I18N::translate('Cremation of a maternal grandmother'), 787 'U' => I18N::translate('Cremation of a grandparent'), 788 ], 789 ]; 790 791 $death_of_a_paternal_grandparent = [ 792 'DEAT' => [ 793 'M' => I18N::translate('Death of a paternal grandfather'), 794 'F' => I18N::translate('Death of a paternal grandmother'), 795 'U' => I18N::translate('Death of a grandparent'), 796 ], 797 'BURI' => [ 798 'M' => I18N::translate('Burial of a paternal grandfather'), 799 'F' => I18N::translate('Burial of a paternal grandmother'), 800 'U' => I18N::translate('Burial of a grandparent'), 801 ], 802 'CREM' => [ 803 'M' => I18N::translate('Cremation of a paternal grandfather'), 804 'F' => I18N::translate('Cremation of a paternal grandmother'), 805 'U' => I18N::translate('Cremation of a grandparent'), 806 ], 807 ]; 808 809 $marriage_of_a_parent = [ 810 'M' => I18N::translate('Marriage of a father'), 811 'F' => I18N::translate('Marriage of a mother'), 812 'U' => I18N::translate('Marriage of a parent'), 813 ]; 814 815 $facts = []; 816 817 if ($sosa === 1) { 818 foreach ($person->childFamilies() as $family) { 819 // Add siblings 820 foreach ($this->childFacts($person, $family, '_SIBL', '', $min_date, $max_date) as $fact) { 821 $facts[] = $fact; 822 } 823 foreach ($family->spouses() as $spouse) { 824 foreach ($spouse->spouseFamilies() as $sfamily) { 825 if ($family !== $sfamily) { 826 // Add half-siblings 827 foreach ($this->childFacts($person, $sfamily, '_HSIB', '', $min_date, $max_date) as $fact) { 828 $facts[] = $fact; 829 } 830 } 831 } 832 // Add grandparents 833 foreach ($this->parentFacts($spouse, $spouse->sex() === 'F' ? 3 : 2, $min_date, $max_date) as $fact) { 834 $facts[] = $fact; 835 } 836 } 837 } 838 839 if (str_contains($SHOW_RELATIVES_EVENTS, '_MARR_PARE')) { 840 // add father/mother marriages 841 foreach ($person->childFamilies() as $sfamily) { 842 foreach ($sfamily->facts(['MARR']) as $fact) { 843 if ($this->includeFact($fact, $min_date, $max_date)) { 844 // marriage of parents (to each other) 845 $facts[] = $this->convertEvent($fact, I18N::translate('Marriage of parents')); 846 } 847 } 848 } 849 foreach ($person->childStepFamilies() as $sfamily) { 850 foreach ($sfamily->facts(['MARR']) as $fact) { 851 if ($this->includeFact($fact, $min_date, $max_date)) { 852 // marriage of a parent (to another spouse) 853 $facts[] = $this->convertEvent($fact, $marriage_of_a_parent['U']); 854 } 855 } 856 } 857 } 858 } 859 860 foreach ($person->childFamilies() as $family) { 861 foreach ($family->spouses() as $parent) { 862 if (str_contains($SHOW_RELATIVES_EVENTS, '_DEAT' . ($sosa === 1 ? '_PARE' : '_GPAR'))) { 863 foreach ($parent->facts(['DEAT', 'BURI', 'CREM']) as $fact) { 864 // Show death of parent when it happened prior to birth 865 if ($sosa === 1 && Date::compare($fact->date(), $min_date) < 0 || $this->includeFact($fact, $min_date, $max_date)) { 866 switch ($sosa) { 867 case 1: 868 $facts[] = $this->convertEvent($fact, $death_of_a_parent[$fact->getTag()][$fact->record()->sex()]); 869 break; 870 case 2: 871 case 3: 872 switch ($person->sex()) { 873 case 'M': 874 $facts[] = $this->convertEvent($fact, $death_of_a_paternal_grandparent[$fact->getTag()][$fact->record()->sex()]); 875 break; 876 case 'F': 877 $facts[] = $this->convertEvent($fact, $death_of_a_maternal_grandparent[$fact->getTag()][$fact->record()->sex()]); 878 break; 879 default: 880 $facts[] = $this->convertEvent($fact, $death_of_a_grandparent[$fact->getTag()][$fact->record()->sex()]); 881 break; 882 } 883 } 884 } 885 } 886 } 887 } 888 } 889 890 return $facts; 891 } 892 893 /** 894 * Get any historical events. 895 * 896 * @param Individual $individual 897 * 898 * @return Fact[] 899 */ 900 private function historicalFacts(Individual $individual): array 901 { 902 return $this->module_service->findByInterface(ModuleHistoricEventsInterface::class) 903 ->map(static function (ModuleHistoricEventsInterface $module) use ($individual): Collection { 904 return $module->historicEventsForIndividual($individual); 905 }) 906 ->flatten() 907 ->all(); 908 } 909 910 /** 911 * Get the events of associates. 912 * 913 * @param Individual $person 914 * 915 * @return Fact[] 916 */ 917 private function associateFacts(Individual $person): array 918 { 919 $facts = []; 920 921 /** @var Individual[] $associates */ 922 $asso1 = $person->linkedIndividuals('ASSO'); 923 $asso2 = $person->linkedIndividuals('_ASSO'); 924 $asso3 = $person->linkedFamilies('ASSO'); 925 $asso4 = $person->linkedFamilies('_ASSO'); 926 927 $associates = $asso1->merge($asso2)->merge($asso3)->merge($asso4); 928 929 foreach ($associates as $associate) { 930 foreach ($associate->facts() as $fact) { 931 if (preg_match('/\n\d _?ASSO @' . $person->xref() . '@/', $fact->gedcom())) { 932 // Extract the important details from the fact 933 $factrec = '1 ' . $fact->getTag(); 934 if (preg_match('/\n2 DATE .*/', $fact->gedcom(), $match)) { 935 $factrec .= $match[0]; 936 } 937 if (preg_match('/\n2 PLAC .*/', $fact->gedcom(), $match)) { 938 $factrec .= $match[0]; 939 } 940 if ($associate instanceof Family) { 941 foreach ($associate->spouses() as $spouse) { 942 $factrec .= "\n2 _ASSO @" . $spouse->xref() . '@'; 943 } 944 } else { 945 $factrec .= "\n2 _ASSO @" . $associate->xref() . '@'; 946 } 947 $facts[] = new Fact($factrec, $associate, 'asso'); 948 } 949 } 950 } 951 952 return $facts; 953 } 954 955 /** 956 * This module handles the following facts - so don't show them on the "Facts and events" tab. 957 * 958 * @return Collection<string> 959 */ 960 public function supportedFacts(): Collection 961 { 962 // We don't actually displaye these facts, but they are displayed 963 // outside the tabs/sidebar systems. This just forces them to be excluded here. 964 return new Collection(['NAME', 'SEX', 'OBJE']); 965 } 966} 967