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\Auth; 19use Fisharebest\Webtrees\Census\Census; 20use Fisharebest\Webtrees\Census\CensusOfCzechRepublic; 21use Fisharebest\Webtrees\Census\CensusOfDenmark; 22use Fisharebest\Webtrees\Census\CensusOfDeutschland; 23use Fisharebest\Webtrees\Census\CensusOfEngland; 24use Fisharebest\Webtrees\Census\CensusOfFrance; 25use Fisharebest\Webtrees\Census\CensusOfScotland; 26use Fisharebest\Webtrees\Census\CensusOfUnitedStates; 27use Fisharebest\Webtrees\Census\CensusOfWales; 28use Fisharebest\Webtrees\Config; 29use Fisharebest\Webtrees\Date; 30use Fisharebest\Webtrees\Fact; 31use Fisharebest\Webtrees\Family; 32use Fisharebest\Webtrees\Filter; 33use Fisharebest\Webtrees\GedcomCode\GedcomCodeAdop; 34use Fisharebest\Webtrees\GedcomCode\GedcomCodeName; 35use Fisharebest\Webtrees\GedcomCode\GedcomCodePedi; 36use Fisharebest\Webtrees\GedcomCode\GedcomCodeQuay; 37use Fisharebest\Webtrees\GedcomCode\GedcomCodeRela; 38use Fisharebest\Webtrees\GedcomCode\GedcomCodeStat; 39use Fisharebest\Webtrees\GedcomCode\GedcomCodeTemp; 40use Fisharebest\Webtrees\GedcomRecord; 41use Fisharebest\Webtrees\GedcomTag; 42use Fisharebest\Webtrees\I18N; 43use Fisharebest\Webtrees\Individual; 44use Fisharebest\Webtrees\Media; 45use Fisharebest\Webtrees\Module; 46use Fisharebest\Webtrees\Note; 47use Fisharebest\Webtrees\Repository; 48use Fisharebest\Webtrees\Source; 49use Fisharebest\Webtrees\User; 50use Rhumsaa\Uuid\Uuid; 51 52/** 53 * Class FunctionsEdit - common functions 54 */ 55class FunctionsEdit 56{ 57 /** 58 * Create a <select> control for a form. 59 * 60 * @param string $name 61 * @param string[] $values 62 * @param string|null $empty 63 * @param string $selected 64 * @param string $extra 65 * 66 * @return string 67 */ 68 public static function selectEditControl($name, $values, $empty, $selected, $extra = '') 69 { 70 if ($empty === null) { 71 $html = ''; 72 } else { 73 if (empty($selected)) { 74 $html = '<option value="" selected>' . Filter::escapeHtml($empty) . '</option>'; 75 } else { 76 $html = '<option value="">' . Filter::escapeHtml($empty) . '</option>'; 77 } 78 } 79 // A completely empty list would be invalid, and break various things 80 if (empty($values) && empty($html)) { 81 $html = '<option value=""></option>'; 82 } 83 foreach ($values as $key => $value) { 84 // PHP array keys are cast to integers! Cast them back 85 if ((string) $key === (string) $selected) { 86 $html .= '<option value="' . Filter::escapeHtml($key) . '" selected dir="auto">' . Filter::escapeHtml($value) . '</option>'; 87 } else { 88 $html .= '<option value="' . Filter::escapeHtml($key) . '" dir="auto">' . Filter::escapeHtml($value) . '</option>'; 89 } 90 } 91 if (substr($name, -2) === '[]') { 92 // id attribute is not used for arrays 93 return '<select name="' . $name . '" ' . $extra . '>' . $html . '</select>'; 94 } else { 95 return '<select id="' . $name . '" name="' . $name . '" ' . $extra . '>' . $html . '</select>'; 96 } 97 } 98 99 /** 100 * Create a set of radio buttons for a form 101 * 102 * @param string $name The ID for the form element 103 * @param string[] $values Array of value=>display items 104 * @param string $selected The currently selected item 105 * @param string $extra Additional markup for the label 106 * 107 * @return string 108 */ 109 public static function radioButtons($name, $values, $selected, $extra = '') 110 { 111 $html = ''; 112 foreach ($values as $key => $value) { 113 $html .= 114 '<label ' . $extra . '>' . 115 '<input type="radio" name="' . $name . '" value="' . Filter::escapeHtml($key) . '"'; 116 // PHP array keys are cast to integers! Cast them back 117 if ((string) $key === (string) $selected) { 118 $html .= ' checked'; 119 } 120 $html .= '>' . Filter::escapeHtml($value) . '</label>'; 121 } 122 123 return $html; 124 } 125 126 /** 127 * Print an edit control for a Yes/No field 128 * 129 * @param string $name 130 * @param bool $selected 131 * @param string $extra 132 * 133 * @return string 134 */ 135 public static function editFieldYesNo($name, $selected = false, $extra = '') 136 { 137 return self::radioButtons( 138 $name, array(I18N::translate('no'), I18N::translate('yes')), $selected, $extra 139 ); 140 } 141 142 /** 143 * Print an edit control for a checkbox. 144 * 145 * @param string $name 146 * @param bool $is_checked 147 * @param string $extra 148 * 149 * @return string 150 */ 151 public static function checkbox($name, $is_checked = false, $extra = '') 152 { 153 return '<input type="checkbox" name="' . $name . '" value="1" ' . ($is_checked ? 'checked ' : '') . $extra . '>'; 154 } 155 156 /** 157 * Print an edit control for a checkbox, with a hidden field to store one of the two states. 158 * By default, a checkbox is either set, or not sent. 159 * This function gives us a three options, set, unset or not sent. 160 * Useful for dynamically generated forms where we don't know what elements are present. 161 * 162 * @param string $name 163 * @param int $is_checked 0 or 1 164 * @param string $extra 165 * 166 * @return string 167 */ 168 public static function twoStateCheckbox($name, $is_checked = 0, $extra = '') 169 { 170 return 171 '<input type="hidden" id="' . $name . '" name="' . $name . '" value="' . ($is_checked ? 1 : 0) . '">' . 172 '<input type="checkbox" name="' . $name . '-GUI-ONLY" value="1"' . 173 ($is_checked ? ' checked' : '') . 174 ' onclick="document.getElementById(\'' . $name . '\').value=(this.checked?1:0);" ' . $extra . '>'; 175 } 176 177 /** 178 * Function edit_language_checkboxes 179 * 180 * @param string $parameter_name 181 * @param array $accepted_languages 182 * 183 * @return string 184 */ 185 public static function editLanguageCheckboxes($parameter_name, $accepted_languages) 186 { 187 $html = ''; 188 foreach (I18N::activeLocales() as $locale) { 189 $html .= '<div class="checkbox">'; 190 $html .= '<label title="' . $locale->languageTag() . '">'; 191 $html .= '<input type="checkbox" name="' . $parameter_name . '[]" value="' . $locale->languageTag() . '"'; 192 $html .= in_array($locale->languageTag(), $accepted_languages) ? ' checked>' : '>'; 193 $html .= $locale->endonym(); 194 $html .= '</label>'; 195 $html .= '</div>'; 196 } 197 198 return $html; 199 } 200 201 /** 202 * Print an edit control for access level. 203 * 204 * @param string $name 205 * @param string $selected 206 * @param string $extra 207 * 208 * @return string 209 */ 210 public static function editFieldAccessLevel($name, $selected = '', $extra = '') 211 { 212 $ACCESS_LEVEL = array( 213 Auth::PRIV_PRIVATE => I18N::translate('Show to visitors'), 214 Auth::PRIV_USER => I18N::translate('Show to members'), 215 Auth::PRIV_NONE => I18N::translate('Show to managers'), 216 Auth::PRIV_HIDE => I18N::translate('Hide from everyone'), 217 ); 218 219 return self::selectEditControl($name, $ACCESS_LEVEL, null, $selected, $extra); 220 } 221 222 /** 223 * Print an edit control for a RESN field. 224 * 225 * @param string $name 226 * @param string $selected 227 * @param string $extra 228 * 229 * @return string 230 */ 231 public static function editFieldRestriction($name, $selected = '', $extra = '') 232 { 233 $RESN = array( 234 '' => '', 235 'none' => I18N::translate('Show to visitors'), // Not valid GEDCOM, but very useful 236 'privacy' => I18N::translate('Show to members'), 237 'confidential' => I18N::translate('Show to managers'), 238 'locked' => I18N::translate('Only managers can edit'), 239 ); 240 241 return self::selectEditControl($name, $RESN, null, $selected, $extra); 242 } 243 244 /** 245 * Print an edit control for a contact method field. 246 * 247 * @param string $name 248 * @param string $selected 249 * @param string $extra 250 * 251 * @return string 252 */ 253 public static function editFieldContact($name, $selected = '', $extra = '') 254 { 255 // Different ways to contact the users 256 $CONTACT_METHODS = array( 257 'messaging' => I18N::translate('Internal messaging'), 258 'messaging2' => I18N::translate('Internal messaging with emails'), 259 'messaging3' => I18N::translate('webtrees sends emails with no storage'), 260 'mailto' => I18N::translate('Mailto link'), 261 'none' => I18N::translate('No contact'), 262 ); 263 264 return self::selectEditControl($name, $CONTACT_METHODS, null, $selected, $extra); 265 } 266 267 /** 268 * Print an edit control for a language field. 269 * 270 * @param string $name 271 * @param string $selected 272 * @param string $extra 273 * 274 * @return string 275 */ 276 public static function editFieldLanguage($name, $selected = '', $extra = '') 277 { 278 $languages = array(); 279 foreach (I18N::activeLocales() as $locale) { 280 $languages[$locale->languageTag()] = $locale->endonym(); 281 } 282 283 return self::selectEditControl($name, $languages, null, $selected, $extra); 284 } 285 286 /** 287 * Print an edit control for a range of integers. 288 * 289 * @param string $name 290 * @param string $selected 291 * @param int $min 292 * @param int $max 293 * @param string $extra 294 * 295 * @return string 296 */ 297 public static function editFieldInteger($name, $selected = '', $min, $max, $extra = '') 298 { 299 $array = array(); 300 for ($i = $min; $i <= $max; ++$i) { 301 $array[$i] = I18N::number($i); 302 } 303 304 return self::selectEditControl($name, $array, null, $selected, $extra); 305 } 306 307 /** 308 * Print an edit control for a username. 309 * 310 * @param string $name 311 * @param string $selected 312 * @param string $extra 313 * 314 * @return string 315 */ 316 public static function editFieldUsername($name, $selected = '', $extra = '') 317 { 318 $users = array(); 319 foreach (User::all() as $user) { 320 $users[$user->getUserName()] = $user->getRealName() . ' - ' . $user->getUserName(); 321 } 322 // The currently selected user may not exist 323 if ($selected && !array_key_exists($selected, $users)) { 324 $users[$selected] = $selected; 325 } 326 327 return self::selectEditControl($name, $users, '-', $selected, $extra); 328 } 329 330 /** 331 * Print an edit control for a ADOP field. 332 * 333 * @param string $name 334 * @param string $selected 335 * @param string $extra 336 * @param Individual|null $individual 337 * 338 * @return string 339 */ 340 public static function editFieldAdoption($name, $selected = '', $extra = '', Individual $individual = null) 341 { 342 return self::selectEditControl($name, GedcomCodeAdop::getValues($individual), null, $selected, $extra); 343 } 344 345 /** 346 * Print an edit control for a PEDI field. 347 * 348 * @param string $name 349 * @param string $selected 350 * @param string $extra 351 * @param Individual|null $individual 352 * 353 * @return string 354 */ 355 public static function editFieldPedigree($name, $selected = '', $extra = '', Individual $individual = null) 356 { 357 return self::selectEditControl($name, GedcomCodePedi::getValues($individual), '', $selected, $extra); 358 } 359 360 /** 361 * Print an edit control for a NAME TYPE field. 362 * 363 * @param string $name 364 * @param string $selected 365 * @param string $extra 366 * @param Individual|null $individual 367 * 368 * @return string 369 */ 370 public static function editFieldNameType($name, $selected = '', $extra = '', Individual $individual = null) 371 { 372 return self::selectEditControl($name, GedcomCodeName::getValues($individual), '', $selected, $extra); 373 } 374 375 /** 376 * Print an edit control for a RELA field. 377 * 378 * @param string $name 379 * @param string $selected 380 * @param string $extra 381 * 382 * @return string 383 */ 384 public static function editFieldRelationship($name, $selected = '', $extra = '') 385 { 386 $rela_codes = GedcomCodeRela::getValues(); 387 // The user is allowed to specify values that aren't in the list. 388 if (!array_key_exists($selected, $rela_codes)) { 389 $rela_codes[$selected] = I18N::translate($selected); 390 } 391 392 return self::selectEditControl($name, $rela_codes, '', $selected, $extra); 393 } 394 395 /** 396 * Remove all links from $gedrec to $xref, and any sub-tags. 397 * 398 * @param string $gedrec 399 * @param string $xref 400 * 401 * @return string 402 */ 403 public static function removeLinks($gedrec, $xref) 404 { 405 $gedrec = preg_replace('/\n1 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[2-9].*)*/', '', $gedrec); 406 $gedrec = preg_replace('/\n2 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[3-9].*)*/', '', $gedrec); 407 $gedrec = preg_replace('/\n3 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[4-9].*)*/', '', $gedrec); 408 $gedrec = preg_replace('/\n4 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[5-9].*)*/', '', $gedrec); 409 $gedrec = preg_replace('/\n5 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[6-9].*)*/', '', $gedrec); 410 411 return $gedrec; 412 } 413 414 /** 415 * Generates javascript code for calendar popup in user’s language. 416 * 417 * @param string $id 418 * 419 * @return string 420 */ 421 public static function printCalendarPopup($id) 422 { 423 return 424 ' <a href="#" onclick="cal_toggleDate(\'caldiv' . $id . '\', \'' . $id . '\'); return false;" class="icon-button_calendar" title="' . I18N::translate('Select a date') . '"></a>' . 425 '<div id="caldiv' . $id . '" style="position:absolute;visibility:hidden;background-color:white;z-index:1000;"></div>'; 426 } 427 428 /** 429 * An HTML link to create a new media object. 430 * 431 * @param string $element_id 432 * 433 * @return string 434 */ 435 public static function printAddNewMediaLink($element_id) 436 { 437 return '<a href="#" onclick="pastefield=document.getElementById(\'' . $element_id . '\'); window.open(\'addmedia.php?action=showmediaform\', \'_blank\', edit_window_specs); return false;" class="icon-button_addmedia" title="' . I18N::translate('Create a media object') . '"></a>'; 438 } 439 440 /** 441 * An HTML link to create a new repository. 442 * 443 * @param string $element_id 444 * 445 * @return string 446 */ 447 public static function printAddNewRepositoryLink($element_id) 448 { 449 return '<a href="#" onclick="addnewrepository(document.getElementById(\'' . $element_id . '\')); return false;" class="icon-button_addrepository" title="' . I18N::translate('Create a repository') . '"></a>'; 450 } 451 452 /** 453 * An HTML link to create a new note. 454 * 455 * @param string $element_id 456 * 457 * @return string 458 */ 459 public static function printAddNewNoteLink($element_id) 460 { 461 return '<a href="#" onclick="addnewnote(document.getElementById(\'' . $element_id . '\')); return false;" class="icon-button_addnote" title="' . I18N::translate('Create a shared note') . '"></a>'; 462 } 463 464 /** 465 * An HTML link to edit a note. 466 * 467 * @param string $note_id 468 * 469 * @return string 470 */ 471 public static function printEditNoteLink($note_id) 472 { 473 return '<a href="#" onclick="edit_note(\'' . $note_id . '\'); return false;" class="icon-button_note" title="' . I18N::translate('Edit the shared note') . '"></a>'; 474 } 475 476 /** 477 * An HTML link to create a new source. 478 * 479 * @param string $element_id 480 * 481 * @return string 482 */ 483 public static function printAddNewSourceLink($element_id) 484 { 485 return '<a href="#" onclick="addnewsource(document.getElementById(\'' . $element_id . '\')); return false;" class="icon-button_addsource" title="' . I18N::translate('Create a source') . '"></a>'; 486 } 487 488 /** 489 * add a new tag input field 490 * 491 * called for each fact to be edited on a form. 492 * Fact level=0 means a new empty form : data are POSTed by name 493 * else data are POSTed using arrays : 494 * glevels[] : tag level 495 * islink[] : tag is a link 496 * tag[] : tag name 497 * text[] : tag value 498 * 499 * @param string $tag fact record to edit (eg 2 DATE xxxxx) 500 * @param string $upperlevel optional upper level tag (eg BIRT) 501 * @param string $label An optional label to echo instead of the default 502 * @param string $extra optional text to display after the input field 503 * @param Individual $person For male/female translations 504 * 505 * @return string 506 */ 507 public static function addSimpleTag($tag, $upperlevel = '', $label = '', $extra = null, Individual $person = null) 508 { 509 global $tags, $main_fact, $xref, $bdm, $action, $WT_TREE; 510 511 // Keep track of SOUR fields, so we can reference them in subsequent PAGE fields. 512 static $source_element_id; 513 514 $subnamefacts = array('NPFX', 'GIVN', 'SPFX', 'SURN', 'NSFX', '_MARNM_SURN'); 515 preg_match('/^(?:(\d+) (' . WT_REGEX_TAG . ') ?(.*))/', $tag, $match); 516 list(, $level, $fact, $value) = $match; 517 $level = (int) $level; 518 519 // element name : used to POST data 520 if ($level === 0) { 521 if ($upperlevel) { 522 $element_name = $upperlevel . '_' . $fact; 523 } else { 524 $element_name = $fact; 525 } 526 } else { 527 $element_name = 'text[]'; 528 } 529 if ($level === 1) { 530 $main_fact = $fact; 531 } 532 533 // element id : used by javascript functions 534 if ($level === 0) { 535 $element_id = $fact; 536 } else { 537 $element_id = $fact . Uuid::uuid4(); 538 } 539 if ($upperlevel) { 540 $element_id = $upperlevel . '_' . $fact . Uuid::uuid4(); 541 } 542 543 // field value 544 $islink = (substr($value, 0, 1) === '@' && substr($value, 0, 2) !== '@#'); 545 if ($islink) { 546 $value = trim(substr($tag, strlen($fact) + 3), '@'); 547 } else { 548 $value = (string) substr($tag, strlen($fact) + 3); 549 } 550 if ($fact === 'REPO' || $fact === 'SOUR' || $fact === 'OBJE' || $fact === 'FAMC') { 551 $islink = true; 552 } 553 554 if ($fact === 'SHARED_NOTE_EDIT' || $fact === 'SHARED_NOTE') { 555 $islink = true; 556 $fact = 'NOTE'; 557 } 558 559 // label 560 echo '<tr id="', $element_id, '_tr"'; 561 if ($fact === 'DATA' || $fact === 'MAP' || ($fact === 'LATI' || $fact === 'LONG') && $value === '') { 562 echo ' style="display:none;"'; 563 } 564 echo '>'; 565 566 if (in_array($fact, $subnamefacts) || $fact === 'LATI' || $fact === 'LONG') { 567 echo '<td class="optionbox wrap width25">'; 568 } else { 569 echo '<td class="descriptionbox wrap width25">'; 570 } 571 572 // tag name 573 if ($label) { 574 echo $label; 575 } elseif ($upperlevel) { 576 echo GedcomTag::getLabel($upperlevel . ':' . $fact); 577 } else { 578 echo GedcomTag::getLabel($fact); 579 } 580 581 // If using GEDFact-assistant window 582 if ($action === 'addnewnote_assisted') { 583 // Do not print on GEDFact Assistant window 584 } else { 585 // Not all facts have help text. 586 switch ($fact) { 587 case 'NAME': 588 if ($upperlevel !== 'REPO' && $upperlevel !== 'UNKNOWN') { 589 echo FunctionsPrint::helpLink($fact); 590 } 591 break; 592 case 'DATE': 593 case 'PLAC': 594 case 'RESN': 595 case 'ROMN': 596 case 'SURN': 597 case '_HEB': 598 echo FunctionsPrint::helpLink($fact); 599 break; 600 } 601 } 602 // tag level 603 if ($level > 0) { 604 echo '<input type="hidden" name="glevels[]" value="', $level, '">'; 605 echo '<input type="hidden" name="islink[]" value="', $islink, '">'; 606 echo '<input type="hidden" name="tag[]" value="', $fact, '">'; 607 } 608 echo '</td>'; 609 610 // value 611 echo '<td class="optionbox wrap">'; 612 613 // retrieve linked NOTE 614 if ($fact === 'NOTE' && $islink) { 615 $note1 = Note::getInstance($value, $WT_TREE); 616 if ($note1) { 617 $noterec = $note1->getGedcom(); 618 preg_match('/' . $value . '/i', $noterec, $notematch); 619 $value = $notematch[0]; 620 } 621 } 622 623 // Show names for spouses in MARR/HUSB/AGE and MARR/WIFE/AGE 624 if ($fact === 'HUSB' || $fact === 'WIFE') { 625 $family = Family::getInstance($xref, $WT_TREE); 626 if ($family) { 627 $spouse_link = $family->getFirstFact($fact); 628 if ($spouse_link) { 629 $spouse = $spouse_link->getTarget(); 630 if ($spouse) { 631 echo $spouse->getFullName(); 632 } 633 } 634 } 635 } 636 637 if (in_array($fact, Config::emptyFacts()) && ($value === '' || $value === 'Y' || $value === 'y')) { 638 echo '<input type="hidden" id="', $element_id, '" name="', $element_name, '" value="', $value, '">'; 639 if ($level <= 1) { 640 echo '<input type="checkbox" '; 641 if ($value) { 642 echo 'checked'; 643 } 644 echo ' onclick="document.getElementById(\'' . $element_id . '\').value = (this.checked) ? \'Y\' : \'\';">'; 645 echo I18N::translate('yes'); 646 } 647 648 if ($fact === 'CENS' && $value === 'Y') { 649 echo self::censusDateSelector(WT_LOCALE, $xref); 650 if (Module::getModuleByName('GEDFact_assistant') && GedcomRecord::getInstance($xref, $WT_TREE) instanceof Individual) { 651 echo 652 '<div></div><a href="#" style="display: none;" id="assistant-link" onclick="return activateCensusAssistant();">' . 653 I18N::translate('Create a shared note using the census assistant') . 654 '</a></div>'; 655 } 656 } 657 } elseif ($fact === 'TEMP') { 658 echo self::selectEditControl($element_name, GedcomCodeTemp::templeNames(), I18N::translate('No temple - living ordinance'), $value); 659 } elseif ($fact === 'ADOP') { 660 echo self::editFieldAdoption($element_name, $value, '', $person); 661 } elseif ($fact === 'PEDI') { 662 echo self::editFieldPedigree($element_name, $value, '', $person); 663 } elseif ($fact === 'STAT') { 664 echo self::selectEditControl($element_name, GedcomCodeStat::statusNames($upperlevel), '', $value); 665 } elseif ($fact === 'RELA') { 666 echo self::editFieldRelationship($element_name, strtolower($value)); 667 } elseif ($fact === 'QUAY') { 668 echo self::selectEditControl($element_name, GedcomCodeQuay::getValues(), '', $value); 669 } elseif ($fact === '_WT_USER') { 670 echo self::editFieldUsername($element_name, $value); 671 } elseif ($fact === 'RESN') { 672 echo self::editFieldRestriction($element_name, $value); 673 } elseif ($fact === '_PRIM') { 674 echo '<select id="', $element_id, '" name="', $element_name, '" >'; 675 echo '<option value=""></option>'; 676 echo '<option value="Y" '; 677 if ($value === 'Y') { 678 echo ' selected'; 679 } 680 echo '>', /* I18N: option in list box “always use this image” */ 681 I18N::translate('always'), '</option>'; 682 echo '<option value="N" '; 683 if ($value === 'N') { 684 echo 'selected'; 685 } 686 echo '>', /* I18N: option in list box “never use this image” */ 687 I18N::translate('never'), '</option>'; 688 echo '</select>'; 689 echo '<p class="small text-muted">', I18N::translate('Use this image for charts and on the individual’s page.'), '</p>'; 690 } elseif ($fact === 'SEX') { 691 echo '<select id="', $element_id, '" name="', $element_name, '"><option value="M" '; 692 if ($value === 'M') { 693 echo 'selected'; 694 } 695 echo '>', I18N::translate('Male'), '</option><option value="F" '; 696 if ($value === 'F') { 697 echo 'selected'; 698 } 699 echo '>', I18N::translate('Female'), '</option><option value="U" '; 700 if ($value === 'U' || empty($value)) { 701 echo 'selected'; 702 } 703 echo '>', I18N::translateContext('unknown gender', 'Unknown'), '</option></select>'; 704 } elseif ($fact === 'TYPE' && $level === 3) { 705 //-- Build the selector for the Media 'TYPE' Fact 706 echo '<select name="text[]"><option selected value="" ></option>'; 707 $selectedValue = strtolower($value); 708 if (!array_key_exists($selectedValue, GedcomTag::getFileFormTypes())) { 709 echo '<option selected value="', Filter::escapeHtml($value), '" >', Filter::escapeHtml($value), '</option>'; 710 } 711 foreach (GedcomTag::getFileFormTypes() as $typeName => $typeValue) { 712 echo '<option value="', $typeName, '" '; 713 if ($selectedValue === $typeName) { 714 echo 'selected'; 715 } 716 echo '>', $typeValue, '</option>'; 717 } 718 echo '</select>'; 719 } elseif (($fact === 'NAME' && $upperlevel !== 'REPO' && $upperlevel !== 'UNKNOWN') || $fact === '_MARNM') { 720 // Populated in javascript from sub-tags 721 echo '<input type="hidden" id="', $element_id, '" name="', $element_name, '" onchange="updateTextName(\'', $element_id, '\');" value="', Filter::escapeHtml($value), '" class="', $fact, '">'; 722 echo '<span id="', $element_id, '_display" dir="auto">', Filter::escapeHtml($value), '</span>'; 723 echo ' <a href="#edit_name" onclick="convertHidden(\'', $element_id, '\'); return false;" class="icon-edit_indi" title="' . I18N::translate('Edit the name') . '"></a>'; 724 } else { 725 // textarea 726 if ($fact === 'TEXT' || $fact === 'ADDR' || ($fact === 'NOTE' && !$islink)) { 727 echo '<textarea id="', $element_id, '" name="', $element_name, '" dir="auto">', Filter::escapeHtml($value), '</textarea><br>'; 728 } else { 729 // text 730 // If using GEDFact-assistant window 731 if ($action === 'addnewnote_assisted') { 732 echo '<input type="text" id="', $element_id, '" name="', $element_name, '" value="', Filter::escapeHtml($value), '" style="width:4.1em;" dir="ltr"'; 733 } else { 734 echo '<input type="text" id="', $element_id, '" name="', $element_name, '" value="', Filter::escapeHtml($value), '" dir="ltr"'; 735 } 736 echo ' class="', $fact, '"'; 737 if (in_array($fact, $subnamefacts)) { 738 echo ' onblur="updatewholename();" onkeyup="updatewholename();"'; 739 } 740 741 // Extra markup for specific fact types 742 switch ($fact) { 743 case 'ALIA': 744 case 'ASSO': 745 case '_ASSO': 746 echo ' data-autocomplete-type="ASSO" data-autocomplete-extra="input.DATE"'; 747 break; 748 case 'DATE': 749 echo ' onblur="valid_date(this);" onmouseout="valid_date(this);"'; 750 break; 751 case 'GIVN': 752 echo ' autofocus data-autocomplete-type="GIVN"'; 753 break; 754 case 'LATI': 755 echo ' onblur="valid_lati_long(this, \'N\', \'S\');" onmouseout="valid_lati_long(this, \'N\', \'S\');"'; 756 break; 757 case 'LONG': 758 echo ' onblur="valid_lati_long(this, \'E\', \'W\');" onmouseout="valid_lati_long(this, \'E\', \'W\');"'; 759 break; 760 case 'NOTE': 761 // Shared notes. Inline notes are handled elsewhere. 762 echo ' data-autocomplete-type="NOTE"'; 763 break; 764 case 'OBJE': 765 echo ' data-autocomplete-type="OBJE"'; 766 break; 767 case 'PAGE': 768 echo ' data-autocomplete-type="PAGE" data-autocomplete-extra="#' . $source_element_id . '"'; 769 break; 770 case 'PLAC': 771 echo ' data-autocomplete-type="PLAC"'; 772 break; 773 case 'REPO': 774 echo ' data-autocomplete-type="REPO"'; 775 break; 776 case 'SOUR': 777 $source_element_id = $element_id; 778 echo ' data-autocomplete-type="SOUR"'; 779 break; 780 case 'SURN': 781 case '_MARNM_SURN': 782 echo ' data-autocomplete-type="SURN"'; 783 break; 784 case 'TIME': 785 echo ' pattern="([0-1][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])?" dir="ltr" placeholder="' . /* I18N: Examples of valid time formats (hours:minutes:seconds) */ 786 I18N::translate('hh:mm or hh:mm:ss') . '"'; 787 break; 788 } 789 echo '>'; 790 } 791 792 $tmp_array = array('TYPE', 'TIME', 'NOTE', 'SOUR', 'REPO', 'OBJE', 'ASSO', '_ASSO', 'AGE'); 793 794 // split PLAC 795 if ($fact === 'PLAC') { 796 echo '<div id="', $element_id, '_pop" style="display: inline;">'; 797 echo FunctionsPrint::printSpecialCharacterLink($element_id), ' ', FunctionsPrint::printFindPlaceLink($element_id); 798 echo '<span onclick="jQuery(\'tr[id^=', $upperlevel, '_LATI],tr[id^=', $upperlevel, '_LONG],tr[id^=LATI],tr[id^=LONG]\').toggle(\'fast\'); return false;" class="icon-target" title="', GedcomTag::getLabel('LATI'), ' / ', GedcomTag::getLabel('LONG'), '"></span>'; 799 echo '</div>'; 800 if (Module::getModuleByName('places_assistant')) { 801 \PlacesAssistantModule::setup_place_subfields($element_id); 802 \PlacesAssistantModule::print_place_subfields($element_id); 803 } 804 } elseif (!in_array($fact, $tmp_array)) { 805 echo FunctionsPrint::printSpecialCharacterLink($element_id); 806 } 807 } 808 // MARRiage TYPE : hide text field and show a selection list 809 if ($fact === 'TYPE' && $level === 2 && $tags[0] === 'MARR') { 810 echo '<script>'; 811 echo 'document.getElementById(\'', $element_id, '\').style.display=\'none\''; 812 echo '</script>'; 813 echo '<select id="', $element_id, '_sel" onchange="document.getElementById(\'', $element_id, '\').value=this.value;" >'; 814 foreach (array('Unknown', 'Civil', 'Religious', 'Partners') as $key) { 815 if ($key === 'Unknown') { 816 echo '<option value="" '; 817 } else { 818 echo '<option value="', $key, '" '; 819 } 820 $a = strtolower($key); 821 $b = strtolower($value); 822 if ($b !== '' && strpos($a, $b) !== false || strpos($b, $a) !== false) { 823 echo 'selected'; 824 } 825 echo '>', GedcomTag::getLabel('MARR_' . strtoupper($key)), '</option>'; 826 } 827 echo '</select>'; 828 } elseif ($fact === 'TYPE' && $level === 0) { 829 // NAME TYPE : hide text field and show a selection list 830 $onchange = 'onchange="document.getElementById(\'' . $element_id . '\').value=this.value;"'; 831 echo self::editFieldNameType($element_name, $value, $onchange, $person); 832 echo '<script>document.getElementById("', $element_id, '").style.display="none";</script>'; 833 } 834 835 // popup links 836 switch ($fact) { 837 case 'DATE': 838 echo self::printCalendarPopup($element_id); 839 break; 840 case 'FAMC': 841 case 'FAMS': 842 echo FunctionsPrint::printFindFamilyLink($element_id); 843 break; 844 case 'ALIA': 845 case 'ASSO': 846 case '_ASSO': 847 echo FunctionsPrint::printFindIndividualLink($element_id, $element_id . '_description'); 848 break; 849 case 'FILE': 850 FunctionsPrint::printFindMediaLink($element_id, '0file'); 851 break; 852 case 'SOUR': 853 echo FunctionsPrint::printFindSourceLink($element_id, $element_id . '_description'), ' ', self::printAddNewSourceLink($element_id); 854 //-- checkboxes to apply '1 SOUR' to BIRT/MARR/DEAT as '2 SOUR' 855 if ($level === 1) { 856 echo '<br>'; 857 switch ($WT_TREE->getPreference('PREFER_LEVEL2_SOURCES')) { 858 case '2': // records 859 $level1_checked = 'checked'; 860 $level2_checked = ''; 861 break; 862 case '1': // facts 863 $level1_checked = ''; 864 $level2_checked = 'checked'; 865 break; 866 case '0': // none 867 default: 868 $level1_checked = ''; 869 $level2_checked = ''; 870 break; 871 } 872 if (strpos($bdm, 'B') !== false) { 873 echo ' <label><input type="checkbox" name="SOUR_INDI" ', $level1_checked, ' value="1">', I18N::translate('Individual'), '</label>'; 874 if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('QUICK_REQUIRED_FACTS'), $matches)) { 875 foreach ($matches[1] as $match) { 876 if (!in_array($match, explode('|', WT_EVENTS_DEAT))) { 877 echo ' <label><input type="checkbox" name="SOUR_', $match, '" ', $level2_checked, ' value="1">', GedcomTag::getLabel($match), '</label>'; 878 } 879 } 880 } 881 } 882 if (strpos($bdm, 'D') !== false) { 883 if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('QUICK_REQUIRED_FACTS'), $matches)) { 884 foreach ($matches[1] as $match) { 885 if (in_array($match, explode('|', WT_EVENTS_DEAT))) { 886 echo ' <label><input type="checkbox" name="SOUR_', $match, '"', $level2_checked, ' value="1">', GedcomTag::getLabel($match), '</label>'; 887 } 888 } 889 } 890 } 891 if (strpos($bdm, 'M') !== false) { 892 echo ' <label><input type="checkbox" name="SOUR_FAM" ', $level1_checked, ' value="1">', I18N::translate('Family'), '</label>'; 893 if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('QUICK_REQUIRED_FAMFACTS'), $matches)) { 894 foreach ($matches[1] as $match) { 895 echo ' <label><input type="checkbox" name="SOUR_', $match, '"', $level2_checked, ' value="1">', GedcomTag::getLabel($match), '</label>'; 896 } 897 } 898 } 899 } 900 break; 901 case 'REPO': 902 echo FunctionsPrint::printFindRepositoryLink($element_id), ' ', self::printAddNewRepositoryLink($element_id); 903 break; 904 case 'NOTE': 905 // Shared Notes Icons ======================================== 906 if ($islink) { 907 // Print regular Shared Note icons --------------------------- 908 echo ' ', FunctionsPrint::printFindNoteLink($element_id, $element_id . '_description'), ' ', self::printAddNewNoteLink($element_id); 909 if ($value) { 910 echo ' ', self::printEditNoteLink($value); 911 } 912 } 913 break; 914 case 'OBJE': 915 echo FunctionsPrint::printFindMediaLink($element_id, '1media'); 916 if (!$value) { 917 echo ' ', self::printAddNewMediaLink($element_id); 918 $value = 'new'; 919 } 920 break; 921 } 922 923 echo '<div id="' . $element_id . '_description">'; 924 925 // current value 926 if ($fact === 'DATE') { 927 $date = new Date($value); 928 echo $date->display(); 929 } 930 if (($fact === 'ASSO' || $fact === '_ASSO') && $value === '') { 931 if ($level === 1) { 932 echo '<p class="small text-muted">' . I18N::translate('An associate is another individual who was involved with this individual, such as a friend or an employer.') . '</p>'; 933 } else { 934 echo '<p class="small text-muted">' . I18N::translate('An associate is another individual who was involved with this fact or event, such as a witness or a priest.') . '</p>'; 935 } 936 } 937 938 if ($value && $value !== 'new' && $islink) { 939 switch ($fact) { 940 case 'ALIA': 941 case 'ASSO': 942 case '_ASSO': 943 $tmp = Individual::getInstance($value, $WT_TREE); 944 if ($tmp) { 945 echo ' ', $tmp->getFullName(); 946 } 947 break; 948 case 'SOUR': 949 $tmp = Source::getInstance($value, $WT_TREE); 950 if ($tmp) { 951 echo ' ', $tmp->getFullName(); 952 } 953 break; 954 case 'NOTE': 955 $tmp = Note::getInstance($value, $WT_TREE); 956 if ($tmp) { 957 echo ' ', $tmp->getFullName(); 958 } 959 break; 960 case 'OBJE': 961 $tmp = Media::getInstance($value, $WT_TREE); 962 if ($tmp) { 963 echo ' ', $tmp->getFullName(); 964 } 965 break; 966 case 'REPO': 967 $tmp = Repository::getInstance($value, $WT_TREE); 968 if ($tmp) { 969 echo ' ', $tmp->getFullName(); 970 } 971 break; 972 } 973 } 974 975 // pastable values 976 if ($fact === 'FORM' && $upperlevel === 'OBJE') { 977 FunctionsPrint::printAutoPasteLink($element_id, Config::fileFormats()); 978 } 979 echo '</div>', $extra, '</td></tr>'; 980 981 return $element_id; 982 } 983 984 /** 985 * Genearate a <select> element, with the dates/places of all known censuses 986 * 987 * @param string $locale - Sort the censuses for this locale 988 * @param string $xref - The individual for whom we are adding a census 989 * 990 * @return string 991 */ 992 public static function censusDateSelector($locale, $xref) 993 { 994 global $controller; 995 996 // Show more likely census details at the top of the list. 997 switch (WT_LOCALE) { 998 case 'cs': 999 $census_places = array(new CensusOfCzechRepublic); 1000 break; 1001 case 'en-AU': 1002 case 'en-GB': 1003 $census_places = array(new CensusOfEngland, new CensusOfWales, new CensusOfScotland); 1004 break; 1005 case 'en-US': 1006 $census_places = array(new CensusOfUnitedStates); 1007 break; 1008 case 'fr': 1009 case 'fr-CA': 1010 $census_places = array(new CensusOfFrance); 1011 break; 1012 case 'da': 1013 $census_places = array(new CensusOfDenmark); 1014 break; 1015 case 'de': 1016 $census_places = array(new CensusOfDeutschland); 1017 break; 1018 default: 1019 $census_places = array(); 1020 break; 1021 } 1022 foreach (Census::allCensusPlaces() as $census_place) { 1023 if (!in_array($census_place, $census_places)) { 1024 $census_places[] = $census_place; 1025 } 1026 } 1027 1028 $controller->addInlineJavascript(' 1029 function selectCensus(el) { 1030 var option = jQuery(":selected", el); 1031 jQuery("input.DATE", jQuery(el).closest("table")).val(option.val()); 1032 jQuery("input.PLAC", jQuery(el).closest("table")).val(option.data("place")); 1033 jQuery("input.census-class", jQuery(el).closest("table")).val(option.data("census")); 1034 if (option.data("place")) { 1035 jQuery("#assistant-link").show(); 1036 } else { 1037 jQuery("#assistant-link").hide(); 1038 } 1039 } 1040 function set_pid_array(pa) { 1041 jQuery("#pid_array").val(pa); 1042 } 1043 function activateCensusAssistant() { 1044 if (jQuery("#newshared_note_img").hasClass("icon-plus")) { 1045 expand_layer("newshared_note"); 1046 } 1047 var field = jQuery("#newshared_note input.NOTE")[0]; 1048 var xref = jQuery("input[name=xref]").val(); 1049 var census = jQuery(".census-assistant-selector :selected").data("census"); 1050 return addnewnote_assisted(field, xref, census); 1051 } 1052 '); 1053 1054 $options = '<option value="">' . I18N::translate('Census date') . '</option>'; 1055 1056 foreach ($census_places as $census_place) { 1057 $options .= '<option value=""></option>'; 1058 foreach ($census_place->allCensusDates() as $census) { 1059 $date = new Date($census->censusDate()); 1060 $year = $date->minimumDate()->format('%Y'); 1061 $place_hierarchy = explode(', ', $census->censusPlace()); 1062 $options .= '<option value="' . $census->censusDate() . '" data-place="' . $census->censusPlace() . '" data-census="' . get_class($census) . '">' . $place_hierarchy[0] . ' ' . $year . '</option>'; 1063 } 1064 } 1065 1066 return 1067 '<input type="hidden" id="pid_array" name="pid_array" value="">' . 1068 '<select class="census-assistant-selector" onchange="selectCensus(this);">' . $options . '</select>'; 1069 } 1070 1071 /** 1072 * Prints collapsable fields to add ASSO/RELA, SOUR, OBJE, etc. 1073 * 1074 * @param string $tag 1075 * @param int $level 1076 * @param string $parent_tag 1077 */ 1078 public static function printAddLayer($tag, $level = 2, $parent_tag = '') 1079 { 1080 global $WT_TREE; 1081 1082 switch ($tag) { 1083 case 'SOUR': 1084 echo '<a href="#" onclick="return expand_layer(\'newsource\');"><i id="newsource_img" class="icon-plus"></i> ', I18N::translate('Add a source citation'), '</a>'; 1085 echo '<br>'; 1086 echo '<div id="newsource" style="display: none;">'; 1087 echo '<table class="facts_table">'; 1088 // 2 SOUR 1089 self::addSimpleTag($level . ' SOUR @'); 1090 // 3 PAGE 1091 self::addSimpleTag(($level + 1) . ' PAGE'); 1092 // 3 DATA 1093 self::addSimpleTag(($level + 1) . ' DATA'); 1094 // 4 TEXT 1095 self::addSimpleTag(($level + 2) . ' TEXT'); 1096 if ($WT_TREE->getPreference('FULL_SOURCES')) { 1097 // 4 DATE 1098 self::addSimpleTag(($level + 2) . ' DATE', '', GedcomTag::getLabel('DATA:DATE')); 1099 // 3 QUAY 1100 self::addSimpleTag(($level + 1) . ' QUAY'); 1101 } 1102 // 3 OBJE 1103 self::addSimpleTag(($level + 1) . ' OBJE'); 1104 // 3 SHARED_NOTE 1105 self::addSimpleTag(($level + 1) . ' SHARED_NOTE'); 1106 echo '</table></div>'; 1107 break; 1108 1109 case 'ASSO': 1110 case 'ASSO2': 1111 //-- Add a new ASSOciate 1112 if ($tag === 'ASSO') { 1113 echo "<a href=\"#\" onclick=\"return expand_layer('newasso');\"><i id=\"newasso_img\" class=\"icon-plus\"></i> ", I18N::translate('Add an associate'), '</a>'; 1114 echo '<br>'; 1115 echo '<div id="newasso" style="display: none;">'; 1116 } else { 1117 echo "<a href=\"#\" onclick=\"return expand_layer('newasso2');\"><i id=\"newasso2_img\" class=\"icon-plus\"></i> ", I18N::translate('Add an associate'), '</a>'; 1118 echo '<br>'; 1119 echo '<div id="newasso2" style="display: none;">'; 1120 } 1121 echo '<table class="facts_table">'; 1122 // 2 ASSO 1123 self::addSimpleTag($level . ' _ASSO @'); 1124 // 3 RELA 1125 self::addSimpleTag(($level + 1) . ' RELA'); 1126 // 3 NOTE 1127 self::addSimpleTag(($level + 1) . ' NOTE'); 1128 // 3 SHARED_NOTE 1129 self::addSimpleTag(($level + 1) . ' SHARED_NOTE'); 1130 echo '</table></div>'; 1131 break; 1132 1133 case 'NOTE': 1134 //-- Retrieve existing note or add new note to fact 1135 echo "<a href=\"#\" onclick=\"return expand_layer('newnote');\"><i id=\"newnote_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a note'), '</a>'; 1136 echo '<br>'; 1137 echo '<div id="newnote" style="display: none;">'; 1138 echo '<table class="facts_table">'; 1139 // 2 NOTE 1140 self::addSimpleTag($level . ' NOTE'); 1141 echo '</table></div>'; 1142 break; 1143 1144 case 'SHARED_NOTE': 1145 echo "<a href=\"#\" onclick=\"return expand_layer('newshared_note');\"><i id=\"newshared_note_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a shared note'), '</a>'; 1146 echo '<br>'; 1147 echo '<div id="newshared_note" style="display: none;">'; 1148 echo '<table class="facts_table">'; 1149 // 2 SHARED NOTE 1150 self::addSimpleTag($level . ' SHARED_NOTE', $parent_tag); 1151 echo '</table></div>'; 1152 break; 1153 1154 case 'OBJE': 1155 if ($WT_TREE->getPreference('MEDIA_UPLOAD') >= Auth::accessLevel($WT_TREE)) { 1156 echo "<a href=\"#\" onclick=\"return expand_layer('newobje');\"><i id=\"newobje_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a media object'), '</a>'; 1157 echo '<br>'; 1158 echo '<div id="newobje" style="display: none;">'; 1159 echo '<table class="facts_table">'; 1160 self::addSimpleTag($level . ' OBJE'); 1161 echo '</table></div>'; 1162 } 1163 break; 1164 1165 case 'RESN': 1166 echo "<a href=\"#\" onclick=\"return expand_layer('newresn');\"><i id=\"newresn_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a restriction'), '</a>'; 1167 echo '<br>'; 1168 echo '<div id="newresn" style="display: none;">'; 1169 echo '<table class="facts_table">'; 1170 // 2 RESN 1171 self::addSimpleTag($level . ' RESN'); 1172 echo '</table></div>'; 1173 break; 1174 } 1175 } 1176 1177 /** 1178 * Add some empty tags to create a new fact. 1179 * 1180 * @param string $fact 1181 */ 1182 public static function addSimpleTags($fact) 1183 { 1184 global $WT_TREE; 1185 1186 // For new individuals, these facts default to "Y" 1187 if ($fact === 'MARR') { 1188 self::addSimpleTag('0 ' . $fact . ' Y'); 1189 } else { 1190 self::addSimpleTag('0 ' . $fact); 1191 } 1192 1193 if (!in_array($fact, Config::nonDateFacts())) { 1194 self::addSimpleTag('0 DATE', $fact, GedcomTag::getLabel($fact . ':DATE')); 1195 } 1196 1197 if (!in_array($fact, Config::nonPlaceFacts())) { 1198 self::addSimpleTag('0 PLAC', $fact, GedcomTag::getLabel($fact . ':PLAC')); 1199 1200 if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_PLAC_FACTS'), $match)) { 1201 foreach ($match[1] as $tag) { 1202 self::addSimpleTag('0 ' . $tag, $fact, GedcomTag::getLabel($fact . ':PLAC:' . $tag)); 1203 } 1204 } 1205 self::addSimpleTag('0 MAP', $fact); 1206 self::addSimpleTag('0 LATI', $fact); 1207 self::addSimpleTag('0 LONG', $fact); 1208 } 1209 } 1210 1211 /** 1212 * Assemble the pieces of a newly created record into gedcom 1213 * 1214 * @return string 1215 */ 1216 public static function addNewName() 1217 { 1218 global $WT_TREE; 1219 1220 $gedrec = "\n1 NAME " . Filter::post('NAME'); 1221 1222 $tags = array('NPFX', 'GIVN', 'SPFX', 'SURN', 'NSFX'); 1223 1224 if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_NAME_FACTS'), $match)) { 1225 $tags = array_merge($tags, $match[1]); 1226 } 1227 1228 // Paternal and Polish and Lithuanian surname traditions can also create a _MARNM 1229 $SURNAME_TRADITION = $WT_TREE->getPreference('SURNAME_TRADITION'); 1230 if ($SURNAME_TRADITION === 'paternal' || $SURNAME_TRADITION === 'polish' || $SURNAME_TRADITION === 'lithuanian') { 1231 $tags[] = '_MARNM'; 1232 } 1233 1234 foreach (array_unique($tags) as $tag) { 1235 $TAG = Filter::post($tag); 1236 if ($TAG) { 1237 $gedrec .= "\n2 {$tag} {$TAG}"; 1238 } 1239 } 1240 1241 return $gedrec; 1242 } 1243 1244 /** 1245 * Create a form to add a sex record. 1246 * 1247 * @return string 1248 */ 1249 public static function addNewSex() 1250 { 1251 switch (Filter::post('SEX', '[MF]', 'U')) { 1252 case 'M': 1253 return "\n1 SEX M"; 1254 case 'F': 1255 return "\n1 SEX F"; 1256 default: 1257 return "\n1 SEX U"; 1258 } 1259 } 1260 1261 /** 1262 * Create a form to add a new fact. 1263 * 1264 * @param string $fact 1265 * 1266 * @return string 1267 */ 1268 public static function addNewFact($fact) 1269 { 1270 global $WT_TREE; 1271 1272 $FACT = Filter::post($fact); 1273 $DATE = Filter::post($fact . '_DATE'); 1274 $PLAC = Filter::post($fact . '_PLAC'); 1275 if ($DATE || $PLAC || $FACT && $FACT !== 'Y') { 1276 if ($FACT && $FACT !== 'Y') { 1277 $gedrec = "\n1 " . $fact . ' ' . $FACT; 1278 } else { 1279 $gedrec = "\n1 " . $fact; 1280 } 1281 if ($DATE) { 1282 $gedrec .= "\n2 DATE " . $DATE; 1283 } 1284 if ($PLAC) { 1285 $gedrec .= "\n2 PLAC " . $PLAC; 1286 1287 if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_PLAC_FACTS'), $match)) { 1288 foreach ($match[1] as $tag) { 1289 $TAG = Filter::post($fact . '_' . $tag); 1290 if ($TAG) { 1291 $gedrec .= "\n3 " . $tag . ' ' . $TAG; 1292 } 1293 } 1294 } 1295 $LATI = Filter::post($fact . '_LATI'); 1296 $LONG = Filter::post($fact . '_LONG'); 1297 if ($LATI || $LONG) { 1298 $gedrec .= "\n3 MAP\n4 LATI " . $LATI . "\n4 LONG " . $LONG; 1299 } 1300 } 1301 if (Filter::postBool('SOUR_' . $fact)) { 1302 return self::updateSource($gedrec, 2); 1303 } else { 1304 return $gedrec; 1305 } 1306 } elseif ($FACT === 'Y') { 1307 if (Filter::postBool('SOUR_' . $fact)) { 1308 return self::updateSource("\n1 " . $fact . ' Y', 2); 1309 } else { 1310 return "\n1 " . $fact . ' Y'; 1311 } 1312 } else { 1313 return ''; 1314 } 1315 } 1316 1317 /** 1318 * This function splits the $glevels, $tag, $islink, and $text arrays so that the 1319 * entries associated with a SOUR record are separate from everything else. 1320 * 1321 * Input arrays: 1322 * - $glevels[] - an array of the gedcom level for each line that was edited 1323 * - $tag[] - an array of the tags for each gedcom line that was edited 1324 * - $islink[] - an array of 1 or 0 values to indicate when the text is a link element 1325 * - $text[] - an array of the text data for each line 1326 * 1327 * Output arrays: 1328 * ** For the SOUR record: 1329 * - $glevelsSOUR[] - an array of the gedcom level for each line that was edited 1330 * - $tagSOUR[] - an array of the tags for each gedcom line that was edited 1331 * - $islinkSOUR[] - an array of 1 or 0 values to indicate when the text is a link element 1332 * - $textSOUR[] - an array of the text data for each line 1333 * ** For the remaining records: 1334 * - $glevelsRest[] - an array of the gedcom level for each line that was edited 1335 * - $tagRest[] - an array of the tags for each gedcom line that was edited 1336 * - $islinkRest[] - an array of 1 or 0 values to indicate when the text is a link element 1337 * - $textRest[] - an array of the text data for each line 1338 */ 1339 public static function splitSource() 1340 { 1341 global $glevels, $tag, $islink, $text; 1342 global $glevelsSOUR, $tagSOUR, $islinkSOUR, $textSOUR; 1343 global $glevelsRest, $tagRest, $islinkRest, $textRest; 1344 1345 $glevelsSOUR = array(); 1346 $tagSOUR = array(); 1347 $islinkSOUR = array(); 1348 $textSOUR = array(); 1349 1350 $glevelsRest = array(); 1351 $tagRest = array(); 1352 $islinkRest = array(); 1353 $textRest = array(); 1354 1355 $inSOUR = false; 1356 1357 for ($i = 0; $i < count($glevels); $i++) { 1358 if ($inSOUR) { 1359 if ($levelSOUR < $glevels[$i]) { 1360 $dest = 'S'; 1361 } else { 1362 $inSOUR = false; 1363 $dest = 'R'; 1364 } 1365 } else { 1366 if ($tag[$i] === 'SOUR') { 1367 $inSOUR = true; 1368 $levelSOUR = $glevels[$i]; 1369 $dest = 'S'; 1370 } else { 1371 $dest = 'R'; 1372 } 1373 } 1374 if ($dest === 'S') { 1375 $glevelsSOUR[] = $glevels[$i]; 1376 $tagSOUR[] = $tag[$i]; 1377 $islinkSOUR[] = $islink[$i]; 1378 $textSOUR[] = $text[$i]; 1379 } else { 1380 $glevelsRest[] = $glevels[$i]; 1381 $tagRest[] = $tag[$i]; 1382 $islinkRest[] = $islink[$i]; 1383 $textRest[] = $text[$i]; 1384 } 1385 } 1386 } 1387 1388 /** 1389 * Add new GEDCOM lines from the $xxxSOUR interface update arrays, which 1390 * were produced by the splitSOUR() function. 1391 * See the FunctionsEdit::handle_updatesges() function for details. 1392 * 1393 * @param string $inputRec 1394 * @param string $levelOverride 1395 * 1396 * @return string 1397 */ 1398 public static function updateSource($inputRec, $levelOverride = 'no') 1399 { 1400 global $glevels, $tag, $islink, $text; 1401 global $glevelsSOUR, $tagSOUR, $islinkSOUR, $textSOUR; 1402 1403 if (count($tagSOUR) === 0) { 1404 return $inputRec; // No update required 1405 } 1406 1407 // Save original interface update arrays before replacing them with the xxxSOUR ones 1408 $glevelsSave = $glevels; 1409 $tagSave = $tag; 1410 $islinkSave = $islink; 1411 $textSave = $text; 1412 1413 $glevels = $glevelsSOUR; 1414 $tag = $tagSOUR; 1415 $islink = $islinkSOUR; 1416 $text = $textSOUR; 1417 1418 $myRecord = self::handleUpdates($inputRec, $levelOverride); // Now do the update 1419 1420 // Restore the original interface update arrays (just in case ...) 1421 $glevels = $glevelsSave; 1422 $tag = $tagSave; 1423 $islink = $islinkSave; 1424 $text = $textSave; 1425 1426 return $myRecord; 1427 } 1428 1429 /** 1430 * Add new GEDCOM lines from the $xxxRest interface update arrays, which 1431 * were produced by the splitSOUR() function. 1432 * See the FunctionsEdit::handle_updatesges() function for details. 1433 * 1434 * @param string $inputRec 1435 * @param string $levelOverride 1436 * 1437 * @return string 1438 */ 1439 public static function updateRest($inputRec, $levelOverride = 'no') 1440 { 1441 global $glevels, $tag, $islink, $text; 1442 global $glevelsRest, $tagRest, $islinkRest, $textRest; 1443 1444 if (count($tagRest) === 0) { 1445 return $inputRec; // No update required 1446 } 1447 1448 // Save original interface update arrays before replacing them with the xxxRest ones 1449 $glevelsSave = $glevels; 1450 $tagSave = $tag; 1451 $islinkSave = $islink; 1452 $textSave = $text; 1453 1454 $glevels = $glevelsRest; 1455 $tag = $tagRest; 1456 $islink = $islinkRest; 1457 $text = $textRest; 1458 1459 $myRecord = self::handleUpdates($inputRec, $levelOverride); // Now do the update 1460 1461 // Restore the original interface update arrays (just in case ...) 1462 $glevels = $glevelsSave; 1463 $tag = $tagSave; 1464 $islink = $islinkSave; 1465 $text = $textSave; 1466 1467 return $myRecord; 1468 } 1469 1470 /** 1471 * Add new gedcom lines from interface update arrays 1472 * The edit_interface and FunctionsEdit::add_simple_tag function produce the following 1473 * arrays incoming from the $_POST form 1474 * - $glevels[] - an array of the gedcom level for each line that was edited 1475 * - $tag[] - an array of the tags for each gedcom line that was edited 1476 * - $islink[] - an array of 1 or 0 values to tell whether the text is a link element and should be surrounded by @@ 1477 * - $text[] - an array of the text data for each line 1478 * With these arrays you can recreate the gedcom lines like this 1479 * <code>$glevel[0].' '.$tag[0].' '.$text[0]</code> 1480 * There will be an index in each of these arrays for each line of the gedcom 1481 * fact that is being edited. 1482 * If the $text[] array is empty for the given line, then it means that the 1483 * user removed that line during editing or that the line is supposed to be 1484 * empty (1 DEAT, 1 BIRT) for example. To know if the line should be removed 1485 * there is a section of code that looks ahead to the next lines to see if there 1486 * are sub lines. For example we don't want to remove the 1 DEAT line if it has 1487 * a 2 PLAC or 2 DATE line following it. If there are no sub lines, then the line 1488 * can be safely removed. 1489 * 1490 * @param string $newged the new gedcom record to add the lines to 1491 * @param string $levelOverride Override GEDCOM level specified in $glevels[0] 1492 * 1493 * @return string The updated gedcom record 1494 */ 1495 public static function handleUpdates($newged, $levelOverride = 'no') 1496 { 1497 global $glevels, $islink, $tag, $uploaded_files, $text; 1498 1499 if ($levelOverride === 'no' || count($glevels) === 0) { 1500 $levelAdjust = 0; 1501 } else { 1502 $levelAdjust = $levelOverride - $glevels[0]; 1503 } 1504 1505 for ($j = 0; $j < count($glevels); $j++) { 1506 1507 // Look for empty SOUR reference with non-empty sub-records. 1508 // This can happen when the SOUR entry is deleted but its sub-records 1509 // were incorrectly left intact. 1510 // The sub-records should be deleted. 1511 if ($tag[$j] === 'SOUR' && ($text[$j] === '@@' || $text[$j] === '')) { 1512 $text[$j] = ''; 1513 $k = $j + 1; 1514 while (($k < count($glevels)) && ($glevels[$k] > $glevels[$j])) { 1515 $text[$k] = ''; 1516 $k++; 1517 } 1518 } 1519 1520 if (trim($text[$j]) !== '') { 1521 $pass = true; 1522 } else { 1523 //-- for facts with empty values they must have sub records 1524 //-- this section checks if they have subrecords 1525 $k = $j + 1; 1526 $pass = false; 1527 while (($k < count($glevels)) && ($glevels[$k] > $glevels[$j])) { 1528 if ($text[$k] !== '') { 1529 if (($tag[$j] !== 'OBJE') || ($tag[$k] === 'FILE')) { 1530 $pass = true; 1531 break; 1532 } 1533 } 1534 if (($tag[$k] === 'FILE') && (count($uploaded_files) > 0)) { 1535 $filename = array_shift($uploaded_files); 1536 if (!empty($filename)) { 1537 $text[$k] = $filename; 1538 $pass = true; 1539 break; 1540 } 1541 } 1542 $k++; 1543 } 1544 } 1545 1546 //-- if the value is not empty or it has sub lines 1547 //--- then write the line to the gedcom record 1548 //-- we have to let some emtpy text lines pass through... (DEAT, BIRT, etc) 1549 if ($pass) { 1550 $newline = $glevels[$j] + $levelAdjust . ' ' . $tag[$j]; 1551 if ($text[$j] !== '') { 1552 if ($islink[$j]) { 1553 $newline .= ' @' . $text[$j] . '@'; 1554 } else { 1555 $newline .= ' ' . $text[$j]; 1556 } 1557 } 1558 $newged .= "\n" . str_replace("\n", "\n" . (1 + substr($newline, 0, 1)) . ' CONT ', $newline); 1559 } 1560 } 1561 1562 return $newged; 1563 } 1564 1565 /** 1566 * builds the form for adding new facts 1567 * 1568 * @param string $fact the new fact we are adding 1569 */ 1570 public static function createAddForm($fact) 1571 { 1572 global $tags, $WT_TREE; 1573 1574 $tags = array(); 1575 1576 // handle MARRiage TYPE 1577 if (substr($fact, 0, 5) === 'MARR_') { 1578 $tags[0] = 'MARR'; 1579 self::addSimpleTag('1 MARR'); 1580 self::insertMissingSubtags($fact); 1581 } else { 1582 $tags[0] = $fact; 1583 if ($fact === '_UID') { 1584 $fact .= ' ' . GedcomTag::createUid(); 1585 } 1586 // These new level 1 tags need to be turned into links 1587 if (in_array($fact, array('ALIA', 'ASSO'))) { 1588 $fact .= ' @'; 1589 } 1590 if (in_array($fact, Config::emptyFacts())) { 1591 self::addSimpleTag('1 ' . $fact . ' Y'); 1592 } else { 1593 self::addSimpleTag('1 ' . $fact); 1594 } 1595 self::insertMissingSubtags($tags[0]); 1596 //-- handle the special SOURce case for level 1 sources [ 1759246 ] 1597 if ($fact === 'SOUR') { 1598 self::addSimpleTag('2 PAGE'); 1599 self::addSimpleTag('2 DATA'); 1600 self::addSimpleTag('3 TEXT'); 1601 if ($WT_TREE->getPreference('FULL_SOURCES')) { 1602 self::addSimpleTag('3 DATE', '', GedcomTag::getLabel('DATA:DATE')); 1603 self::addSimpleTag('2 QUAY'); 1604 } 1605 } 1606 } 1607 } 1608 1609 /** 1610 * Create a form to edit a Fact object. 1611 * 1612 * @param Fact $fact 1613 * 1614 * @return string 1615 */ 1616 public static function createEditForm(Fact $fact) 1617 { 1618 global $tags; 1619 1620 $record = $fact->getParent(); 1621 1622 $tags = array(); 1623 $gedlines = explode("\n", $fact->getGedcom()); 1624 1625 $linenum = 0; 1626 $fields = explode(' ', $gedlines[$linenum]); 1627 $glevel = $fields[0]; 1628 $level = $glevel; 1629 1630 $type = $fact->getTag(); 1631 $level0type = $record::RECORD_TYPE; 1632 $level1type = $type; 1633 1634 $i = $linenum; 1635 $inSource = false; 1636 $levelSource = 0; 1637 $add_date = true; 1638 1639 // List of tags we would expect at the next level 1640 // NB add_missing_subtags() already takes care of the simple cases 1641 // where a level 1 tag is missing a level 2 tag. Here we only need to 1642 // handle the more complicated cases. 1643 $expected_subtags = array( 1644 'SOUR' => array('PAGE', 'DATA'), 1645 'DATA' => array('TEXT'), 1646 'PLAC' => array('MAP'), 1647 'MAP' => array('LATI', 'LONG'), 1648 ); 1649 if ($record->getTree()->getPreference('FULL_SOURCES')) { 1650 $expected_subtags['SOUR'][] = 'QUAY'; 1651 $expected_subtags['DATA'][] = 'DATE'; 1652 } 1653 if (GedcomCodeTemp::isTagLDS($level1type)) { 1654 $expected_subtags['STAT'] = array('DATE'); 1655 } 1656 if (in_array($level1type, Config::dateAndTime())) { 1657 $expected_subtags['DATE'] = array('TIME'); // TIME is NOT a valid 5.5.1 tag 1658 } 1659 if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $record->getTree()->getPreference('ADVANCED_PLAC_FACTS'), $match)) { 1660 $expected_subtags['PLAC'] = array_merge($match[1], $expected_subtags['PLAC']); 1661 } 1662 1663 $stack = array(); 1664 // Loop on existing tags : 1665 while (true) { 1666 // Keep track of our hierarchy, e.g. 1=>BIRT, 2=>PLAC, 3=>FONE 1667 $stack[$level] = $type; 1668 // Merge them together, e.g. BIRT:PLAC:FONE 1669 $label = implode(':', array_slice($stack, 0, $level)); 1670 1671 $text = ''; 1672 for ($j = 2; $j < count($fields); $j++) { 1673 if ($j > 2) { 1674 $text .= ' '; 1675 } 1676 $text .= $fields[$j]; 1677 } 1678 $text = rtrim($text); 1679 while (($i + 1 < count($gedlines)) && (preg_match("/" . ($level + 1) . ' CONT ?(.*)/', $gedlines[$i + 1], $cmatch) > 0)) { 1680 $text .= "\n" . $cmatch[1]; 1681 $i++; 1682 } 1683 1684 if ($type === 'SOUR') { 1685 $inSource = true; 1686 $levelSource = $level; 1687 } elseif ($levelSource >= $level) { 1688 $inSource = false; 1689 } 1690 1691 if ($type !== 'CONT') { 1692 $tags[] = $type; 1693 $subrecord = $level . ' ' . $type . ' ' . $text; 1694 if ($inSource && $type === 'DATE') { 1695 self::addSimpleTag($subrecord, '', GedcomTag::getLabel($label, $record)); 1696 } elseif (!$inSource && $type === 'DATE') { 1697 self::addSimpleTag($subrecord, $level1type, GedcomTag::getLabel($label, $record)); 1698 if ($level === '2') { 1699 // We already have a date - no need to add one. 1700 $add_date = false; 1701 } 1702 } elseif ($type === 'STAT') { 1703 self::addSimpleTag($subrecord, $level1type, GedcomTag::getLabel($label, $record)); 1704 } else { 1705 self::addSimpleTag($subrecord, $level0type, GedcomTag::getLabel($label, $record)); 1706 } 1707 } 1708 1709 // Get a list of tags present at the next level 1710 $subtags = array(); 1711 for ($ii = $i + 1; isset($gedlines[$ii]) && preg_match('/^(\d+) (\S+)/', $gedlines[$ii], $mm) && $mm[1] > $level; ++$ii) { 1712 if ($mm[1] == $level + 1) { 1713 $subtags[] = $mm[2]; 1714 } 1715 } 1716 1717 // Insert missing tags 1718 if (!empty($expected_subtags[$type])) { 1719 foreach ($expected_subtags[$type] as $subtag) { 1720 if (!in_array($subtag, $subtags)) { 1721 self::addSimpleTag(($level + 1) . ' ' . $subtag, '', GedcomTag::getLabel($label . ':' . $subtag)); 1722 if (!empty($expected_subtags[$subtag])) { 1723 foreach ($expected_subtags[$subtag] as $subsubtag) { 1724 self::addSimpleTag(($level + 2) . ' ' . $subsubtag, '', GedcomTag::getLabel($label . ':' . $subtag . ':' . $subsubtag)); 1725 } 1726 } 1727 } 1728 } 1729 } 1730 1731 $i++; 1732 if (isset($gedlines[$i])) { 1733 $fields = explode(' ', $gedlines[$i]); 1734 $level = $fields[0]; 1735 if (isset($fields[1])) { 1736 $type = trim($fields[1]); 1737 } else { 1738 $level = 0; 1739 } 1740 } else { 1741 $level = 0; 1742 } 1743 if ($level <= $glevel) { 1744 break; 1745 } 1746 } 1747 1748 if ($level1type !== '_PRIM') { 1749 self::insertMissingSubtags($level1type, $add_date); 1750 } 1751 1752 return $level1type; 1753 } 1754 1755 /** 1756 * Populates the global $tags array with any missing sub-tags. 1757 * 1758 * @param string $level1tag the type of the level 1 gedcom record 1759 * @param bool $add_date 1760 */ 1761 public static function insertMissingSubtags($level1tag, $add_date = false) 1762 { 1763 global $tags, $WT_TREE; 1764 1765 // handle MARRiage TYPE 1766 $type_val = ''; 1767 if (substr($level1tag, 0, 5) === 'MARR_') { 1768 $type_val = substr($level1tag, 5); 1769 $level1tag = 'MARR'; 1770 } 1771 1772 foreach (Config::levelTwoTags() as $key => $value) { 1773 if ($key === 'DATE' && in_array($level1tag, Config::nonDateFacts()) || $key === 'PLAC' && in_array($level1tag, Config::nonPlaceFacts())) { 1774 continue; 1775 } 1776 if (in_array($level1tag, $value) && !in_array($key, $tags)) { 1777 if ($key === 'TYPE') { 1778 self::addSimpleTag('2 TYPE ' . $type_val, $level1tag); 1779 } elseif ($level1tag === '_TODO' && $key === 'DATE') { 1780 self::addSimpleTag('2 ' . $key . ' ' . strtoupper(date('d M Y')), $level1tag); 1781 } elseif ($level1tag === '_TODO' && $key === '_WT_USER') { 1782 self::addSimpleTag('2 ' . $key . ' ' . Auth::user()->getUserName(), $level1tag); 1783 } elseif ($level1tag === 'TITL' && strstr($WT_TREE->getPreference('ADVANCED_NAME_FACTS'), $key) !== false) { 1784 self::addSimpleTag('2 ' . $key, $level1tag); 1785 } elseif ($level1tag === 'NAME' && strstr($WT_TREE->getPreference('ADVANCED_NAME_FACTS'), $key) !== false) { 1786 self::addSimpleTag('2 ' . $key, $level1tag); 1787 } elseif ($level1tag !== 'TITL' && $level1tag !== 'NAME') { 1788 self::addSimpleTag('2 ' . $key, $level1tag); 1789 } 1790 // Add level 3/4 tags as appropriate 1791 switch ($key) { 1792 case 'PLAC': 1793 if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_PLAC_FACTS'), $match)) { 1794 foreach ($match[1] as $tag) { 1795 self::addSimpleTag('3 ' . $tag, '', GedcomTag::getLabel($level1tag . ':PLAC:' . $tag)); 1796 } 1797 } 1798 self::addSimpleTag('3 MAP'); 1799 self::addSimpleTag('4 LATI'); 1800 self::addSimpleTag('4 LONG'); 1801 break; 1802 case 'FILE': 1803 self::addSimpleTag('3 FORM'); 1804 break; 1805 case 'EVEN': 1806 self::addSimpleTag('3 DATE'); 1807 self::addSimpleTag('3 PLAC'); 1808 break; 1809 case 'STAT': 1810 if (GedcomCodeTemp::isTagLDS($level1tag)) { 1811 self::addSimpleTag('3 DATE', '', GedcomTag::getLabel('STAT:DATE')); 1812 } 1813 break; 1814 case 'DATE': 1815 // TIME is NOT a valid 5.5.1 tag 1816 if (in_array($level1tag, Config::dateAndTime())) { 1817 self::addSimpleTag('3 TIME'); 1818 } 1819 break; 1820 case 'HUSB': 1821 case 'WIFE': 1822 self::addSimpleTag('3 AGE'); 1823 break; 1824 case 'FAMC': 1825 if ($level1tag === 'ADOP') { 1826 self::addSimpleTag('3 ADOP BOTH'); 1827 } 1828 break; 1829 } 1830 } elseif ($key === 'DATE' && $add_date) { 1831 self::addSimpleTag('2 DATE', $level1tag, GedcomTag::getLabel($level1tag . ':DATE')); 1832 } 1833 } 1834 // Do something (anything!) with unrecognized custom tags 1835 if (substr($level1tag, 0, 1) === '_' && $level1tag !== '_UID' && $level1tag !== '_TODO') { 1836 foreach (array('DATE', 'PLAC', 'ADDR', 'AGNC', 'TYPE', 'AGE') as $tag) { 1837 if (!in_array($tag, $tags)) { 1838 self::addSimpleTag('2 ' . $tag); 1839 if ($tag === 'PLAC') { 1840 if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_PLAC_FACTS'), $match)) { 1841 foreach ($match[1] as $ptag) { 1842 self::addSimpleTag('3 ' . $ptag, '', GedcomTag::getLabel($level1tag . ':PLAC:' . $ptag)); 1843 } 1844 } 1845 self::addSimpleTag('3 MAP'); 1846 self::addSimpleTag('4 LATI'); 1847 self::addSimpleTag('4 LONG'); 1848 } 1849 } 1850 } 1851 } 1852 } 1853} 1854