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