1<?php 2/** 3 * webtrees: online genealogy 4 * Copyright (C) 2019 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\Functions; 17 18use Fisharebest\Webtrees\Config; 19use Fisharebest\Webtrees\Controller\SearchController; 20use Fisharebest\Webtrees\Date; 21use Fisharebest\Webtrees\Fact; 22use Fisharebest\Webtrees\Family; 23use Fisharebest\Webtrees\Filter; 24use Fisharebest\Webtrees\GedcomCode\GedcomCodeStat; 25use Fisharebest\Webtrees\GedcomCode\GedcomCodeTemp; 26use Fisharebest\Webtrees\GedcomRecord; 27use Fisharebest\Webtrees\GedcomTag; 28use Fisharebest\Webtrees\I18N; 29use Fisharebest\Webtrees\Individual; 30use Fisharebest\Webtrees\Module; 31use Fisharebest\Webtrees\Module\CensusAssistantModule; 32use Fisharebest\Webtrees\Note; 33use Fisharebest\Webtrees\Place; 34use Fisharebest\Webtrees\Session; 35use Fisharebest\Webtrees\Theme; 36use Fisharebest\Webtrees\Tree; 37use Rhumsaa\Uuid\Uuid; 38 39/** 40 * Class FunctionsPrint - common functions 41 */ 42class FunctionsPrint 43{ 44 /** 45 * print the information for an individual chart box 46 * 47 * find and print a given individuals information for a pedigree chart 48 * 49 * @param Individual $person The person to print 50 * @param int $show_full The style to print the box in, 0 for smaller boxes, 1 for larger boxes 51 */ 52 public static function printPedigreePerson(Individual $person = null, $show_full = 1) 53 { 54 switch ($show_full) { 55 case 0: 56 if ($person) { 57 echo Theme::theme()->individualBoxSmall($person); 58 } else { 59 echo Theme::theme()->individualBoxSmallEmpty(); 60 } 61 break; 62 case 1: 63 if ($person) { 64 echo Theme::theme()->individualBox($person); 65 } else { 66 echo Theme::theme()->individualBoxEmpty(); 67 } 68 break; 69 } 70 } 71 72 /** 73 * print a note record 74 * 75 * @param string $text 76 * @param int $nlevel the level of the note record 77 * @param string $nrec the note record to print 78 * @param bool $textOnly Don't print the "Note: " introduction 79 * 80 * @return string 81 */ 82 public static function printNoteRecord($text, $nlevel, $nrec, $textOnly = false) 83 { 84 global $WT_TREE; 85 86 $text .= Functions::getCont($nlevel, $nrec); 87 88 // Check if shared note (we have already checked that it exists) 89 if (preg_match('/^0 @(' . WT_REGEX_XREF . ')@ NOTE/', $nrec, $match)) { 90 $note = Note::getInstance($match[1], $WT_TREE); 91 $label = 'SHARED_NOTE'; 92 // If Census assistant installed, allow it to format the note 93 if (Module::getModuleByName('GEDFact_assistant')) { 94 $html = CensusAssistantModule::formatCensusNote($note); 95 } else { 96 $html = Filter::formatText($note->getNote(), $WT_TREE); 97 } 98 } else { 99 $note = null; 100 $label = 'NOTE'; 101 $html = Filter::formatText($text, $WT_TREE); 102 } 103 104 if ($textOnly) { 105 return strip_tags($text); 106 } 107 108 if (strpos($text, "\n") === false) { 109 // A one-line note? strip the block-level tags, so it displays inline 110 return GedcomTag::getLabelValue($label, strip_tags($html, '<a><strong><em>')); 111 } elseif ($WT_TREE->getPreference('EXPAND_NOTES')) { 112 // A multi-line note, and we're expanding notes by default 113 return GedcomTag::getLabelValue($label, $html); 114 } else { 115 // A multi-line note, with an expand/collapse option 116 $element_id = Uuid::uuid4(); 117 // NOTE: class "note-details" is (currently) used only by some third-party themes 118 if ($note) { 119 $first_line = '<a href="' . $note->getHtmlUrl() . '">' . $note->getFullName() . '</a>'; 120 } else { 121 list($text) = explode("\n", strip_tags($html)); 122 $first_line = strlen($text) > 100 ? mb_substr($text, 0, 100) . I18N::translate('…') : $text; 123 } 124 125 return 126 '<div class="fact_NOTE"><span class="label">' . 127 '<a href="#" onclick="expand_layer(\'' . $element_id . '\'); return false;"><i id="' . $element_id . '_img" class="icon-plus"></i></a> ' . GedcomTag::getLabel($label) . ':</span> ' . '<span id="' . $element_id . '-alt">' . $first_line . '</span>' . 128 '</div>' . 129 '<div class="note-details" id="' . $element_id . '" style="display:none">' . $html . '</div>'; 130 } 131 } 132 133 /** 134 * Print all of the notes in this fact record 135 * 136 * @param string $factrec The factrecord to print the notes from 137 * @param int $level The level of the factrecord 138 * @param bool $textOnly Don't print the "Note: " introduction 139 * 140 * @return string HTML 141 */ 142 public static function printFactNotes($factrec, $level, $textOnly = false) 143 { 144 global $WT_TREE; 145 146 $data = ''; 147 $previous_spos = 0; 148 $nlevel = $level + 1; 149 $ct = preg_match_all("/$level NOTE (.*)/", $factrec, $match, PREG_SET_ORDER); 150 for ($j = 0; $j < $ct; $j++) { 151 $spos1 = strpos($factrec, $match[$j][0], $previous_spos); 152 $spos2 = strpos($factrec . "\n$level", "\n$level", $spos1 + 1); 153 if (!$spos2) { 154 $spos2 = strlen($factrec); 155 } 156 $previous_spos = $spos2; 157 $nrec = substr($factrec, $spos1, $spos2 - $spos1); 158 if (!isset($match[$j][1])) { 159 $match[$j][1] = ''; 160 } 161 if (!preg_match('/@(.*)@/', $match[$j][1], $nmatch)) { 162 $data .= self::printNoteRecord($match[$j][1], $nlevel, $nrec, $textOnly); 163 } else { 164 $note = Note::getInstance($nmatch[1], $WT_TREE); 165 if ($note) { 166 if ($note->canShow()) { 167 $noterec = $note->getGedcom(); 168 $nt = preg_match("/0 @$nmatch[1]@ NOTE (.*)/", $noterec, $n1match); 169 $data .= self::printNoteRecord(($nt > 0) ? $n1match[1] : "", 1, $noterec, $textOnly); 170 if (!$textOnly) { 171 if (strpos($noterec, '1 SOUR') !== false) { 172 $data .= FunctionsPrintFacts::printFactSources($noterec, 1); 173 } 174 } 175 } 176 } else { 177 $data = '<div class="fact_NOTE"><span class="label">' . I18N::translate('Note') . '</span>: <span class="field error">' . $nmatch[1] . '</span></div>'; 178 } 179 } 180 if (!$textOnly) { 181 if (strpos($factrec, "$nlevel SOUR") !== false) { 182 $data .= "<div class=\"indent\">"; 183 $data .= FunctionsPrintFacts::printFactSources($nrec, $nlevel); 184 $data .= "</div>"; 185 } 186 } 187 } 188 189 return $data; 190 } 191 192 /** 193 * Print a link for a popup help window. 194 * 195 * @param string $help_topic 196 * @param string $module 197 * 198 * @return string 199 */ 200 public static function helpLink($help_topic, $module = '') 201 { 202 return '<span class="icon-help" onclick="helpDialog(\'' . $help_topic . '\',\'' . $module . '\'); return false;"> </span>'; 203 } 204 205 /** 206 * Print an external help link to the wiki site. 207 * 208 * @deprecated - nothing should be so complicated that it needs lengthy instructions! 209 * 210 * @param string $topic 211 * 212 * @return string 213 */ 214 public static function wikiHelpLink($topic) 215 { 216 return '<a class="help icon-wiki" href="' . WT_WEBTREES_WIKI . $topic . '" title="' . I18N::translate('webtrees wiki') . '"></a>'; 217 } 218 219 /** 220 * When a user has searched for text, highlight any matches in 221 * the displayed string. 222 * 223 * @param string $string 224 * 225 * @return string 226 */ 227 public static function highlightSearchHits($string) 228 { 229 global $controller; 230 231 if ($controller instanceof SearchController && $controller->query) { 232 // TODO: when a search contains multiple words, we search independently. 233 // e.g. searching for "FOO BAR" will find records containing both FOO and BAR. 234 // However, we only highlight the original search string, not the search terms. 235 // The controller needs to provide its "query_terms" array. 236 $regex = array(); 237 foreach (array($controller->query) as $search_term) { 238 $regex[] = preg_quote($search_term, '/'); 239 } 240 // Match these strings, provided they do not occur inside HTML tags 241 $regex = '(' . implode('|', $regex) . ')(?![^<]*>)'; 242 243 return preg_replace('/' . $regex . '/i', '<span class="search_hit">$1</span>', $string); 244 } else { 245 return $string; 246 } 247 } 248 249 /** 250 * Format age of parents in HTML 251 * 252 * @param Individual $person child 253 * @param Date $birth_date 254 * 255 * @return string HTML 256 */ 257 public static function formatParentsAges(Individual $person, Date $birth_date) 258 { 259 $html = ''; 260 $families = $person->getChildFamilies(); 261 // Multiple sets of parents (e.g. adoption) cause complications, so ignore. 262 if ($birth_date->isOK() && count($families) == 1) { 263 $family = current($families); 264 foreach ($family->getSpouses() as $parent) { 265 if ($parent->getBirthDate()->isOK()) { 266 $sex = $parent->getSexImage(); 267 $age = Date::getAge($parent->getBirthDate(), $birth_date, 2); 268 $deatdate = $parent->getDeathDate(); 269 switch ($parent->getSex()) { 270 case 'F': 271 // Highlight mothers who die in childbirth or shortly afterwards 272 if ($deatdate->isOK() && $deatdate->maximumJulianDay() < $birth_date->minimumJulianDay() + 90) { 273 $html .= ' <span title="' . GedcomTag::getLabel('_DEAT_PARE', $parent) . '" class="parentdeath">' . $sex . $age . '</span>'; 274 } else { 275 $html .= ' <span title="' . I18N::translate('Mother’s age') . '">' . $sex . $age . '</span>'; 276 } 277 break; 278 case 'M': 279 // Highlight fathers who die before the birth 280 if ($deatdate->isOK() && $deatdate->maximumJulianDay() < $birth_date->minimumJulianDay()) { 281 $html .= ' <span title="' . GedcomTag::getLabel('_DEAT_PARE', $parent) . '" class="parentdeath">' . $sex . $age . '</span>'; 282 } else { 283 $html .= ' <span title="' . I18N::translate('Father’s age') . '">' . $sex . $age . '</span>'; 284 } 285 break; 286 default: 287 $html .= ' <span title="' . I18N::translate('Parent’s age') . '">' . $sex . $age . '</span>'; 288 break; 289 } 290 } 291 } 292 if ($html) { 293 $html = '<span class="age">' . $html . '</span>'; 294 } 295 } 296 297 return $html; 298 } 299 300 /** 301 * Print fact DATE/TIME 302 * 303 * @param Fact $event event containing the date/age 304 * @param GedcomRecord $record the person (or couple) whose ages should be printed 305 * @param bool $anchor option to print a link to calendar 306 * @param bool $time option to print TIME value 307 * 308 * @return string 309 */ 310 public static function formatFactDate(Fact $event, GedcomRecord $record, $anchor, $time) 311 { 312 global $pid; 313 314 $factrec = $event->getGedcom(); 315 $html = ''; 316 // Recorded age 317 if (preg_match('/\n2 AGE (.+)/', $factrec, $match)) { 318 $fact_age = $match[1]; 319 } else { 320 $fact_age = ''; 321 } 322 if (preg_match('/\n2 HUSB\n3 AGE (.+)/', $factrec, $match)) { 323 $husb_age = $match[1]; 324 } else { 325 $husb_age = ''; 326 } 327 if (preg_match('/\n2 WIFE\n3 AGE (.+)/', $factrec, $match)) { 328 $wife_age = $match[1]; 329 } else { 330 $wife_age = ''; 331 } 332 333 // Calculated age 334 $fact = $event->getTag(); 335 if (preg_match('/\n2 DATE (.+)/', $factrec, $match)) { 336 $date = new Date($match[1]); 337 $html .= ' ' . $date->display($anchor); 338 // time 339 if ($time && preg_match('/\n3 TIME (.+)/', $factrec, $match)) { 340 $html .= ' – <span class="date">' . $match[1] . '</span>'; 341 } 342 if ($record instanceof Individual) { 343 if ($fact === 'BIRT' && $record->getTree()->getPreference('SHOW_PARENTS_AGE')) { 344 // age of parents at child birth 345 $html .= self::formatParentsAges($record, $date); 346 } elseif ($fact !== 'BIRT' && $fact !== 'CHAN' && $fact !== '_TODO') { 347 // age at event 348 $birth_date = $record->getBirthDate(); 349 // Can't use getDeathDate(), as this also gives BURI/CREM events, which 350 // wouldn't give the correct "days after death" result for people with 351 // no DEAT. 352 $death_event = $record->getFirstFact('DEAT'); 353 if ($death_event) { 354 $death_date = $death_event->getDate(); 355 } else { 356 $death_date = new Date(''); 357 } 358 $ageText = ''; 359 if ((Date::compare($date, $death_date) <= 0 || !$record->isDead()) || $fact == 'DEAT') { 360 // Before death, print age 361 $age = Date::getAgeGedcom($birth_date, $date); 362 // Only show calculated age if it differs from recorded age 363 if ($age != '') { 364 if ( 365 $fact_age != '' && $fact_age != $age || 366 $fact_age == '' && $husb_age == '' && $wife_age == '' || 367 $husb_age != '' && $record->getSex() == 'M' && $husb_age != $age || 368 $wife_age != '' && $record->getSex() == 'F' && $wife_age != $age 369 ) { 370 if ($age != "0d") { 371 $ageText = '(' . I18N::translate('Age') . ' ' . FunctionsDate::getAgeAtEvent($age) . ')'; 372 } 373 } 374 } 375 } 376 if ($fact != 'DEAT' && Date::compare($date, $death_date) >= 0) { 377 // After death, print time since death 378 $age = FunctionsDate::getAgeAtEvent(Date::getAgeGedcom($death_date, $date)); 379 if ($age != '') { 380 if (Date::getAgeGedcom($death_date, $date) == "0d") { 381 $ageText = '(' . I18N::translate('on the date of death') . ')'; 382 } else { 383 $ageText = '(' . $age . ' ' . I18N::translate('after death') . ')'; 384 // Family events which occur after death are probably errors 385 if ($event->getParent() instanceof Family) { 386 $ageText .= '<i class="icon-warning"></i>'; 387 } 388 } 389 } 390 } 391 if ($ageText) { 392 $html .= ' <span class="age">' . $ageText . '</span>'; 393 } 394 } 395 } elseif ($record instanceof Family) { 396 $indi = Individual::getInstance($pid, $record->getTree()); 397 if ($indi) { 398 $birth_date = $indi->getBirthDate(); 399 $death_date = $indi->getDeathDate(); 400 $ageText = ''; 401 if (Date::compare($date, $death_date) <= 0) { 402 $age = Date::getAgeGedcom($birth_date, $date); 403 // Only show calculated age if it differs from recorded age 404 if ($age != '' && $age > 0) { 405 if ( 406 $fact_age != '' && $fact_age != $age || 407 $fact_age == '' && $husb_age == '' && $wife_age == '' || 408 $husb_age != '' && $indi->getSex() == 'M' && $husb_age != $age || 409 $wife_age != '' && $indi->getSex() == 'F' && $wife_age != $age 410 ) { 411 $ageText = '(' . I18N::translate('Age') . ' ' . FunctionsDate::getAgeAtEvent($age) . ')'; 412 } 413 } 414 } 415 if ($ageText) { 416 $html .= ' <span class="age">' . $ageText . '</span>'; 417 } 418 } 419 } 420 } elseif (strpos($factrec, "\n2 PLAC ") === false && in_array($fact, Config::emptyFacts())) { 421 // There is no DATE. If there is also no PLAC, then print "yes" 422 $html .= I18N::translate('yes'); 423 } 424 // print gedcom ages 425 foreach (array(GedcomTag::getLabel('AGE') => $fact_age, GedcomTag::getLabel('HUSB') => $husb_age, GedcomTag::getLabel('WIFE') => $wife_age) as $label => $age) { 426 if ($age != '') { 427 $html .= ' <span class="label">' . $label . ':</span> <span class="age">' . FunctionsDate::getAgeAtEvent($age) . '</span>'; 428 } 429 } 430 431 return $html; 432 } 433 434 /** 435 * print fact PLACe TEMPle STATus 436 * 437 * @param Fact $event gedcom fact record 438 * @param bool $anchor to print a link to placelist 439 * @param bool $sub_records to print place subrecords 440 * @param bool $lds to print LDS TEMPle and STATus 441 * 442 * @return string HTML 443 */ 444 public static function formatFactPlace(Fact $event, $anchor = false, $sub_records = false, $lds = false) 445 { 446 if ($anchor) { 447 // Show the full place name, for facts/events tab 448 $html = '<a href="' . $event->getPlace()->getURL() . '">' . $event->getPlace()->getFullName() . '</a>'; 449 } else { 450 // Abbreviate the place name, for chart boxes 451 return $event->getPlace()->getShortName(); 452 } 453 454 if ($sub_records) { 455 $placerec = Functions::getSubRecord(2, '2 PLAC', $event->getGedcom()); 456 if (!empty($placerec)) { 457 if (preg_match_all('/\n3 (?:_HEB|ROMN) (.+)/', $placerec, $matches)) { 458 foreach ($matches[1] as $match) { 459 $wt_place = new Place($match, $event->getParent()->getTree()); 460 $html .= ' - ' . $wt_place->getFullName(); 461 } 462 } 463 $map_lati = ""; 464 $cts = preg_match('/\d LATI (.*)/', $placerec, $match); 465 if ($cts > 0) { 466 $map_lati = $match[1]; 467 $html .= '<br><span class="label">' . GedcomTag::getLabel('LATI') . ': </span>' . $map_lati; 468 } 469 $map_long = ''; 470 $cts = preg_match('/\d LONG (.*)/', $placerec, $match); 471 if ($cts > 0) { 472 $map_long = $match[1]; 473 $html .= ' <span class="label">' . GedcomTag::getLabel('LONG') . ': </span>' . $map_long; 474 } 475 if ($map_lati && $map_long) { 476 $map_lati = trim(strtr($map_lati, "NSEW,�", " - -. ")); // S5,6789 ==> -5.6789 477 $map_long = trim(strtr($map_long, "NSEW,�", " - -. ")); // E3.456� ==> 3.456 478 $html .= ' <a rel="nofollow" href="https://maps.google.com/maps?q=' . $map_lati . ',' . $map_long . '" class="icon-googlemaps" title="' . I18N::translate('Google Maps™') . '"></a>'; 479 $html .= ' <a rel="nofollow" href="https://www.bing.com/maps/?lvl=15&cp=' . $map_lati . '~' . $map_long . '" class="icon-bing" title="' . I18N::translate('Bing Maps™') . '"></a>'; 480 $html .= ' <a rel="nofollow" href="https://www.openstreetmap.org/#map=15/' . $map_lati . '/' . $map_long . '" class="icon-osm" title="' . I18N::translate('OpenStreetMap™') . '"></a>'; 481 } 482 if (preg_match('/\d NOTE (.*)/', $placerec, $match)) { 483 $html .= '<br>' . self::printFactNotes($placerec, 3); 484 } 485 } 486 } 487 if ($lds) { 488 if (preg_match('/2 TEMP (.*)/', $event->getGedcom(), $match)) { 489 $html .= '<br>' . I18N::translate('LDS temple') . ': ' . GedcomCodeTemp::templeName($match[1]); 490 } 491 if (preg_match('/2 STAT (.*)/', $event->getGedcom(), $match)) { 492 $html .= '<br>' . I18N::translate('Status') . ': ' . GedcomCodeStat::statusName($match[1]); 493 if (preg_match('/3 DATE (.*)/', $event->getGedcom(), $match)) { 494 $date = new Date($match[1]); 495 $html .= ', ' . GedcomTag::getLabel('STAT:DATE') . ': ' . $date->display(); 496 } 497 } 498 } 499 500 return $html; 501 } 502 503 /** 504 * Check for facts that may exist only once for a certain record type. 505 * If the fact already exists in the second array, delete it from the first one. 506 * 507 * @param string[] $uniquefacts 508 * @param Fact[] $recfacts 509 * @param string $type 510 * 511 * @return string[] 512 */ 513 public static function checkFactUnique($uniquefacts, $recfacts, $type) 514 { 515 foreach ($recfacts as $factarray) { 516 $fact = false; 517 if (is_object($factarray)) { 518 $fact = $factarray->getTag(); 519 } else { 520 if ($type === 'SOUR' || $type === 'REPO') { 521 $factrec = $factarray[0]; 522 } 523 if ($type === 'FAM' || $type === 'INDI') { 524 $factrec = $factarray[1]; 525 } 526 527 $ft = preg_match("/1 (\w+)(.*)/", $factrec, $match); 528 if ($ft > 0) { 529 $fact = trim($match[1]); 530 } 531 } 532 if ($fact !== false) { 533 $key = array_search($fact, $uniquefacts); 534 if ($key !== false) { 535 unset($uniquefacts[$key]); 536 } 537 } 538 } 539 540 return $uniquefacts; 541 } 542 543 /** 544 * Print a new fact box on details pages 545 * 546 * @param string $id the id of the person, family, source etc the fact will be added to 547 * @param array $usedfacts an array of facts already used in this record 548 * @param string $type the type of record INDI, FAM, SOUR etc 549 */ 550 public static function printAddNewFact($id, $usedfacts, $type) 551 { 552 global $WT_TREE; 553 554 // -- Add from clipboard 555 if (is_array(Session::get('clipboard'))) { 556 $newRow = true; 557 foreach (array_reverse(Session::get('clipboard'), true) as $fact_id => $fact) { 558 if ($fact["type"] == $type || $fact["type"] == 'all') { 559 if ($newRow) { 560 $newRow = false; 561 echo '<tr class="noprint"><td class="descriptionbox">'; 562 echo I18N::translate('Add from clipboard'), '</td>'; 563 echo '<td class="optionbox wrap"><form method="get" name="newFromClipboard" action="?" onsubmit="return false;">'; 564 echo '<select id="newClipboardFact">'; 565 } 566 echo '<option value="', Filter::escapeHtml($fact_id), '">', GedcomTag::getLabel($fact['fact']); 567 // TODO use the event class to store/parse the clipboard events 568 if (preg_match('/^2 DATE (.+)/m', $fact['factrec'], $match)) { 569 $tmp = new Date($match[1]); 570 echo '; ', $tmp->minimumDate()->format('%Y'); 571 } 572 if (preg_match('/^2 PLAC ([^,\n]+)/m', $fact['factrec'], $match)) { 573 echo '; ', $match[1]; 574 } 575 echo '</option>'; 576 } 577 } 578 if (!$newRow) { 579 echo '</select>'; 580 echo ' <input type="button" value="', /* I18N: A button label. */ I18N::translate('add'), "\" onclick=\"return paste_fact('$id', '#newClipboardFact');\"> "; 581 echo '</form></td></tr>', "\n"; 582 } 583 } 584 585 // -- Add from pick list 586 switch ($type) { 587 case "INDI": 588 $addfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('INDI_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY); 589 $uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('INDI_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY); 590 $quickfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('INDI_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY); 591 break; 592 case "FAM": 593 $addfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('FAM_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY); 594 $uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('FAM_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY); 595 $quickfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('FAM_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY); 596 break; 597 case "SOUR": 598 $addfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('SOUR_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY); 599 $uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('SOUR_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY); 600 $quickfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('SOUR_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY); 601 break; 602 case "NOTE": 603 $addfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('NOTE_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY); 604 $uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('NOTE_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY); 605 $quickfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('NOTE_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY); 606 break; 607 case "REPO": 608 $addfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('REPO_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY); 609 $uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('REPO_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY); 610 $quickfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('REPO_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY); 611 break; 612 default: 613 return; 614 } 615 $addfacts = array_merge(self::checkFactUnique($uniquefacts, $usedfacts, $type), $addfacts); 616 $quickfacts = array_intersect($quickfacts, $addfacts); 617 $translated_addfacts = array(); 618 foreach ($addfacts as $addfact) { 619 $translated_addfacts[$addfact] = GedcomTag::getLabel($addfact); 620 } 621 uasort($translated_addfacts, function ($x, $y) { 622 return I18N::strcasecmp(I18N::translate($x), I18N::translate($y)); 623 }); 624 echo '<tr class="noprint"><td class="descriptionbox">'; 625 echo I18N::translate('Fact or event'); 626 echo '</td>'; 627 echo '<td class="optionbox wrap">'; 628 echo '<form method="get" name="newfactform" action="?" onsubmit="return false;">'; 629 echo '<select id="newfact" name="newfact">'; 630 echo '<option value="" disabled selected>' . I18N::translate('<select>') . '</option>'; 631 foreach ($translated_addfacts as $fact => $fact_name) { 632 echo '<option value="', $fact, '">', $fact_name, '</option>'; 633 } 634 if ($type == 'INDI' || $type == 'FAM') { 635 echo '<option value="FACT">', I18N::translate('Custom fact'), '</option>'; 636 echo '<option value="EVEN">', I18N::translate('Custom event'), '</option>'; 637 } 638 echo '</select>'; 639 echo '<input type="button" value="', /* I18N: A button label. */ I18N::translate('add'), '" onclick="add_record(\'' . $id . '\', \'newfact\');">'; 640 echo '<span class="quickfacts">'; 641 foreach ($quickfacts as $fact) { 642 echo '<a href="#" onclick="add_new_record(\'' . $id . '\', \'' . $fact . '\');return false;">', GedcomTag::getLabel($fact), '</a>'; 643 } 644 echo '</span></form>'; 645 echo '</td></tr>'; 646 } 647 648 /** 649 * javascript declaration for calendar popup 650 */ 651 public static function initializeCalendarPopup() 652 { 653 global $controller; 654 655 $controller->addInlineJavascript(' 656 cal_setMonthNames( 657 "' . I18N::translateContext('NOMINATIVE', 'January') . '", 658 "' . I18N::translateContext('NOMINATIVE', 'February') . '", 659 "' . I18N::translateContext('NOMINATIVE', 'March') . '", 660 "' . I18N::translateContext('NOMINATIVE', 'April') . '", 661 "' . I18N::translateContext('NOMINATIVE', 'May') . '", 662 "' . I18N::translateContext('NOMINATIVE', 'June') . '", 663 "' . I18N::translateContext('NOMINATIVE', 'July') . '", 664 "' . I18N::translateContext('NOMINATIVE', 'August') . '", 665 "' . I18N::translateContext('NOMINATIVE', 'September') . '", 666 "' . I18N::translateContext('NOMINATIVE', 'October') . '", 667 "' . I18N::translateContext('NOMINATIVE', 'November') . '", 668 "' . I18N::translateContext('NOMINATIVE', 'December') . '" 669 ) 670 cal_setDayHeaders( 671 "' . I18N::translate('Sun') . '", 672 "' . I18N::translate('Mon') . '", 673 "' . I18N::translate('Tue') . '", 674 "' . I18N::translate('Wed') . '", 675 "' . I18N::translate('Thu') . '", 676 "' . I18N::translate('Fri') . '", 677 "' . I18N::translate('Sat') . '" 678 ) 679 cal_setWeekStart(' . I18N::firstDay() . '); 680 '); 681 } 682 683 /** 684 * HTML link to find an individual. 685 * 686 * @param string $element_id 687 * @param string $indiname 688 * @param Tree $tree 689 * 690 * @return string 691 */ 692 public static function printFindIndividualLink($element_id, $indiname = '', $tree = null) 693 { 694 global $WT_TREE; 695 696 if ($tree === null) { 697 $tree = $WT_TREE; 698 } 699 700 return '<a href="#" onclick="findIndi(document.getElementById(\'' . $element_id . '\'), document.getElementById(\'' . $indiname . '\'), \'' . $tree->getNameHtml() . '\'); return false;" class="icon-button_indi" title="' . I18N::translate('Find an individual') . '"></a>'; 701 } 702 703 /** 704 * HTML link to find a place. 705 * 706 * @param string $element_id 707 * 708 * @return string 709 */ 710 public static function printFindPlaceLink($element_id) 711 { 712 return '<a href="#" onclick="findPlace(document.getElementById(\'' . $element_id . '\'), WT_GEDCOM); return false;" class="icon-button_place" title="' . I18N::translate('Find a place') . '"></a>'; 713 } 714 715 /** 716 * HTML link to find a family. 717 * 718 * @param string $element_id 719 * 720 * @return string 721 */ 722 public static function printFindFamilyLink($element_id) 723 { 724 return '<a href="#" onclick="findFamily(document.getElementById(\'' . $element_id . '\'), WT_GEDCOM); return false;" class="icon-button_family" title="' . I18N::translate('Find a family') . '"></a>'; 725 } 726 727 /** 728 * HTML link to open the special character window. 729 * 730 * @param string $element_id 731 * 732 * @return string 733 */ 734 public static function printSpecialCharacterLink($element_id) 735 { 736 return '<span onclick="findSpecialChar(document.getElementById(\'' . $element_id . '\')); if (window.updatewholename) { updatewholename(); } return false;" class="icon-button_keyboard" title="' . I18N::translate('Find a special character') . '"></span>'; 737 } 738 739 /** 740 * HTML element to insert a value from a list. 741 * 742 * @param string $element_id 743 * @param string[] $choices 744 */ 745 public static function printAutoPasteLink($element_id, $choices) 746 { 747 echo '<small>'; 748 foreach ($choices as $choice) { 749 echo '<span onclick="document.getElementById(\'', $element_id, '\').value='; 750 echo '\'', $choice, '\';'; 751 echo " return false;\">", $choice, '</span> '; 752 } 753 echo '</small>'; 754 } 755 756 /** 757 * HTML link to find a source. 758 * 759 * @param string $element_id 760 * @param string $sourcename 761 * 762 * @return string 763 */ 764 public static function printFindSourceLink($element_id, $sourcename = '') 765 { 766 return '<a href="#" onclick="findSource(document.getElementById(\'' . $element_id . '\'), document.getElementById(\'' . $sourcename . '\'), WT_GEDCOM); return false;" class="icon-button_source" title="' . I18N::translate('Find a source') . '"></a>'; 767 } 768 769 /** 770 * HTML link to find a note. 771 * 772 * @param string $element_id 773 * @param string $notename 774 * 775 * @return string 776 */ 777 public static function printFindNoteLink($element_id, $notename = '') 778 { 779 return '<a href="#" onclick="findnote(document.getElementById(\'' . $element_id . '\'), document.getElementById(\'' . $notename . '\'), \'WT_GEDCOM\'); return false;" class="icon-button_find" title="' . I18N::translate('Find a shared note') . '"></a>'; 780 } 781 782 /** 783 * HTML link to find a repository. 784 * 785 * @param string $element_id 786 * 787 * @return string 788 */ 789 public static function printFindRepositoryLink($element_id) 790 { 791 return '<a href="#" onclick="findRepository(document.getElementById(\'' . $element_id . '\'), WT_GEDCOM); return false;" class="icon-button_repository" title="' . I18N::translate('Find a repository') . '"></a>'; 792 } 793 794 /** 795 * HTML link to find a media object. 796 * 797 * @param string $element_id 798 * @param string $choose 799 * 800 * @return string 801 */ 802 public static function printFindMediaLink($element_id, $choose = '') 803 { 804 return '<a href="#" onclick="findMedia(document.getElementById(\'' . $element_id . '\'), \'' . $choose . '\', WT_GEDCOM); return false;" class="icon-button_media" title="' . I18N::translate('Find a media object') . '"></a>'; 805 } 806 807 /** 808 * HTML link to find a fact. 809 * 810 * @param string $element_id 811 * 812 * @return string 813 */ 814 public static function printFindFactLink($element_id) 815 { 816 return '<a href="#" onclick="findFact(document.getElementById(\'' . $element_id . '\'), WT_GEDCOM); return false;" class="icon-button_find_facts" title="' . I18N::translate('Find a fact or event') . '"></a>'; 817 } 818 819 /** 820 * Summary of LDS ordinances. 821 * 822 * @param Individual $individual 823 * 824 * @return string 825 */ 826 public static function getLdsSummary(Individual $individual) 827 { 828 $BAPL = $individual->getFacts('BAPL') ? 'B' : '_'; 829 $ENDL = $individual->getFacts('ENDL') ? 'E' : '_'; 830 $SLGC = $individual->getFacts('SLGC') ? 'C' : '_'; 831 $SLGS = '_'; 832 833 foreach ($individual->getSpouseFamilies() as $family) { 834 if ($family->getFacts('SLGS')) { 835 $SLGS = ''; 836 } 837 } 838 839 return $BAPL . $ENDL . $SLGS . $SLGC; 840 } 841} 842