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