1<?php 2/** 3 * webtrees: online genealogy 4 * Copyright (C) 2019 webtrees development team 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, either version 3 of the License, or 8 * (at your option) any later version. 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * You should have received a copy of the GNU General Public License 14 * along with this program. If not, see <http://www.gnu.org/licenses/>. 15 */ 16declare(strict_types=1); 17 18namespace Fisharebest\Webtrees\Module; 19 20use Fisharebest\Webtrees\Date; 21use Fisharebest\Webtrees\Fact; 22use Fisharebest\Webtrees\Family; 23use Fisharebest\Webtrees\Functions\Functions; 24use Fisharebest\Webtrees\Gedcom; 25use Fisharebest\Webtrees\I18N; 26use Fisharebest\Webtrees\Individual; 27use Fisharebest\Webtrees\Module; 28use Fisharebest\Webtrees\Site; 29 30/** 31 * Class IndividualFactsTabModule 32 */ 33class IndividualFactsTabModule extends AbstractModule implements ModuleTabInterface 34{ 35 /** {@inheritdoc} */ 36 public function getTitle(): string 37 { 38 /* I18N: Name of a module/tab on the individual page. */ 39 return I18N::translate('Facts and events'); 40 } 41 42 /** {@inheritdoc} */ 43 public function getDescription(): string 44 { 45 /* I18N: Description of the “Facts and events” module */ 46 return I18N::translate('A tab showing the facts and events of an individual.'); 47 } 48 49 /** {@inheritdoc} */ 50 public function defaultTabOrder(): int 51 { 52 return 10; 53 } 54 55 /** {@inheritdoc} */ 56 public function isGrayedOut(Individual $individual): bool 57 { 58 return false; 59 } 60 61 /** {@inheritdoc} */ 62 public function getTabContent(Individual $individual): string 63 { 64 // Only include events of close relatives that are between birth and death 65 $min_date = $individual->getEstimatedBirthDate(); 66 $max_date = $individual->getEstimatedDeathDate(); 67 68 $indifacts = []; 69 // The individual’s own facts 70 foreach ($individual->facts() as $fact) { 71 switch ($fact->getTag()) { 72 case 'SEX': 73 case 'NAME': 74 case 'SOUR': 75 case 'OBJE': 76 case 'NOTE': 77 case 'FAMC': 78 case 'FAMS': 79 break; 80 default: 81 if (!array_key_exists('extra_info', Module::getActiveSidebars($individual->tree())) || !ExtraInformationModule::showFact($fact)) { 82 $indifacts[] = $fact; 83 } 84 break; 85 } 86 } 87 88 // Add spouse-family facts 89 foreach ($individual->getSpouseFamilies() as $family) { 90 foreach ($family->facts() as $fact) { 91 switch ($fact->getTag()) { 92 case 'SOUR': 93 case 'NOTE': 94 case 'OBJE': 95 case 'CHAN': 96 case '_UID': 97 case 'RIN': 98 case 'HUSB': 99 case 'WIFE': 100 case 'CHIL': 101 break; 102 default: 103 $indifacts[] = $fact; 104 break; 105 } 106 } 107 108 $spouse = $family->getSpouse($individual); 109 110 if ($spouse instanceof Individual) { 111 $spouse_facts = self::spouseFacts($individual, $spouse, $min_date, $max_date); 112 $indifacts = array_merge($indifacts, $spouse_facts); 113 } 114 115 $child_facts = self::childFacts($individual, $family, '_CHIL', '', $min_date, $max_date); 116 $indifacts = array_merge($indifacts, $child_facts); 117 } 118 119 $parent_facts = self::parentFacts($individual, 1, $min_date, $max_date); 120 $associate_facts = self::associateFacts($individual); 121 $historical_facts = self::historicalFacts($individual, $min_date, $max_date); 122 123 $indifacts = array_merge($indifacts, $parent_facts, $associate_facts, $historical_facts); 124 125 Functions::sortFacts($indifacts); 126 127 return view('modules/personal_facts/tab', [ 128 'can_edit' => $individual->canEdit(), 129 'has_historical_facts' => !empty($historical_facts), 130 'individual' => $individual, 131 'facts' => $indifacts, 132 ]); 133 } 134 135 /** 136 * Does a relative event occur within a date range (i.e. the individual's lifetime)? 137 * 138 * @param Fact $fact 139 * @param Date $min_date 140 * @param Date $max_date 141 * 142 * @return bool 143 */ 144 private static function includeFact(Fact $fact, Date $min_date, Date $max_date): bool 145 { 146 $fact_date = $fact->date(); 147 148 return $fact_date->isOK() && Date::compare($min_date, $fact_date) <= 0 && Date::compare($fact_date, $max_date) <= 0; 149 } 150 151 /** {@inheritdoc} */ 152 public function hasTabContent(Individual $individual): bool 153 { 154 return true; 155 } 156 157 /** {@inheritdoc} */ 158 public function canLoadAjax(): bool 159 { 160 return false; 161 } 162 163 /** 164 * Spouse facts that are shown on an individual’s page. 165 * 166 * @param Individual $individual Show events that occured during the lifetime of this individual 167 * @param Individual $spouse Show events of this individual 168 * @param Date $min_date 169 * @param Date $max_date 170 * 171 * @return Fact[] 172 */ 173 private static function spouseFacts(Individual $individual, Individual $spouse, Date $min_date, Date $max_date): array 174 { 175 $SHOW_RELATIVES_EVENTS = $individual->tree()->getPreference('SHOW_RELATIVES_EVENTS'); 176 177 $facts = []; 178 if (strstr($SHOW_RELATIVES_EVENTS, '_DEAT_SPOU')) { 179 foreach ($spouse->facts(Gedcom::DEATH_EVENTS) as $fact) { 180 if (self::includeFact($fact, $min_date, $max_date)) { 181 // Convert the event to a close relatives event. 182 $rela_fact = clone($fact); 183 $rela_fact->setTag('_' . $fact->getTag() . '_SPOU'); 184 $facts[] = $rela_fact; 185 } 186 } 187 } 188 189 return $facts; 190 } 191 192 /** 193 * Get the events of children and grandchildren. 194 * 195 * @param Individual $person 196 * @param Family $family 197 * @param string $option 198 * @param string $relation 199 * @param Date $min_date 200 * @param Date $max_date 201 * 202 * @return Fact[] 203 */ 204 private static function childFacts(Individual $person, Family $family, $option, $relation, Date $min_date, Date $max_date): array 205 { 206 $SHOW_RELATIVES_EVENTS = $person->tree()->getPreference('SHOW_RELATIVES_EVENTS'); 207 208 $facts = []; 209 210 // Deal with recursion. 211 switch ($option) { 212 case '_CHIL': 213 // Add grandchildren 214 foreach ($family->getChildren() as $child) { 215 foreach ($child->getSpouseFamilies() as $cfamily) { 216 switch ($child->getSex()) { 217 case 'M': 218 foreach (self::childFacts($person, $cfamily, '_GCHI', 'son', $min_date, $max_date) as $fact) { 219 $facts[] = $fact; 220 } 221 break; 222 case 'F': 223 foreach (self::childFacts($person, $cfamily, '_GCHI', 'dau', $min_date, $max_date) as $fact) { 224 $facts[] = $fact; 225 } 226 break; 227 default: 228 foreach (self::childFacts($person, $cfamily, '_GCHI', 'chi', $min_date, $max_date) as $fact) { 229 $facts[] = $fact; 230 } 231 break; 232 } 233 } 234 } 235 break; 236 } 237 238 // For each child in the family 239 foreach ($family->getChildren() as $child) { 240 if ($child->xref() == $person->xref()) { 241 // We are not our own sibling! 242 continue; 243 } 244 // add child’s birth 245 if (strpos($SHOW_RELATIVES_EVENTS, '_BIRT' . str_replace('_HSIB', '_SIBL', $option)) !== false) { 246 foreach ($child->facts(Gedcom::BIRTH_EVENTS) as $fact) { 247 // Always show _BIRT_CHIL, even if the dates are not known 248 if ($option == '_CHIL' || self::includeFact($fact, $min_date, $max_date)) { 249 if ($option == '_GCHI' && $relation == 'dau') { 250 // Convert the event to a close relatives event. 251 $rela_fact = clone($fact); 252 $rela_fact->setTag('_' . $fact->getTag() . '_GCH1'); 253 $facts[] = $rela_fact; 254 } elseif ($option == '_GCHI' && $relation == 'son') { 255 // Convert the event to a close relatives event. 256 $rela_fact = clone($fact); 257 $rela_fact->setTag('_' . $fact->getTag() . '_GCH2'); 258 $facts[] = $rela_fact; 259 } else { 260 // Convert the event to a close relatives event. 261 $rela_fact = clone($fact); 262 $rela_fact->setTag('_' . $fact->getTag() . $option); 263 $facts[] = $rela_fact; 264 } 265 } 266 } 267 } 268 // add child’s death 269 if (strpos($SHOW_RELATIVES_EVENTS, '_DEAT' . str_replace('_HSIB', '_SIBL', $option)) !== false) { 270 foreach ($child->facts(Gedcom::DEATH_EVENTS) as $fact) { 271 if (self::includeFact($fact, $min_date, $max_date)) { 272 if ($option == '_GCHI' && $relation == 'dau') { 273 // Convert the event to a close relatives event. 274 $rela_fact = clone($fact); 275 $rela_fact->setTag('_' . $fact->getTag() . '_GCH1'); 276 $facts[] = $rela_fact; 277 } elseif ($option == '_GCHI' && $relation == 'son') { 278 // Convert the event to a close relatives event. 279 $rela_fact = clone($fact); 280 $rela_fact->setTag('_' . $fact->getTag() . '_GCH2'); 281 $facts[] = $rela_fact; 282 } else { 283 // Convert the event to a close relatives event. 284 $rela_fact = clone($fact); 285 $rela_fact->setTag('_' . $fact->getTag() . $option); 286 $facts[] = $rela_fact; 287 } 288 } 289 } 290 } 291 // add child’s marriage 292 if (strstr($SHOW_RELATIVES_EVENTS, '_MARR' . str_replace('_HSIB', '_SIBL', $option))) { 293 foreach ($child->getSpouseFamilies() as $sfamily) { 294 foreach ($sfamily->facts(['MARR']) as $fact) { 295 if (self::includeFact($fact, $min_date, $max_date)) { 296 if ($option == '_GCHI' && $relation == 'dau') { 297 // Convert the event to a close relatives event. 298 $rela_fact = clone($fact); 299 $rela_fact->setTag('_' . $fact->getTag() . '_GCH1'); 300 $facts[] = $rela_fact; 301 } elseif ($option == '_GCHI' && $relation == 'son') { 302 // Convert the event to a close relatives event. 303 $rela_fact = clone($fact); 304 $rela_fact->setTag('_' . $fact->getTag() . '_GCH2'); 305 $facts[] = $rela_fact; 306 } else { 307 // Convert the event to a close relatives event. 308 $rela_fact = clone($fact); 309 $rela_fact->setTag('_' . $fact->getTag() . $option); 310 $facts[] = $rela_fact; 311 } 312 } 313 } 314 } 315 } 316 } 317 318 return $facts; 319 } 320 321 /** 322 * Get the events of parents and grandparents. 323 * 324 * @param Individual $person 325 * @param int $sosa 326 * @param Date $min_date 327 * @param Date $max_date 328 * 329 * @return Fact[] 330 */ 331 private static function parentFacts(Individual $person, $sosa, Date $min_date, Date $max_date): array 332 { 333 $SHOW_RELATIVES_EVENTS = $person->tree()->getPreference('SHOW_RELATIVES_EVENTS'); 334 335 $facts = []; 336 337 if ($sosa == 1) { 338 foreach ($person->getChildFamilies() as $family) { 339 // Add siblings 340 foreach (self::childFacts($person, $family, '_SIBL', '', $min_date, $max_date) as $fact) { 341 $facts[] = $fact; 342 } 343 foreach ($family->getSpouses() as $spouse) { 344 foreach ($spouse->getSpouseFamilies() as $sfamily) { 345 if ($family !== $sfamily) { 346 // Add half-siblings 347 foreach (self::childFacts($person, $sfamily, '_HSIB', '', $min_date, $max_date) as $fact) { 348 $facts[] = $fact; 349 } 350 } 351 } 352 // Add grandparents 353 foreach (self::parentFacts($spouse, $spouse->getSex() == 'F' ? 3 : 2, $min_date, $max_date) as $fact) { 354 $facts[] = $fact; 355 } 356 } 357 } 358 359 if (strstr($SHOW_RELATIVES_EVENTS, '_MARR_PARE')) { 360 // add father/mother marriages 361 foreach ($person->getChildFamilies() as $sfamily) { 362 foreach ($sfamily->facts(['MARR']) as $fact) { 363 if (self::includeFact($fact, $min_date, $max_date)) { 364 // marriage of parents (to each other) 365 $rela_fact = clone($fact); 366 $rela_fact->setTag('_' . $fact->getTag() . '_FAMC'); 367 $facts[] = $rela_fact; 368 } 369 } 370 } 371 foreach ($person->getChildStepFamilies() as $sfamily) { 372 foreach ($sfamily->facts(['MARR']) as $fact) { 373 if (self::includeFact($fact, $min_date, $max_date)) { 374 // marriage of a parent (to another spouse) 375 // Convert the event to a close relatives event 376 $rela_fact = clone($fact); 377 $rela_fact->setTag('_' . $fact->getTag() . '_PARE'); 378 $facts[] = $rela_fact; 379 } 380 } 381 } 382 } 383 } 384 385 foreach ($person->getChildFamilies() as $family) { 386 foreach ($family->getSpouses() as $parent) { 387 if (strstr($SHOW_RELATIVES_EVENTS, '_DEAT' . ($sosa == 1 ? '_PARE' : '_GPAR'))) { 388 foreach ($parent->facts(Gedcom::DEATH_EVENTS) as $fact) { 389 if (self::includeFact($fact, $min_date, $max_date)) { 390 switch ($sosa) { 391 case 1: 392 // Convert the event to a close relatives event. 393 $rela_fact = clone($fact); 394 $rela_fact->setTag('_' . $fact->getTag() . '_PARE'); 395 $facts[] = $rela_fact; 396 break; 397 case 2: 398 // Convert the event to a close relatives event 399 $rela_fact = clone($fact); 400 $rela_fact->setTag('_' . $fact->getTag() . '_GPA1'); 401 $facts[] = $rela_fact; 402 break; 403 case 3: 404 // Convert the event to a close relatives event 405 $rela_fact = clone($fact); 406 $rela_fact->setTag('_' . $fact->getTag() . '_GPA2'); 407 $facts[] = $rela_fact; 408 break; 409 } 410 } 411 } 412 } 413 } 414 } 415 416 return $facts; 417 } 418 419 /** 420 * Get any historical events. 421 * 422 * @param Individual $person 423 * @param Date $min_date 424 * @param Date $max_date 425 * 426 * @return Fact[] 427 */ 428 private static function historicalFacts(Individual $person, Date $min_date, Date $max_date): array 429 { 430 $facts = []; 431 432 if (file_exists(Site::getPreference('INDEX_DIRECTORY') . 'histo.' . WT_LOCALE . '.php')) { 433 $histo = []; 434 require Site::getPreference('INDEX_DIRECTORY') . 'histo.' . WT_LOCALE . '.php'; 435 foreach ($histo as $hist) { 436 $fact = new Fact($hist, $person, 'histo'); 437 438 if (self::includeFact($fact, $min_date, $max_date)) { 439 $facts[] = $fact; 440 } 441 } 442 } 443 444 return $facts; 445 } 446 447 /** 448 * Get the events of associates. 449 * 450 * @param Individual $person 451 * 452 * @return Fact[] 453 */ 454 private static function associateFacts(Individual $person): array 455 { 456 $facts = []; 457 458 /** @var Individual[] $associates */ 459 $associates = array_merge( 460 $person->linkedIndividuals('ASSO'), 461 $person->linkedIndividuals('_ASSO'), 462 $person->linkedFamilies('ASSO'), 463 $person->linkedFamilies('_ASSO') 464 ); 465 foreach ($associates as $associate) { 466 foreach ($associate->facts() as $fact) { 467 $arec = $fact->attribute('_ASSO'); 468 if (!$arec) { 469 $arec = $fact->attribute('ASSO'); 470 } 471 if ($arec && trim($arec, '@') === $person->xref()) { 472 // Extract the important details from the fact 473 $factrec = '1 ' . $fact->getTag(); 474 if (preg_match('/\n2 DATE .*/', $fact->gedcom(), $match)) { 475 $factrec .= $match[0]; 476 } 477 if (preg_match('/\n2 PLAC .*/', $fact->gedcom(), $match)) { 478 $factrec .= $match[0]; 479 } 480 if ($associate instanceof Family) { 481 foreach ($associate->getSpouses() as $spouse) { 482 $factrec .= "\n2 _ASSO @" . $spouse->xref() . '@'; 483 } 484 } else { 485 $factrec .= "\n2 _ASSO @" . $associate->xref() . '@'; 486 } 487 $facts[] = new Fact($factrec, $associate, 'asso'); 488 } 489 } 490 } 491 492 return $facts; 493 } 494} 495