1<?php 2namespace Fisharebest\Webtrees\Module; 3 4/** 5 * webtrees: online genealogy 6 * Copyright (C) 2015 webtrees development team 7 * This program is free software: you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License as published by 9 * the Free Software Foundation, either version 3 of the License, or 10 * (at your option) any later version. 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 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 = array(); 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 echo '<table class="facts_table">'; 124 echo '<tbody>'; 125 if (!$indifacts) { 126 echo '<tr><td colspan="2" class="facts_value">', I18N::translate('There are no facts for this individual.'), '</td></tr>'; 127 } 128 129 echo '<tr><td colspan="2" class="descriptionbox rela"><form action="?"><input id="checkbox_rela_facts" type="checkbox" '; 130 echo $controller->record->getTree()->getPreference('EXPAND_RELATIVES_EVENTS') ? 'checked' : ''; 131 echo ' onclick="jQuery(\'tr.rela\').toggle();"><label for="checkbox_rela_facts">', I18N::translate('Events of close relatives'), '</label>'; 132 if (file_exists(Site::getPreference('INDEX_DIRECTORY') . 'histo.' . WT_LOCALE . '.php')) { 133 echo ' <input id="checkbox_histo" type="checkbox" '; 134 echo $EXPAND_HISTO_EVENTS ? 'checked' : ''; 135 echo ' onclick="jQuery(\'tr.histo\').toggle();"><label for="checkbox_histo">', I18N::translate('Historical facts'), '</label>'; 136 } 137 echo '</form></td></tr>'; 138 139 foreach ($indifacts as $fact) { 140 FunctionsPrintFacts::printFact($fact, $controller->record); 141 } 142 143 //-- new fact link 144 if ($controller->record->canEdit()) { 145 FunctionsPrint::printAddNewFact($controller->record->getXref(), $indifacts, 'INDI'); 146 } 147 echo '</tbody>'; 148 echo '</table>'; 149 150 if (!$controller->record->getTree()->getPreference('EXPAND_RELATIVES_EVENTS')) { 151 echo '<script>jQuery("tr.rela").toggle();</script>'; 152 } 153 if (!$EXPAND_HISTO_EVENTS) { 154 echo '<script>jQuery("tr.histo").toggle();</script>'; 155 } 156 157 return '<div id="' . $this->getName() . '_content">' . ob_get_clean() . '</div>'; 158 } 159 160 /** {@inheritdoc} */ 161 public function hasTabContent() { 162 return true; 163 } 164 165 /** {@inheritdoc} */ 166 public function canLoadAjax() { 167 return !Auth::isSearchEngine(); // Search engines cannot use AJAX 168 } 169 170 /** {@inheritdoc} */ 171 public function getPreLoadContent() { 172 return ''; 173 } 174 175 /** 176 * Spouse facts that are shown on an individual’s page. 177 * 178 * @param Individual $individual Show events that occured during the lifetime of this individual 179 * @param Individual $spouse Show events of this individual 180 * 181 * @return Fact[] 182 */ 183 private static function spouseFacts(Individual $individual, Individual $spouse) { 184 $SHOW_RELATIVES_EVENTS = $individual->getTree()->getPreference('SHOW_RELATIVES_EVENTS'); 185 186 $facts = array(); 187 if (strstr($SHOW_RELATIVES_EVENTS, '_DEAT_SPOU')) { 188 // Only include events between birth and death 189 $birt_date = $individual->getEstimatedBirthDate(); 190 $deat_date = $individual->getEstimatedDeathDate(); 191 192 foreach ($spouse->getFacts(WT_EVENTS_DEAT) as $fact) { 193 194 $fact_date = $fact->getDate(); 195 if ($fact_date->isOK() && Date::compare($birt_date, $fact_date) <= 0 && Date::compare($fact_date, $deat_date) <= 0) { 196 // Convert the event to a close relatives event. 197 $rela_fact = clone($fact); 198 $rela_fact->setTag('_' . $fact->getTag() . '_SPOU'); 199 $facts[] = $rela_fact; 200 } 201 } 202 } 203 204 return $facts; 205 } 206 207 /** 208 * Get the events of children and grandchildren. 209 * 210 * @param Individual $person 211 * @param Family $family 212 * @param string $option 213 * @param string $relation 214 * 215 * @return Fact[] 216 */ 217 private static function childFacts(Individual $person, Family $family, $option, $relation) { 218 global $controller; 219 220 $SHOW_RELATIVES_EVENTS = $person->getTree()->getPreference('SHOW_RELATIVES_EVENTS'); 221 222 $facts = array(); 223 224 // Only include events between birth and death 225 $birt_date = $controller->record->getEstimatedBirthDate(); 226 $deat_date = $controller->record->getEstimatedDeathDate(); 227 228 // Deal with recursion. 229 switch ($option) { 230 case '_CHIL': 231 // Add grandchildren 232 foreach ($family->getChildren() as $child) { 233 foreach ($child->getSpouseFamilies() as $cfamily) { 234 switch ($child->getSex()) { 235 case 'M': 236 foreach (self::childFacts($person, $cfamily, '_GCHI', 'son') as $fact) { 237 $facts[] = $fact; 238 } 239 break; 240 case 'F': 241 foreach (self::childFacts($person, $cfamily, '_GCHI', 'dau') as $fact) { 242 $facts[] = $fact; 243 } 244 break; 245 default: 246 foreach (self::childFacts($person, $cfamily, '_GCHI', 'chi') as $fact) { 247 $facts[] = $fact; 248 } 249 break; 250 } 251 } 252 } 253 break; 254 } 255 256 // For each child in the family 257 foreach ($family->getChildren() as $child) { 258 if ($child->getXref() == $person->getXref()) { 259 // We are not our own sibling! 260 continue; 261 } 262 // add child’s birth 263 if (strpos($SHOW_RELATIVES_EVENTS, '_BIRT' . str_replace('_HSIB', '_SIBL', $option)) !== false) { 264 foreach ($child->getFacts(WT_EVENTS_BIRT) as $fact) { 265 $sgdate = $fact->getDate(); 266 // Always show _BIRT_CHIL, even if the dates are not known 267 if ($option == '_CHIL' || $sgdate->isOK() && Date::compare($birt_date, $sgdate) <= 0 && Date::compare($sgdate, $deat_date) <= 0) { 268 if ($option == '_GCHI' && $relation == 'dau') { 269 // Convert the event to a close relatives event. 270 $rela_fact = clone($fact); 271 $rela_fact->setTag('_' . $fact->getTag() . '_GCH1'); 272 $facts[] = $rela_fact; 273 } elseif ($option == '_GCHI' && $relation == 'son') { 274 // Convert the event to a close relatives event. 275 $rela_fact = clone($fact); 276 $rela_fact->setTag('_' . $fact->getTag() . '_GCH2'); 277 $facts[] = $rela_fact; 278 } else { 279 // Convert the event to a close relatives event. 280 $rela_fact = clone($fact); 281 $rela_fact->setTag('_' . $fact->getTag() . $option); 282 $facts[] = $rela_fact; 283 } 284 } 285 } 286 } 287 // add child’s death 288 if (strpos($SHOW_RELATIVES_EVENTS, '_DEAT' . str_replace('_HSIB', '_SIBL', $option)) !== false) { 289 foreach ($child->getFacts(WT_EVENTS_DEAT) as $fact) { 290 $sgdate = $fact->getDate(); 291 if ($sgdate->isOK() && Date::compare($birt_date, $sgdate) <= 0 && Date::compare($sgdate, $deat_date) <= 0) { 292 if ($option == '_GCHI' && $relation == 'dau') { 293 // Convert the event to a close relatives event. 294 $rela_fact = clone($fact); 295 $rela_fact->setTag('_' . $fact->getTag() . '_GCH1'); 296 $facts[] = $rela_fact; 297 } elseif ($option == '_GCHI' && $relation == 'son') { 298 // Convert the event to a close relatives event. 299 $rela_fact = clone($fact); 300 $rela_fact->setTag('_' . $fact->getTag() . '_GCH2'); 301 $facts[] = $rela_fact; 302 } else { 303 // Convert the event to a close relatives event. 304 $rela_fact = clone($fact); 305 $rela_fact->setTag('_' . $fact->getTag() . $option); 306 $facts[] = $rela_fact; 307 } 308 } 309 } 310 } 311 // add child’s marriage 312 if (strstr($SHOW_RELATIVES_EVENTS, '_MARR' . str_replace('_HSIB', '_SIBL', $option))) { 313 foreach ($child->getSpouseFamilies() as $sfamily) { 314 foreach ($sfamily->getFacts(WT_EVENTS_MARR) as $fact) { 315 $sgdate = $fact->getDate(); 316 if ($sgdate->isOK() && Date::compare($birt_date, $sgdate) <= 0 && Date::compare($sgdate, $deat_date) <= 0) { 317 if ($option == '_GCHI' && $relation == 'dau') { 318 // Convert the event to a close relatives event. 319 $rela_fact = clone($fact); 320 $rela_fact->setTag('_' . $fact->getTag() . '_GCH1'); 321 $facts[] = $rela_fact; 322 } elseif ($option == '_GCHI' && $relation == 'son') { 323 // Convert the event to a close relatives event. 324 $rela_fact = clone($fact); 325 $rela_fact->setTag('_' . $fact->getTag() . '_GCH2'); 326 $facts[] = $rela_fact; 327 } else { 328 // Convert the event to a close relatives event. 329 $rela_fact = clone($fact); 330 $rela_fact->setTag('_' . $fact->getTag() . $option); 331 $facts[] = $rela_fact; 332 } 333 } 334 } 335 } 336 } 337 } 338 339 return $facts; 340 } 341 342 /** 343 * Get the events of parents and grandparents. 344 * 345 * @param Individual $person 346 * @param int $sosa 347 * 348 * @return Fact[] 349 */ 350 private static function parentFacts(Individual $person, $sosa) { 351 global $controller; 352 353 $SHOW_RELATIVES_EVENTS = $person->getTree()->getPreference('SHOW_RELATIVES_EVENTS'); 354 355 $facts = array(); 356 357 // Only include events between birth and death 358 $birt_date = $controller->record->getEstimatedBirthDate(); 359 $deat_date = $controller->record->getEstimatedDeathDate(); 360 361 if ($sosa == 1) { 362 foreach ($person->getChildFamilies() as $family) { 363 // Add siblings 364 foreach (self::childFacts($person, $family, '_SIBL', '') as $fact) { 365 $facts[] = $fact; 366 } 367 foreach ($family->getSpouses() as $spouse) { 368 foreach ($spouse->getSpouseFamilies() as $sfamily) { 369 if ($family !== $sfamily) { 370 // Add half-siblings 371 foreach (self::childFacts($person, $sfamily, '_HSIB', '') as $fact) { 372 $facts[] = $fact; 373 } 374 } 375 } 376 // Add grandparents 377 foreach (self::parentFacts($spouse, $spouse->getSex() == 'F' ? 3 : 2) as $fact) { 378 $facts[] = $fact; 379 } 380 } 381 } 382 383 if (strstr($SHOW_RELATIVES_EVENTS, '_MARR_PARE')) { 384 // add father/mother marriages 385 foreach ($person->getChildFamilies() as $sfamily) { 386 foreach ($sfamily->getFacts(WT_EVENTS_MARR) as $fact) { 387 if ($fact->getDate()->isOK() && Date::compare($birt_date, $fact->getDate()) <= 0 && Date::compare($fact->getDate(), $deat_date) <= 0) { 388 // marriage of parents (to each other) 389 $rela_fact = clone($fact); 390 $rela_fact->setTag('_' . $fact->getTag() . '_FAMC'); 391 $facts[] = $rela_fact; 392 } 393 } 394 } 395 foreach ($person->getChildStepFamilies() as $sfamily) { 396 foreach ($sfamily->getFacts(WT_EVENTS_MARR) as $fact) { 397 if ($fact->getDate()->isOK() && Date::compare($birt_date, $fact->getDate()) <= 0 && Date::compare($fact->getDate(), $deat_date) <= 0) { 398 // marriage of a parent (to another spouse) 399 // Convert the event to a close relatives event 400 $rela_fact = clone($fact); 401 $rela_fact->setTag('_' . $fact->getTag() . '_PARE'); 402 $facts[] = $rela_fact; 403 } 404 } 405 } 406 } 407 } 408 409 foreach ($person->getChildFamilies() as $family) { 410 foreach ($family->getSpouses() as $parent) { 411 if (strstr($SHOW_RELATIVES_EVENTS, '_DEAT' . ($sosa == 1 ? '_PARE' : '_GPAR'))) { 412 foreach ($parent->getFacts(WT_EVENTS_DEAT) as $fact) { 413 if ($fact->getDate()->isOK() && Date::compare($birt_date, $fact->getDate()) <= 0 && Date::compare($fact->getDate(), $deat_date) <= 0) { 414 switch ($sosa) { 415 case 1: 416 // Convert the event to a close relatives event. 417 $rela_fact = clone($fact); 418 $rela_fact->setTag('_' . $fact->getTag() . '_PARE'); 419 $facts[] = $rela_fact; 420 break; 421 case 2: 422 // Convert the event to a close relatives event 423 $rela_fact = clone($fact); 424 $rela_fact->setTag('_' . $fact->getTag() . '_GPA1'); 425 $facts[] = $rela_fact; 426 break; 427 case 3: 428 // Convert the event to a close relatives event 429 $rela_fact = clone($fact); 430 $rela_fact->setTag('_' . $fact->getTag() . '_GPA2'); 431 $facts[] = $rela_fact; 432 break; 433 } 434 } 435 } 436 } 437 } 438 } 439 440 return $facts; 441 } 442 443 /** 444 * Get any historical events. 445 * 446 * @param Individual $person 447 * 448 * @return Fact[] 449 */ 450 private static function historicalFacts(Individual $person) { 451 $SHOW_RELATIVES_EVENTS = $person->getTree()->getPreference('SHOW_RELATIVES_EVENTS'); 452 453 $facts = array(); 454 455 if ($SHOW_RELATIVES_EVENTS) { 456 // Only include events between birth and death 457 $birt_date = $person->getEstimatedBirthDate(); 458 $deat_date = $person->getEstimatedDeathDate(); 459 460 if (file_exists(Site::getPreference('INDEX_DIRECTORY') . 'histo.' . WT_LOCALE . '.php')) { 461 $histo = array(); 462 require Site::getPreference('INDEX_DIRECTORY') . 'histo.' . WT_LOCALE . '.php'; 463 foreach ($histo as $hist) { 464 // Earlier versions of the WIKI encouraged people to use HTML entities, 465 // rather than UTF8 encoding. 466 $hist = html_entity_decode($hist, ENT_QUOTES, 'UTF-8'); 467 468 $fact = new Fact($hist, $person, 'histo'); 469 $sdate = $fact->getDate(); 470 if ($sdate->isOK() && Date::compare($birt_date, $sdate) <= 0 && Date::compare($sdate, $deat_date) <= 0) { 471 $facts[] = $fact; 472 } 473 } 474 } 475 } 476 477 return $facts; 478 } 479 480 /** 481 * Get the events of associates. 482 * 483 * @param Individual $person 484 * 485 * @return Fact[] 486 */ 487 private static function associateFacts(Individual $person) { 488 $facts = array(); 489 490 $associates = array_merge( 491 $person->linkedIndividuals('ASSO'), 492 $person->linkedIndividuals('_ASSO'), 493 $person->linkedFamilies('ASSO'), 494 $person->linkedFamilies('_ASSO') 495 ); 496 foreach ($associates as $associate) { 497 foreach ($associate->getFacts() as $fact) { 498 $arec = $fact->getAttribute('_ASSO'); 499 if (!$arec) { 500 $arec = $fact->getAttribute('ASSO'); 501 } 502 if ($arec && trim($arec, '@') === $person->getXref()) { 503 // Extract the important details from the fact 504 $factrec = '1 ' . $fact->getTag(); 505 if (preg_match('/\n2 DATE .*/', $fact->getGedcom(), $match)) { 506 $factrec .= $match[0]; 507 } 508 if (preg_match('/\n2 PLAC .*/', $fact->getGedcom(), $match)) { 509 $factrec .= $match[0]; 510 } 511 if ($associate instanceof Family) { 512 foreach ($associate->getSpouses() as $spouse) { 513 $factrec .= "\n2 _ASSO @" . $spouse->getXref() . '@'; 514 } 515 } else { 516 $factrec .= "\n2 _ASSO @" . $associate->getXref() . '@'; 517 // CHR/BAPM events are commonly used. Generate the reverse relationship 518 if (preg_match('/^(?:BAPM|CHR)$/', $fact->getTag()) && preg_match('/2 _?ASSO @(' . $person->getXref() . ')@\n3 RELA god(?:parent|mother|father)/', $fact->getGedcom())) { 519 switch ($associate->getSex()) { 520 case 'M': 521 $factrec .= "\n3 RELA godson"; 522 break; 523 case 'F': 524 $factrec .= "\n3 RELA goddaughter"; 525 break; 526 default: 527 $factrec .= "\n3 RELA godchild"; 528 break; 529 } 530 } 531 } 532 $facts[] = new Fact($factrec, $associate, 'asso'); 533 } 534 } 535 } 536 537 return $facts; 538 } 539} 540