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