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