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