1<?php 2 3/** 4 * webtrees: online genealogy 5 * Copyright (C) 2020 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 <http://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 return new Fact($gedcom, $fact->record(), $fact->id()); 230 } 231 232 /** 233 * Spouse facts that are shown on an individual’s page. 234 * 235 * @param Individual $individual Show events that occured during the lifetime of this individual 236 * @param Individual $spouse Show events of this individual 237 * @param Date $min_date 238 * @param Date $max_date 239 * 240 * @return Fact[] 241 */ 242 private function spouseFacts(Individual $individual, Individual $spouse, Date $min_date, Date $max_date): array 243 { 244 $SHOW_RELATIVES_EVENTS = $individual->tree()->getPreference('SHOW_RELATIVES_EVENTS'); 245 246 $death_of_a_spouse = [ 247 'DEAT' => [ 248 'M' => I18N::translate('Death of a husband'), 249 'F' => I18N::translate('Death of a wife'), 250 'U' => I18N::translate('Death of a spouse'), 251 ], 252 'BURI' => [ 253 'M' => I18N::translate('Burial of a husband'), 254 'F' => I18N::translate('Burial of a wife'), 255 'U' => I18N::translate('Burial of a spouse'), 256 ], 257 'CREM' => [ 258 'M' => I18N::translate('Cremation of a husband'), 259 'F' => I18N::translate('Cremation of a wife'), 260 'U' => I18N::translate('Cremation of a spouse'), 261 ], 262 ]; 263 264 $facts = []; 265 266 if (str_contains($SHOW_RELATIVES_EVENTS, '_DEAT_SPOU')) { 267 foreach ($spouse->facts(['DEAT', 'BURI', 'CREM']) as $fact) { 268 if ($this->includeFact($fact, $min_date, $max_date)) { 269 $facts[] = $this->convertEvent($fact, $death_of_a_spouse[$fact->getTag()][$fact->record()->sex()]); 270 } 271 } 272 } 273 274 return $facts; 275 } 276 277 /** 278 * Get the events of children and grandchildren. 279 * 280 * @param Individual $person 281 * @param Family $family 282 * @param string $option 283 * @param string $relation 284 * @param Date $min_date 285 * @param Date $max_date 286 * 287 * @return Fact[] 288 */ 289 private function childFacts(Individual $person, Family $family, $option, $relation, Date $min_date, Date $max_date): array 290 { 291 $SHOW_RELATIVES_EVENTS = $person->tree()->getPreference('SHOW_RELATIVES_EVENTS'); 292 293 $birth_of_a_child = [ 294 'BIRT' => [ 295 'M' => I18N::translate('Birth of a son'), 296 'F' => I18N::translate('Birth of a daughter'), 297 'U' => I18N::translate('Birth of a child'), 298 ], 299 'CHR' => [ 300 'M' => I18N::translate('Christening of a son'), 301 'F' => I18N::translate('Christening of a daughter'), 302 'U' => I18N::translate('Christening of a child'), 303 ], 304 'BAPM' => [ 305 'M' => I18N::translate('Baptism of a son'), 306 'F' => I18N::translate('Baptism of a daughter'), 307 'U' => I18N::translate('Baptism of a child'), 308 ], 309 'ADOP' => [ 310 'M' => I18N::translate('Adoption of a son'), 311 'F' => I18N::translate('Adoption of a daughter'), 312 'U' => I18N::translate('Adoption of a child'), 313 ], 314 ]; 315 316 $birth_of_a_sibling = [ 317 'BIRT' => [ 318 'M' => I18N::translate('Birth of a brother'), 319 'F' => I18N::translate('Birth of a sister'), 320 'U' => I18N::translate('Birth of a sibling'), 321 ], 322 'CHR' => [ 323 'M' => I18N::translate('Christening of a brother'), 324 'F' => I18N::translate('Christening of a sister'), 325 'U' => I18N::translate('Christening of a sibling'), 326 ], 327 'BAPM' => [ 328 'M' => I18N::translate('Baptism of a brother'), 329 'F' => I18N::translate('Baptism of a sister'), 330 'U' => I18N::translate('Baptism of a sibling'), 331 ], 332 'ADOP' => [ 333 'M' => I18N::translate('Adoption of a brother'), 334 'F' => I18N::translate('Adoption of a sister'), 335 'U' => I18N::translate('Adoption of a sibling'), 336 ], 337 ]; 338 339 $birth_of_a_half_sibling = [ 340 'BIRT' => [ 341 'M' => I18N::translate('Birth of a half-brother'), 342 'F' => I18N::translate('Birth of a half-sister'), 343 'U' => I18N::translate('Birth of a half-sibling'), 344 ], 345 'CHR' => [ 346 'M' => I18N::translate('Christening of a half-brother'), 347 'F' => I18N::translate('Christening of a half-sister'), 348 'U' => I18N::translate('Christening of a half-sibling'), 349 ], 350 'BAPM' => [ 351 'M' => I18N::translate('Baptism of a half-brother'), 352 'F' => I18N::translate('Baptism of a half-sister'), 353 'U' => I18N::translate('Baptism of a half-sibling'), 354 ], 355 'ADOP' => [ 356 'M' => I18N::translate('Adoption of a half-brother'), 357 'F' => I18N::translate('Adoption of a half-sister'), 358 'U' => I18N::translate('Adoption of a half-sibling'), 359 ], 360 ]; 361 362 $birth_of_a_grandchild = [ 363 'BIRT' => [ 364 'M' => I18N::translate('Birth of a grandson'), 365 'F' => I18N::translate('Birth of a granddaughter'), 366 'U' => I18N::translate('Birth of a grandchild'), 367 ], 368 'CHR' => [ 369 'M' => I18N::translate('Christening of a grandson'), 370 'F' => I18N::translate('Christening of a granddaughter'), 371 'U' => I18N::translate('Christening of a grandchild'), 372 ], 373 'BAPM' => [ 374 'M' => I18N::translate('Baptism of a grandson'), 375 'F' => I18N::translate('Baptism of a granddaughter'), 376 'U' => I18N::translate('Baptism of a grandchild'), 377 ], 378 'ADOP' => [ 379 'M' => I18N::translate('Adoption of a grandson'), 380 'F' => I18N::translate('Adoption of a granddaughter'), 381 'U' => I18N::translate('Adoption of a grandchild'), 382 ], 383 ]; 384 385 $birth_of_a_grandchild1 = [ 386 'BIRT' => [ 387 'M' => I18N::translateContext('daughter’s son', 'Birth of a grandson'), 388 'F' => I18N::translateContext('daughter’s daughter', 'Birth of a granddaughter'), 389 'U' => I18N::translate('Birth of a grandchild'), 390 ], 391 'CHR' => [ 392 'M' => I18N::translateContext('daughter’s son', 'Christening of a grandson'), 393 'F' => I18N::translateContext('daughter’s daughter', 'Christening of a granddaughter'), 394 'U' => I18N::translate('Christening of a grandchild'), 395 ], 396 'BAPM' => [ 397 'M' => I18N::translateContext('daughter’s son', 'Baptism of a grandson'), 398 'F' => I18N::translateContext('daughter’s daughter', 'Baptism of a granddaughter'), 399 'U' => I18N::translate('Baptism of a grandchild'), 400 ], 401 'ADOP' => [ 402 'M' => I18N::translateContext('daughter’s son', 'Adoption of a grandson'), 403 'F' => I18N::translateContext('daughter’s daughter', 'Adoption of a granddaughter'), 404 'U' => I18N::translate('Adoption of a grandchild'), 405 ], 406 ]; 407 408 $birth_of_a_grandchild2 = [ 409 'BIRT' => [ 410 'M' => I18N::translateContext('son’s son', 'Birth of a grandson'), 411 'F' => I18N::translateContext('son’s daughter', 'Birth of a granddaughter'), 412 'U' => I18N::translate('Birth of a grandchild'), 413 ], 414 'CHR' => [ 415 'M' => I18N::translateContext('son’s son', 'Christening of a grandson'), 416 'F' => I18N::translateContext('son’s daughter', 'Christening of a granddaughter'), 417 'U' => I18N::translate('Christening of a grandchild'), 418 ], 419 'BAPM' => [ 420 'M' => I18N::translateContext('son’s son', 'Baptism of a grandson'), 421 'F' => I18N::translateContext('son’s daughter', 'Baptism of a granddaughter'), 422 'U' => I18N::translate('Baptism of a grandchild'), 423 ], 424 'ADOP' => [ 425 'M' => I18N::translateContext('son’s son', 'Adoption of a grandson'), 426 'F' => I18N::translateContext('son’s daughter', 'Adoption of a granddaughter'), 427 'U' => I18N::translate('Adoption of a grandchild'), 428 ], 429 ]; 430 431 $death_of_a_child = [ 432 'DEAT' => [ 433 'M' => I18N::translate('Death of a son'), 434 'F' => I18N::translate('Death of a daughter'), 435 'U' => I18N::translate('Death of a child'), 436 ], 437 'BURI' => [ 438 'M' => I18N::translate('Burial of a son'), 439 'F' => I18N::translate('Burial of a daughter'), 440 'U' => I18N::translate('Burial of a child'), 441 ], 442 'CREM' => [ 443 'M' => I18N::translate('Cremation of a son'), 444 'F' => I18N::translate('Cremation of a daughter'), 445 'U' => I18N::translate('Cremation of a child'), 446 ], 447 ]; 448 449 $death_of_a_sibling = [ 450 'DEAT' => [ 451 'M' => I18N::translate('Death of a brother'), 452 'F' => I18N::translate('Death of a sister'), 453 'U' => I18N::translate('Death of a sibling'), 454 ], 455 'BURI' => [ 456 'M' => I18N::translate('Burial of a brother'), 457 'F' => I18N::translate('Burial of a sister'), 458 'U' => I18N::translate('Burial of a sibling'), 459 ], 460 'CREM' => [ 461 'M' => I18N::translate('Cremation of a brother'), 462 'F' => I18N::translate('Cremation of a sister'), 463 'U' => I18N::translate('Cremation of a sibling'), 464 ], 465 ]; 466 467 $death_of_a_half_sibling = [ 468 'DEAT' => [ 469 'M' => I18N::translate('Death of a half-brother'), 470 'F' => I18N::translate('Death of a half-sister'), 471 'U' => I18N::translate('Death of a half-sibling'), 472 ], 473 'BURI' => [ 474 'M' => I18N::translate('Burial of a half-brother'), 475 'F' => I18N::translate('Burial of a half-sister'), 476 'U' => I18N::translate('Burial of a half-sibling'), 477 ], 478 'CREM' => [ 479 'M' => I18N::translate('Cremation of a half-brother'), 480 'F' => I18N::translate('Cremation of a half-sister'), 481 'U' => I18N::translate('Cremation of a half-sibling'), 482 ], 483 ]; 484 485 $death_of_a_grandchild = [ 486 'DEAT' => [ 487 'M' => I18N::translate('Death of a grandson'), 488 'F' => I18N::translate('Death of a granddaughter'), 489 'U' => I18N::translate('Death of a grandchild'), 490 ], 491 'BURI' => [ 492 'M' => I18N::translate('Burial of a grandson'), 493 'F' => I18N::translate('Burial of a granddaughter'), 494 'U' => I18N::translate('Burial of a grandchild'), 495 ], 496 'CREM' => [ 497 'M' => I18N::translate('Cremation of a grandson'), 498 'F' => I18N::translate('Cremation of a granddaughter'), 499 'U' => I18N::translate('Baptism of a grandchild'), 500 ], 501 ]; 502 503 $death_of_a_grandchild1 = [ 504 'DEAT' => [ 505 'M' => I18N::translateContext('daughter’s son', 'Death of a grandson'), 506 'F' => I18N::translateContext('daughter’s daughter', 'Death of a granddaughter'), 507 'U' => I18N::translate('Death of a grandchild'), 508 ], 509 'BURI' => [ 510 'M' => I18N::translateContext('daughter’s son', 'Burial of a grandson'), 511 'F' => I18N::translateContext('daughter’s daughter', 'Burial of a granddaughter'), 512 'U' => I18N::translate('Burial of a grandchild'), 513 ], 514 'CREM' => [ 515 'M' => I18N::translateContext('daughter’s son', 'Cremation of a grandson'), 516 'F' => I18N::translateContext('daughter’s daughter', 'Cremation of a granddaughter'), 517 'U' => I18N::translate('Baptism of a grandchild'), 518 ], 519 ]; 520 521 $death_of_a_grandchild2 = [ 522 'DEAT' => [ 523 'M' => I18N::translateContext('son’s son', 'Death of a grandson'), 524 'F' => I18N::translateContext('son’s daughter', 'Death of a granddaughter'), 525 'U' => I18N::translate('Death of a grandchild'), 526 ], 527 'BURI' => [ 528 'M' => I18N::translateContext('son’s son', 'Burial of a grandson'), 529 'F' => I18N::translateContext('son’s daughter', 'Burial of a granddaughter'), 530 'U' => I18N::translate('Burial of a grandchild'), 531 ], 532 'CREM' => [ 533 'M' => I18N::translateContext('son’s son', 'Cremation of a grandson'), 534 'F' => I18N::translateContext('son’s daughter', 'Cremation of a granddaughter'), 535 'U' => I18N::translate('Cremation of a grandchild'), 536 ], 537 ]; 538 539 $marriage_of_a_child = [ 540 'M' => I18N::translate('Marriage of a son'), 541 'F' => I18N::translate('Marriage of a daughter'), 542 'U' => I18N::translate('Marriage of a child'), 543 ]; 544 545 $marriage_of_a_grandchild = [ 546 'M' => I18N::translate('Marriage of a grandson'), 547 'F' => I18N::translate('Marriage of a granddaughter'), 548 'U' => I18N::translate('Marriage of a grandchild'), 549 ]; 550 551 $marriage_of_a_grandchild1 = [ 552 'M' => I18N::translateContext('daughter’s son', 'Marriage of a grandson'), 553 'F' => I18N::translateContext('daughter’s daughter', 'Marriage of a granddaughter'), 554 'U' => I18N::translate('Marriage of a grandchild'), 555 ]; 556 557 $marriage_of_a_grandchild2 = [ 558 'M' => I18N::translateContext('son’s son', 'Marriage of a grandson'), 559 'F' => I18N::translateContext('son’s daughter', 'Marriage of a granddaughter'), 560 'U' => I18N::translate('Marriage of a grandchild'), 561 ]; 562 563 $marriage_of_a_sibling = [ 564 'M' => I18N::translate('Marriage of a brother'), 565 'F' => I18N::translate('Marriage of a sister'), 566 'U' => I18N::translate('Marriage of a sibling'), 567 ]; 568 569 $marriage_of_a_half_sibling = [ 570 'M' => I18N::translate('Marriage of a half-brother'), 571 'F' => I18N::translate('Marriage of a half-sister'), 572 'U' => I18N::translate('Marriage of a half-sibling'), 573 ]; 574 575 $facts = []; 576 577 // Deal with recursion. 578 switch ($option) { 579 case '_CHIL': 580 // Add grandchildren 581 foreach ($family->children() as $child) { 582 foreach ($child->spouseFamilies() as $cfamily) { 583 switch ($child->sex()) { 584 case 'M': 585 foreach ($this->childFacts($person, $cfamily, '_GCHI', 'son', $min_date, $max_date) as $fact) { 586 $facts[] = $fact; 587 } 588 break; 589 case 'F': 590 foreach ($this->childFacts($person, $cfamily, '_GCHI', 'dau', $min_date, $max_date) as $fact) { 591 $facts[] = $fact; 592 } 593 break; 594 default: 595 foreach ($this->childFacts($person, $cfamily, '_GCHI', 'chi', $min_date, $max_date) as $fact) { 596 $facts[] = $fact; 597 } 598 break; 599 } 600 } 601 } 602 break; 603 } 604 605 // For each child in the family 606 foreach ($family->children() as $child) { 607 if ($child->xref() === $person->xref()) { 608 // We are not our own sibling! 609 continue; 610 } 611 // add child’s birth 612 if (str_contains($SHOW_RELATIVES_EVENTS, '_BIRT' . str_replace('_HSIB', '_SIBL', $option))) { 613 foreach ($child->facts(['BIRT', 'CHR', 'BAPM', 'ADOP']) as $fact) { 614 // Always show _BIRT_CHIL, even if the dates are not known 615 if ($option === '_CHIL' || $this->includeFact($fact, $min_date, $max_date)) { 616 switch ($option) { 617 case '_GCHI': 618 switch ($relation) { 619 case 'dau': 620 $facts[] = $this->convertEvent($fact, $birth_of_a_grandchild1[$fact->getTag()][$fact->record()->sex()]); 621 break; 622 case 'son': 623 $facts[] = $this->convertEvent($fact, $birth_of_a_grandchild2[$fact->getTag()][$fact->record()->sex()]); 624 break; 625 case 'chil': 626 $facts[] = $this->convertEvent($fact, $birth_of_a_grandchild[$fact->getTag()][$fact->record()->sex()]); 627 break; 628 } 629 break; 630 case '_SIBL': 631 $facts[] = $this->convertEvent($fact, $birth_of_a_sibling[$fact->getTag()][$fact->record()->sex()]); 632 break; 633 case '_HSIB': 634 $facts[] = $this->convertEvent($fact, $birth_of_a_half_sibling[$fact->getTag()][$fact->record()->sex()]); 635 break; 636 case '_CHIL': 637 $facts[] = $this->convertEvent($fact, $birth_of_a_child[$fact->getTag()][$fact->record()->sex()]); 638 break; 639 } 640 } 641 } 642 } 643 // add child’s death 644 if (str_contains($SHOW_RELATIVES_EVENTS, '_DEAT' . str_replace('_HSIB', '_SIBL', $option))) { 645 foreach ($child->facts(['DEAT', 'BURI', 'CREM']) as $fact) { 646 if ($this->includeFact($fact, $min_date, $max_date)) { 647 switch ($option) { 648 case '_GCHI': 649 switch ($relation) { 650 case 'dau': 651 $facts[] = $this->convertEvent($fact, $death_of_a_grandchild1[$fact->getTag()][$fact->record()->sex()]); 652 break; 653 case 'son': 654 $facts[] = $this->convertEvent($fact, $death_of_a_grandchild2[$fact->getTag()][$fact->record()->sex()]); 655 break; 656 case 'chi': 657 $facts[] = $this->convertEvent($fact, $death_of_a_grandchild[$fact->getTag()][$fact->record()->sex()]); 658 break; 659 } 660 break; 661 case '_SIBL': 662 $facts[] = $this->convertEvent($fact, $death_of_a_sibling[$fact->getTag()][$fact->record()->sex()]); 663 break; 664 case '_HSIB': 665 $facts[] = $this->convertEvent($fact, $death_of_a_half_sibling[$fact->getTag()][$fact->record()->sex()]); 666 break; 667 case '_CHIL': 668 $facts[] = $this->convertEvent($fact, $death_of_a_child[$fact->getTag()][$fact->record()->sex()]); 669 break; 670 } 671 } 672 } 673 } 674 675 // add child’s marriage 676 if (str_contains($SHOW_RELATIVES_EVENTS, '_MARR' . str_replace('_HSIB', '_SIBL', $option))) { 677 foreach ($child->spouseFamilies() as $sfamily) { 678 foreach ($sfamily->facts(['MARR']) as $fact) { 679 if ($this->includeFact($fact, $min_date, $max_date)) { 680 switch ($option) { 681 case '_GCHI': 682 switch ($relation) { 683 case 'dau': 684 $facts[] = $this->convertEvent($fact, $marriage_of_a_grandchild1['F']); 685 break; 686 case 'son': 687 $facts[] = $this->convertEvent($fact, $marriage_of_a_grandchild2['M']); 688 break; 689 case 'chi': 690 $facts[] = $this->convertEvent($fact, $marriage_of_a_grandchild['U']); 691 break; 692 } 693 break; 694 case '_SIBL': 695 $facts[] = $this->convertEvent($fact, $marriage_of_a_sibling['U']); 696 break; 697 case '_HSIB': 698 $facts[] = $this->convertEvent($fact, $marriage_of_a_half_sibling['U']); 699 break; 700 case '_CHIL': 701 $facts[] = $this->convertEvent($fact, $marriage_of_a_child['U']); 702 break; 703 } 704 } 705 } 706 } 707 } 708 } 709 710 return $facts; 711 } 712 713 /** 714 * Get the events of parents and grandparents. 715 * 716 * @param Individual $person 717 * @param int $sosa 718 * @param Date $min_date 719 * @param Date $max_date 720 * 721 * @return Fact[] 722 */ 723 private function parentFacts(Individual $person, int $sosa, Date $min_date, Date $max_date): array 724 { 725 $SHOW_RELATIVES_EVENTS = $person->tree()->getPreference('SHOW_RELATIVES_EVENTS'); 726 727 $death_of_a_parent = [ 728 'DEAT' => [ 729 'M' => I18N::translate('Death of a father'), 730 'F' => I18N::translate('Death of a mother'), 731 'U' => I18N::translate('Death of a parent'), 732 ], 733 'BURI' => [ 734 'M' => I18N::translate('Burial of a father'), 735 'F' => I18N::translate('Burial of a mother'), 736 'U' => I18N::translate('Burial of a parent'), 737 ], 738 'CREM' => [ 739 'M' => I18N::translate('Cremation of a father'), 740 'F' => I18N::translate('Cremation of a mother'), 741 'U' => I18N::translate('Cremation of a parent'), 742 ], 743 ]; 744 745 $death_of_a_grandparent = [ 746 'DEAT' => [ 747 'M' => I18N::translate('Death of a grandfather'), 748 'F' => I18N::translate('Death of a grandmother'), 749 'U' => I18N::translate('Death of a grandparent'), 750 ], 751 'BURI' => [ 752 'M' => I18N::translate('Burial of a grandfather'), 753 'F' => I18N::translate('Burial of a grandmother'), 754 'U' => I18N::translate('Burial of a grandparent'), 755 ], 756 'CREM' => [ 757 'M' => I18N::translate('Cremation of a grandfather'), 758 'F' => I18N::translate('Cremation of a grandmother'), 759 'U' => I18N::translate('Cremation of a grandparent'), 760 ], 761 ]; 762 763 $death_of_a_maternal_grandparent = [ 764 'DEAT' => [ 765 'M' => I18N::translate('Death of a maternal grandfather'), 766 'F' => I18N::translate('Death of a maternal grandmother'), 767 'U' => I18N::translate('Death of a grandparent'), 768 ], 769 'BURI' => [ 770 'M' => I18N::translate('Burial of a maternal grandfather'), 771 'F' => I18N::translate('Burial of a maternal grandmother'), 772 'U' => I18N::translate('Burial of a grandparent'), 773 ], 774 'CREM' => [ 775 'M' => I18N::translate('Cremation of a maternal grandfather'), 776 'F' => I18N::translate('Cremation of a maternal grandmother'), 777 'U' => I18N::translate('Cremation of a grandparent'), 778 ], 779 ]; 780 781 $death_of_a_paternal_grandparent = [ 782 'DEAT' => [ 783 'M' => I18N::translate('Death of a paternal grandfather'), 784 'F' => I18N::translate('Death of a paternal grandmother'), 785 'U' => I18N::translate('Death of a grandparent'), 786 ], 787 'BURI' => [ 788 'M' => I18N::translate('Burial of a paternal grandfather'), 789 'F' => I18N::translate('Burial of a paternal grandmother'), 790 'U' => I18N::translate('Burial of a grandparent'), 791 ], 792 'CREM' => [ 793 'M' => I18N::translate('Cremation of a paternal grandfather'), 794 'F' => I18N::translate('Cremation of a paternal grandmother'), 795 'U' => I18N::translate('Cremation of a grandparent'), 796 ], 797 ]; 798 799 $marriage_of_a_parent = [ 800 'M' => I18N::translate('Marriage of a father'), 801 'F' => I18N::translate('Marriage of a mother'), 802 'U' => I18N::translate('Marriage of a parent'), 803 ]; 804 805 $facts = []; 806 807 if ($sosa === 1) { 808 foreach ($person->childFamilies() as $family) { 809 // Add siblings 810 foreach ($this->childFacts($person, $family, '_SIBL', '', $min_date, $max_date) as $fact) { 811 $facts[] = $fact; 812 } 813 foreach ($family->spouses() as $spouse) { 814 foreach ($spouse->spouseFamilies() as $sfamily) { 815 if ($family !== $sfamily) { 816 // Add half-siblings 817 foreach ($this->childFacts($person, $sfamily, '_HSIB', '', $min_date, $max_date) as $fact) { 818 $facts[] = $fact; 819 } 820 } 821 } 822 // Add grandparents 823 foreach ($this->parentFacts($spouse, $spouse->sex() === 'F' ? 3 : 2, $min_date, $max_date) as $fact) { 824 $facts[] = $fact; 825 } 826 } 827 } 828 829 if (str_contains($SHOW_RELATIVES_EVENTS, '_MARR_PARE')) { 830 // add father/mother marriages 831 foreach ($person->childFamilies() as $sfamily) { 832 foreach ($sfamily->facts(['MARR']) as $fact) { 833 if ($this->includeFact($fact, $min_date, $max_date)) { 834 // marriage of parents (to each other) 835 $facts[] = $this->convertEvent($fact, I18N::translate('Marriage of parents')); 836 } 837 } 838 } 839 foreach ($person->childStepFamilies() as $sfamily) { 840 foreach ($sfamily->facts(['MARR']) as $fact) { 841 if ($this->includeFact($fact, $min_date, $max_date)) { 842 // marriage of a parent (to another spouse) 843 $facts[] = $this->convertEvent($fact, $marriage_of_a_parent['U']); 844 } 845 } 846 } 847 } 848 } 849 850 foreach ($person->childFamilies() as $family) { 851 foreach ($family->spouses() as $parent) { 852 if (str_contains($SHOW_RELATIVES_EVENTS, '_DEAT' . ($sosa === 1 ? '_PARE' : '_GPAR'))) { 853 foreach ($parent->facts(['DEAT', 'BURI', 'CREM']) as $fact) { 854 // Show death of parent when it happened prior to birth 855 if ($sosa === 1 && Date::compare($fact->date(), $min_date) < 0 || $this->includeFact($fact, $min_date, $max_date)) { 856 switch ($sosa) { 857 case 1: 858 $facts[] = $this->convertEvent($fact, $death_of_a_parent[$fact->getTag()][$fact->record()->sex()]); 859 break; 860 case 2: 861 case 3: 862 switch ($person->sex()) { 863 case 'M': 864 $facts[] = $this->convertEvent($fact, $death_of_a_paternal_grandparent[$fact->getTag()][$fact->record()->sex()]); 865 break; 866 case 'F': 867 $facts[] = $this->convertEvent($fact, $death_of_a_maternal_grandparent[$fact->getTag()][$fact->record()->sex()]); 868 break; 869 default: 870 $facts[] = $this->convertEvent($fact, $death_of_a_grandparent[$fact->getTag()][$fact->record()->sex()]); 871 break; 872 } 873 } 874 } 875 } 876 } 877 } 878 } 879 880 return $facts; 881 } 882 883 /** 884 * Get any historical events. 885 * 886 * @param Individual $individual 887 * 888 * @return Fact[] 889 */ 890 private function historicalFacts(Individual $individual): array 891 { 892 return $this->module_service->findByInterface(ModuleHistoricEventsInterface::class) 893 ->map(static function (ModuleHistoricEventsInterface $module) use ($individual): Collection { 894 return $module->historicEventsForIndividual($individual); 895 }) 896 ->flatten() 897 ->all(); 898 } 899 900 /** 901 * Get the events of associates. 902 * 903 * @param Individual $person 904 * 905 * @return Fact[] 906 */ 907 private function associateFacts(Individual $person): array 908 { 909 $facts = []; 910 911 /** @var Individual[] $associates */ 912 $asso1 = $person->linkedIndividuals('ASSO'); 913 $asso2 = $person->linkedIndividuals('_ASSO'); 914 $asso3 = $person->linkedFamilies('ASSO'); 915 $asso4 = $person->linkedFamilies('_ASSO'); 916 917 $associates = $asso1->merge($asso2)->merge($asso3)->merge($asso4); 918 919 foreach ($associates as $associate) { 920 foreach ($associate->facts() as $fact) { 921 if (preg_match('/\n\d _?ASSO @' . $person->xref() . '@/', $fact->gedcom())) { 922 // Extract the important details from the fact 923 $factrec = '1 ' . $fact->getTag(); 924 if (preg_match('/\n2 DATE .*/', $fact->gedcom(), $match)) { 925 $factrec .= $match[0]; 926 } 927 if (preg_match('/\n2 PLAC .*/', $fact->gedcom(), $match)) { 928 $factrec .= $match[0]; 929 } 930 if ($associate instanceof Family) { 931 foreach ($associate->spouses() as $spouse) { 932 $factrec .= "\n2 _ASSO @" . $spouse->xref() . '@'; 933 } 934 } else { 935 $factrec .= "\n2 _ASSO @" . $associate->xref() . '@'; 936 } 937 $facts[] = new Fact($factrec, $associate, 'asso'); 938 } 939 } 940 } 941 942 return $facts; 943 } 944 945 /** 946 * This module handles the following facts - so don't show them on the "Facts and events" tab. 947 * 948 * @return Collection<string> 949 */ 950 public function supportedFacts(): Collection 951 { 952 // We don't actually displaye these facts, but they are displayed 953 // outside the tabs/sidebar systems. This just forces them to be excluded here. 954 return new Collection(['NAME', 'SEX', 'OBJE']); 955 } 956} 957