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