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