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