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