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