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