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 $fact_date = $fact->getDate(); 145 146 return $fact_date->isOK() && Date::compare($min_date, $fact_date) <= 0 && Date::compare($fact_date, $max_date) <= 0; 147 } 148 149 /** {@inheritdoc} */ 150 public function hasTabContent(Individual $individual): bool 151 { 152 return true; 153 } 154 155 /** {@inheritdoc} */ 156 public function canLoadAjax(): bool 157 { 158 return false; 159 } 160 161 /** 162 * Spouse facts that are shown on an individual’s page. 163 * 164 * @param Individual $individual Show events that occured during the lifetime of this individual 165 * @param Individual $spouse Show events of this individual 166 * @param Date $min_date 167 * @param Date $max_date 168 * 169 * @return Fact[] 170 */ 171 private static function spouseFacts(Individual $individual, Individual $spouse, Date $min_date, Date $max_date): array 172 { 173 $SHOW_RELATIVES_EVENTS = $individual->getTree()->getPreference('SHOW_RELATIVES_EVENTS'); 174 175 $facts = []; 176 if (strstr($SHOW_RELATIVES_EVENTS, '_DEAT_SPOU')) { 177 foreach ($spouse->getFacts(WT_EVENTS_DEAT) as $fact) { 178 if (self::includeFact($fact, $min_date, $max_date)) { 179 // Convert the event to a close relatives event. 180 $rela_fact = clone($fact); 181 $rela_fact->setTag('_' . $fact->getTag() . '_SPOU'); 182 $facts[] = $rela_fact; 183 } 184 } 185 } 186 187 return $facts; 188 } 189 190 /** 191 * Get the events of children and grandchildren. 192 * 193 * @param Individual $person 194 * @param Family $family 195 * @param string $option 196 * @param string $relation 197 * @param Date $min_date 198 * @param Date $max_date 199 * 200 * @return Fact[] 201 */ 202 private static function childFacts(Individual $person, Family $family, $option, $relation, Date $min_date, Date $max_date): array 203 { 204 $SHOW_RELATIVES_EVENTS = $person->getTree()->getPreference('SHOW_RELATIVES_EVENTS'); 205 206 $facts = []; 207 208 // Deal with recursion. 209 switch ($option) { 210 case '_CHIL': 211 // Add grandchildren 212 foreach ($family->getChildren() as $child) { 213 foreach ($child->getSpouseFamilies() as $cfamily) { 214 switch ($child->getSex()) { 215 case 'M': 216 foreach (self::childFacts($person, $cfamily, '_GCHI', 'son', $min_date, $max_date) as $fact) { 217 $facts[] = $fact; 218 } 219 break; 220 case 'F': 221 foreach (self::childFacts($person, $cfamily, '_GCHI', 'dau', $min_date, $max_date) as $fact) { 222 $facts[] = $fact; 223 } 224 break; 225 default: 226 foreach (self::childFacts($person, $cfamily, '_GCHI', 'chi', $min_date, $max_date) as $fact) { 227 $facts[] = $fact; 228 } 229 break; 230 } 231 } 232 } 233 break; 234 } 235 236 // For each child in the family 237 foreach ($family->getChildren() as $child) { 238 if ($child->getXref() == $person->getXref()) { 239 // We are not our own sibling! 240 continue; 241 } 242 // add child’s birth 243 if (strpos($SHOW_RELATIVES_EVENTS, '_BIRT' . str_replace('_HSIB', '_SIBL', $option)) !== false) { 244 foreach ($child->getFacts(WT_EVENTS_BIRT) as $fact) { 245 // Always show _BIRT_CHIL, even if the dates are not known 246 if ($option == '_CHIL' || self::includeFact($fact, $min_date, $max_date)) { 247 if ($option == '_GCHI' && $relation == 'dau') { 248 // Convert the event to a close relatives event. 249 $rela_fact = clone($fact); 250 $rela_fact->setTag('_' . $fact->getTag() . '_GCH1'); 251 $facts[] = $rela_fact; 252 } elseif ($option == '_GCHI' && $relation == 'son') { 253 // Convert the event to a close relatives event. 254 $rela_fact = clone($fact); 255 $rela_fact->setTag('_' . $fact->getTag() . '_GCH2'); 256 $facts[] = $rela_fact; 257 } else { 258 // Convert the event to a close relatives event. 259 $rela_fact = clone($fact); 260 $rela_fact->setTag('_' . $fact->getTag() . $option); 261 $facts[] = $rela_fact; 262 } 263 } 264 } 265 } 266 // add child’s death 267 if (strpos($SHOW_RELATIVES_EVENTS, '_DEAT' . str_replace('_HSIB', '_SIBL', $option)) !== false) { 268 foreach ($child->getFacts(WT_EVENTS_DEAT) as $fact) { 269 if (self::includeFact($fact, $min_date, $max_date)) { 270 if ($option == '_GCHI' && $relation == 'dau') { 271 // Convert the event to a close relatives event. 272 $rela_fact = clone($fact); 273 $rela_fact->setTag('_' . $fact->getTag() . '_GCH1'); 274 $facts[] = $rela_fact; 275 } elseif ($option == '_GCHI' && $relation == 'son') { 276 // Convert the event to a close relatives event. 277 $rela_fact = clone($fact); 278 $rela_fact->setTag('_' . $fact->getTag() . '_GCH2'); 279 $facts[] = $rela_fact; 280 } else { 281 // Convert the event to a close relatives event. 282 $rela_fact = clone($fact); 283 $rela_fact->setTag('_' . $fact->getTag() . $option); 284 $facts[] = $rela_fact; 285 } 286 } 287 } 288 } 289 // add child’s marriage 290 if (strstr($SHOW_RELATIVES_EVENTS, '_MARR' . str_replace('_HSIB', '_SIBL', $option))) { 291 foreach ($child->getSpouseFamilies() as $sfamily) { 292 foreach ($sfamily->getFacts('MARR') as $fact) { 293 if (self::includeFact($fact, $min_date, $max_date)) { 294 if ($option == '_GCHI' && $relation == 'dau') { 295 // Convert the event to a close relatives event. 296 $rela_fact = clone($fact); 297 $rela_fact->setTag('_' . $fact->getTag() . '_GCH1'); 298 $facts[] = $rela_fact; 299 } elseif ($option == '_GCHI' && $relation == 'son') { 300 // Convert the event to a close relatives event. 301 $rela_fact = clone($fact); 302 $rela_fact->setTag('_' . $fact->getTag() . '_GCH2'); 303 $facts[] = $rela_fact; 304 } else { 305 // Convert the event to a close relatives event. 306 $rela_fact = clone($fact); 307 $rela_fact->setTag('_' . $fact->getTag() . $option); 308 $facts[] = $rela_fact; 309 } 310 } 311 } 312 } 313 } 314 } 315 316 return $facts; 317 } 318 319 /** 320 * Get the events of parents and grandparents. 321 * 322 * @param Individual $person 323 * @param int $sosa 324 * @param Date $min_date 325 * @param Date $max_date 326 * 327 * @return Fact[] 328 */ 329 private static function parentFacts(Individual $person, $sosa, Date $min_date, Date $max_date): array 330 { 331 $SHOW_RELATIVES_EVENTS = $person->getTree()->getPreference('SHOW_RELATIVES_EVENTS'); 332 333 $facts = []; 334 335 if ($sosa == 1) { 336 foreach ($person->getChildFamilies() as $family) { 337 // Add siblings 338 foreach (self::childFacts($person, $family, '_SIBL', '', $min_date, $max_date) as $fact) { 339 $facts[] = $fact; 340 } 341 foreach ($family->getSpouses() as $spouse) { 342 foreach ($spouse->getSpouseFamilies() as $sfamily) { 343 if ($family !== $sfamily) { 344 // Add half-siblings 345 foreach (self::childFacts($person, $sfamily, '_HSIB', '', $min_date, $max_date) as $fact) { 346 $facts[] = $fact; 347 } 348 } 349 } 350 // Add grandparents 351 foreach (self::parentFacts($spouse, $spouse->getSex() == 'F' ? 3 : 2, $min_date, $max_date) as $fact) { 352 $facts[] = $fact; 353 } 354 } 355 } 356 357 if (strstr($SHOW_RELATIVES_EVENTS, '_MARR_PARE')) { 358 // add father/mother marriages 359 foreach ($person->getChildFamilies() as $sfamily) { 360 foreach ($sfamily->getFacts('MARR') as $fact) { 361 if (self::includeFact($fact, $min_date, $max_date)) { 362 // marriage of parents (to each other) 363 $rela_fact = clone($fact); 364 $rela_fact->setTag('_' . $fact->getTag() . '_FAMC'); 365 $facts[] = $rela_fact; 366 } 367 } 368 } 369 foreach ($person->getChildStepFamilies() as $sfamily) { 370 foreach ($sfamily->getFacts('MARR') as $fact) { 371 if (self::includeFact($fact, $min_date, $max_date)) { 372 // marriage of a parent (to another spouse) 373 // Convert the event to a close relatives event 374 $rela_fact = clone($fact); 375 $rela_fact->setTag('_' . $fact->getTag() . '_PARE'); 376 $facts[] = $rela_fact; 377 } 378 } 379 } 380 } 381 } 382 383 foreach ($person->getChildFamilies() as $family) { 384 foreach ($family->getSpouses() as $parent) { 385 if (strstr($SHOW_RELATIVES_EVENTS, '_DEAT' . ($sosa == 1 ? '_PARE' : '_GPAR'))) { 386 foreach ($parent->getFacts(WT_EVENTS_DEAT) as $fact) { 387 if (self::includeFact($fact, $min_date, $max_date)) { 388 switch ($sosa) { 389 case 1: 390 // Convert the event to a close relatives event. 391 $rela_fact = clone($fact); 392 $rela_fact->setTag('_' . $fact->getTag() . '_PARE'); 393 $facts[] = $rela_fact; 394 break; 395 case 2: 396 // Convert the event to a close relatives event 397 $rela_fact = clone($fact); 398 $rela_fact->setTag('_' . $fact->getTag() . '_GPA1'); 399 $facts[] = $rela_fact; 400 break; 401 case 3: 402 // Convert the event to a close relatives event 403 $rela_fact = clone($fact); 404 $rela_fact->setTag('_' . $fact->getTag() . '_GPA2'); 405 $facts[] = $rela_fact; 406 break; 407 } 408 } 409 } 410 } 411 } 412 } 413 414 return $facts; 415 } 416 417 /** 418 * Get any historical events. 419 * 420 * @param Individual $person 421 * @param Date $min_date 422 * @param Date $max_date 423 * 424 * @return Fact[] 425 */ 426 private static function historicalFacts(Individual $person, Date $min_date, Date $max_date): array 427 { 428 $SHOW_RELATIVES_EVENTS = $person->getTree()->getPreference('SHOW_RELATIVES_EVENTS'); 429 430 $facts = []; 431 432 if ($SHOW_RELATIVES_EVENTS) { 433 if (file_exists(Site::getPreference('INDEX_DIRECTORY') . 'histo.' . WT_LOCALE . '.php')) { 434 $histo = []; 435 require Site::getPreference('INDEX_DIRECTORY') . 'histo.' . WT_LOCALE . '.php'; 436 foreach ($histo as $hist) { 437 $fact = new Fact($hist, $person, 'histo'); 438 439 if (self::includeFact($fact, $min_date, $max_date)) { 440 $facts[] = $fact; 441 } 442 } 443 } 444 } 445 446 return $facts; 447 } 448 449 /** 450 * Get the events of associates. 451 * 452 * @param Individual $person 453 * 454 * @return Fact[] 455 */ 456 private static function associateFacts(Individual $person): array 457 { 458 $facts = []; 459 460 /** @var Individual[] $associates */ 461 $associates = array_merge( 462 $person->linkedIndividuals('ASSO'), 463 $person->linkedIndividuals('_ASSO'), 464 $person->linkedFamilies('ASSO'), 465 $person->linkedFamilies('_ASSO') 466 ); 467 foreach ($associates as $associate) { 468 foreach ($associate->getFacts() as $fact) { 469 $arec = $fact->getAttribute('_ASSO'); 470 if (!$arec) { 471 $arec = $fact->getAttribute('ASSO'); 472 } 473 if ($arec && trim($arec, '@') === $person->getXref()) { 474 // Extract the important details from the fact 475 $factrec = '1 ' . $fact->getTag(); 476 if (preg_match('/\n2 DATE .*/', $fact->getGedcom(), $match)) { 477 $factrec .= $match[0]; 478 } 479 if (preg_match('/\n2 PLAC .*/', $fact->getGedcom(), $match)) { 480 $factrec .= $match[0]; 481 } 482 if ($associate instanceof Family) { 483 foreach ($associate->getSpouses() as $spouse) { 484 $factrec .= "\n2 _ASSO @" . $spouse->getXref() . '@'; 485 } 486 } else { 487 $factrec .= "\n2 _ASSO @" . $associate->getXref() . '@'; 488 } 489 $facts[] = new Fact($factrec, $associate, 'asso'); 490 } 491 } 492 } 493 494 return $facts; 495 } 496} 497