1<?php 2 3use Fisharebest\Webtrees\Auth; 4use Fisharebest\Webtrees\Config; 5use Fisharebest\Webtrees\Fact; 6use Fisharebest\Webtrees\Functions\FunctionsEdit; 7use Fisharebest\Webtrees\Gedcom; 8use Fisharebest\Webtrees\GedcomTag; 9use Fisharebest\Webtrees\I18N; 10use Fisharebest\Webtrees\Individual; 11use Fisharebest\Webtrees\SurnameTradition; 12use Fisharebest\Webtrees\View; 13 14/** 15 * @var Individual|null $individual 16 * @var Fact|null $name_fact 17 */ 18 19?> 20 21<?php 22if ($individual instanceof Individual) { 23 $xref = $individual->xref(); 24 $cancel_url = $individual->url(); 25} elseif ($family !== null) { 26 $xref = $family->xref(); 27 $cancel_url = $family->url(); 28} else { 29 $cancel_url = route('admin-trees'); 30 $xref = 'new'; 31} 32 33// Different cultures do surnames differently 34$surname_tradition = SurnameTradition::create($tree->getPreference('SURNAME_TRADITION')); 35 36if ($name_fact instanceof Fact) { 37 // Editing an existing name 38 $name_fact_id = $name_fact->id(); 39 $namerec = $name_fact->gedcom(); 40 $name_fields = [ 41 'NAME' => $name_fact->value(), 42 'TYPE' => $name_fact->attribute('TYPE'), 43 'NPFX' => $name_fact->attribute('NPFX'), 44 'GIVN' => $name_fact->attribute('GIVN'), 45 'NICK' => $name_fact->attribute('NICK'), 46 'SPFX' => $name_fact->attribute('SPFX'), 47 'SURN' => $name_fact->attribute('SURN'), 48 'NSFX' => $name_fact->attribute('NSFX'), 49 ]; 50 51 // Populate any missing subfields from the NAME field 52 $npfx_accept = implode('|', Config::namePrefixes()); 53 if (preg_match('/(((' . $npfx_accept . ')\.? +)*)([^\n\/"]*)("(.*)")? *\/(([a-z]{2,3} +)*)(.*)\/ *(.*)/i', $name_fields['NAME'], $name_bits)) { 54 $name_fields['NPFX'] = $name_fields['NPFX'] ?: $name_bits[1]; 55 $name_fields['GIVN'] = $name_fields['GIVN'] ?: $name_bits[4]; 56 $name_fields['NICK'] = $name_fields['NICK'] ?: $name_bits[6]; 57 $name_fields['SPFX'] = $name_fields['SPFX'] ?: trim($name_bits[7]); 58 $name_fields['SURN'] = $name_fields['SURN'] ?: preg_replace('~/[^/]*/~', ',', $name_bits[9]); 59 $name_fields['NSFX'] = $name_fields['NSFX'] ?: $name_bits[10]; 60 } 61} else { 62 // Creating a new name 63 $name_fact_id = ''; 64 $namerec = ''; 65 $name_fields = [ 66 'NAME' => '', 67 'TYPE' => '', 68 'NPFX' => '', 69 'GIVN' => '', 70 'NICK' => '', 71 'SPFX' => '', 72 'SURN' => '', 73 'NSFX' => '', 74 ]; 75 76 // Inherit surname from parents, spouse or child 77 if ($family) { 78 $father = $family->husband(); 79 if ($father instanceof Individual && $father->facts(['NAME'])->isNotEmpty()) { 80 $father_name = $father->facts(['NAME'])->first()->value(); 81 } else { 82 $father_name = ''; 83 } 84 $mother = $family->wife(); 85 if ($mother instanceof Individual && $mother->facts(['NAME'])->isNotEmpty()) { 86 $mother_name = $mother->facts(['NAME'])->first()->value(); 87 } else { 88 $mother_name = ''; 89 } 90 } else { 91 $father = null; 92 $mother = null; 93 $father_name = ''; 94 $mother_name = ''; 95 } 96 if ($individual && $individual->facts(['NAME'])->isNotEmpty()) { 97 $indi_name = $individual->facts(['NAME'])->first()->value(); 98 } else { 99 $indi_name = ''; 100 } 101 102 switch ($nextaction) { 103 case 'add_child_to_family_action': 104 $name_fields = array_merge($name_fields, $surname_tradition->newChildNames($father_name, $mother_name, $gender)); 105 break; 106 case 'add_child_to_individual_action': 107 if ($individual->sex() === 'F') { 108 $name_fields = array_merge($name_fields, $surname_tradition->newChildNames('', $indi_name, $gender)); 109 } else { 110 $name_fields = array_merge($name_fields, $surname_tradition->newChildNames($indi_name, '', $gender)); 111 } 112 break; 113 case 'add_parent_to_individual_action': 114 $name_fields = array_merge($name_fields, $surname_tradition->newParentNames($indi_name, $gender)); 115 break; 116 case 'add_spouse_to_family_action': 117 if ($father) { 118 $name_fields = array_merge($name_fields, $surname_tradition->newSpouseNames($father_name, $gender)); 119 } else { 120 $name_fields = array_merge($name_fields, $surname_tradition->newSpouseNames($mother_name, $gender)); 121 } 122 break; 123 case 'add_spouse_to_individual_action': 124 $name_fields = array_merge($name_fields, $surname_tradition->newSpouseNames($indi_name, $gender)); 125 break; 126 case 'add_unlinked_indi_action': 127 case 'update': 128 if ($surname_tradition->hasSurnames()) { 129 $name_fields['NAME'] = '//'; 130 } 131 break; 132 } 133} 134 135$bdm = ''; // used to copy '1 SOUR' to '2 SOUR' for BIRT DEAT MARR 136 137?> 138<h2 class="wt-page-title"><?= $title ?></h2> 139 140<form method="post" onsubmit="return checkform();"> 141 <input type="hidden" name="ged" value="<?= e($tree->name()) ?>"> 142 <input type="hidden" name="action" value="<?= e($nextaction) ?>"> 143 <input type="hidden" name="fact_id" value="<?= e($name_fact_id) ?>"> 144 <input type="hidden" name="xref" value="<?= e($xref) ?>"> 145 <input type="hidden" name="famtag" value="<?= e($famtag) ?>"> 146 <input type="hidden" name="gender" value="<?= $gender ?>"> 147 <?= csrf_field() ?> 148 149 <?php if ($nextaction === 'add_child_to_family_action' || $nextaction === 'add_child_to_individual_action') : ?> 150 <?= FunctionsEdit::addSimpleTag($tree, '0 PEDI') ?> 151 <?php endif ?> 152 153 <?php 154 // First - standard name fields 155 foreach ($name_fields as $tag => $value) { 156 if (substr_compare($tag, '_', 0, 1) !== 0) { 157 echo FunctionsEdit::addSimpleTag($tree, '0 ' . $tag . ' ' . $value, '', ''); 158 } 159 } 160 161 // Second - advanced name fields 162 if ($surname_tradition->hasMarriedNames() || preg_match('/\n2 _MARNM /', $namerec)) { 163 $adv_name_fields = ['_MARNM' => '']; 164 } else { 165 $adv_name_fields = []; 166 } 167 if (preg_match_all('/(' . Gedcom::REGEX_TAG . ')/', $tree->getPreference('ADVANCED_NAME_FACTS'), $match)) { 168 foreach ($match[1] as $tag) { 169 // Ignore advanced facts that duplicate standard facts 170 if (!in_array($tag, ['TYPE', 'NPFX', 'GIVN', 'NICK', 'SPFX', 'SURN', 'NSFX'])) { 171 $adv_name_fields[$tag] = ''; 172 } 173 } 174 } 175 176 foreach (array_keys($adv_name_fields) as $tag) { 177 // Edit existing tags, grouped together 178 if (preg_match_all('/2 ' . $tag . ' (.+)/', $namerec, $match)) { 179 foreach ($match[1] as $value) { 180 echo FunctionsEdit::addSimpleTag($tree, '2 ' . $tag . ' ' . $value, '', GedcomTag::getLabel('NAME:' . $tag, $individual)); 181 if ($tag === '_MARNM') { 182 preg_match_all('/\/([^\/]*)\//', $value, $matches); 183 echo FunctionsEdit::addSimpleTag($tree, '2 _MARNM_SURN ' . implode(',', $matches[1])); 184 } 185 } 186 } 187 // Allow a new tag to be entered 188 if (!array_key_exists($tag, $name_fields)) { 189 echo FunctionsEdit::addSimpleTag($tree, '0 ' . $tag, '', GedcomTag::getLabel('NAME:' . $tag, $individual)); 190 if ($tag === '_MARNM') { 191 echo FunctionsEdit::addSimpleTag($tree, '0 _MARNM_SURN'); 192 } 193 } 194 } 195 196 // Third - new/existing custom name fields 197 foreach ($name_fields as $tag => $value) { 198 if (substr_compare($tag, '_', 0, 1) === 0) { 199 echo FunctionsEdit::addSimpleTag($tree, '0 ' . $tag . ' ' . $value); 200 if ($tag === '_MARNM') { 201 preg_match_all('/\/([^\/]*)\//', $value, $matches); 202 echo FunctionsEdit::addSimpleTag($tree, '2 _MARNM_SURN ' . implode(',', $matches[1])); 203 } 204 } 205 } 206 207 // Fourth - SOUR, NOTE, _CUSTOM, etc. 208 if ($namerec !== '') { 209 $gedlines = explode("\n", $namerec); // -- find the number of lines in the record 210 $fields = explode(' ', $gedlines[0]); 211 $glevel = $fields[0]; 212 $level = $glevel; 213 $type = $fields[1]; 214 $tags = []; 215 $i = 0; 216 do { 217 if ($type !== 'TYPE' && !array_key_exists($type, $name_fields) && !array_key_exists($type, $adv_name_fields)) { 218 $text = ''; 219 for ($j = 2; $j < count($fields); $j++) { 220 if ($j > 2) { 221 $text .= ' '; 222 } 223 $text .= $fields[$j]; 224 } 225 while (($i + 1 < count($gedlines)) && (preg_match('/' . ($level + 1) . ' CONT ?(.*)/', $gedlines[$i + 1], $cmatch) > 0)) { 226 $text .= "\n" . $cmatch[1]; 227 $i++; 228 } 229 echo FunctionsEdit::addSimpleTag($tree, $level . ' ' . $type . ' ' . $text); 230 } 231 $tags[] = $type; 232 $i++; 233 if (isset($gedlines[$i])) { 234 $fields = explode(' ', $gedlines[$i]); 235 $level = $fields[0]; 236 if (isset($fields[1])) { 237 $type = $fields[1]; 238 } 239 } 240 } while (($level > $glevel) && ($i < count($gedlines))); 241 } 242 243 // If we are adding a new individual, add the basic details 244 if ($nextaction !== 'update') { 245 echo '</table><br><table class="table wt-facts-table">'; 246 // 1 SEX 247 if ($famtag === 'HUSB' || $gender === 'M') { 248 echo FunctionsEdit::addSimpleTag($tree, '0 SEX M'); 249 } elseif ($famtag === 'WIFE' || $gender === 'F') { 250 echo FunctionsEdit::addSimpleTag($tree, '0 SEX F'); 251 } else { 252 echo FunctionsEdit::addSimpleTag($tree, '0 SEX U'); 253 } 254 $bdm = 'BD'; 255 if (preg_match_all('/(' . Gedcom::REGEX_TAG . ')/', $tree->getPreference('QUICK_REQUIRED_FACTS'), $matches)) { 256 foreach ($matches[1] as $match) { 257 if (!in_array($match, Gedcom::DEATH_EVENTS)) { 258 FunctionsEdit::addSimpleTags($tree, $match); 259 } 260 } 261 } 262 //-- if adding a spouse add the option to add a marriage fact to the new family 263 if ($nextaction === 'add_spouse_to_individual_action' || $nextaction === 'add_spouse_to_family_action') { 264 $bdm .= 'M'; 265 if (preg_match_all('/(' . Gedcom::REGEX_TAG . ')/', $tree->getPreference('QUICK_REQUIRED_FAMFACTS'), $matches)) { 266 foreach ($matches[1] as $match) { 267 FunctionsEdit::addSimpleTags($tree, $match); 268 } 269 } 270 } 271 if (preg_match_all('/(' . Gedcom::REGEX_TAG . ')/', $tree->getPreference('QUICK_REQUIRED_FACTS'), $matches)) { 272 foreach ($matches[1] as $match) { 273 if (in_array($match, Gedcom::DEATH_EVENTS)) { 274 FunctionsEdit::addSimpleTags($tree, $match); 275 } 276 } 277 } 278 } 279 280 echo '</table>'; 281 if ($nextaction === 'update') { 282 // GEDCOM 5.5.1 spec says NAME doesn’t get a OBJE 283 echo view('cards/add-source-citation', [ 284 'level' => 2, 285 'full_citations' => $tree->getPreference('FULL_SOURCES'), 286 'tree' => $tree, 287 ]); 288 echo view('cards/add-note', [ 289 'level' => 2, 290 'tree' => $tree, 291 ]); 292 echo view('cards/add-shared-note', [ 293 'level' => 2, 294 'tree' => $tree, 295 ]); 296 echo view('cards/add-restriction', [ 297 'level' => 2, 298 'tree' => $tree, 299 ]); 300 } else { 301 echo view('cards/add-source-citation', [ 302 'bdm' => $bdm, 303 'level' => 1, 304 'full_citations' => $tree->getPreference('FULL_SOURCES'), 305 'prefer_level2_sources' => $tree->getPreference('PREFER_LEVEL2_SOURCES'), 306 'quick_required_facts' => $tree->getPreference('QUICK_REQUIRED_FACTS'), 307 'quick_required_famfacts' => $tree->getPreference('QUICK_REQUIRED_FAMFACTS'), 308 'tree' => $tree, 309 ]); 310 echo view('cards/add-note', [ 311 'level' => 1, 312 'tree' => $tree, 313 ]); 314 echo view('cards/add-shared-note', [ 315 'level' => 1, 316 'tree' => $tree, 317 ]); 318 echo view('cards/add-restriction', [ 319 'level' => 1, 320 'tree' => $tree, 321 ]); 322 } 323 324 ?> 325 <div class="row form-group"> 326 <div class="col-sm-9 offset-sm-3"> 327 <button class="btn btn-primary" type="submit"> 328 <?= view('icons/save') ?> 329 <?= /* I18N: A button label. */ 330 I18N::translate('save') ?> 331 </button> 332 <?php if (preg_match('/^add_(child|spouse|parent|unlinked_indi)/', $nextaction)) : ?> 333 <button class="btn btn-primary" type="submit" name="goto" value="<?= $xref ?>"> 334 <?= view('icons/save') ?> 335 <?= /* I18N: A button label. */ 336 I18N::translate('go to new individual') ?> 337 </button> 338 <?php endif ?> 339 <a class="btn btn-secondary" href="<?= e($cancel_url) ?>"> 340 <?= view('icons/cancel') ?> 341 <?= /* I18N: A button label. */ 342 I18N::translate('cancel') ?> 343 </a> 344 345 <?php if ($name_fact instanceof Fact && (Auth::isAdmin() || $tree->getPreference('SHOW_GEDCOM_RECORD'))) : ?> 346 <a class="btn btn-link" href="<?= e(route('edit-raw-fact', ['xref' => $xref, 'fact_id' => $name_fact->id(), 'ged' => $tree->name()])) ?>"> 347 <?= I18N::translate('Edit the raw GEDCOM') ?> 348 </a> 349 <?php endif ?> 350 </div> 351 </div> 352</form> 353 354<?= view('modals/on-screen-keyboard') ?> 355<?= view('modals/ajax') ?> 356<?= view('edit/initialize-calendar-popup') ?> 357 358<?php View::push('javascript') ?> 359<script> 360 var SURNAME_TRADITION = <?= json_encode($tree->getPreference('SURNAME_TRADITION')) ?>; 361 var gender = <?= json_encode($gender) ?>; 362 var famtag = <?= json_encode($famtag) ?>; 363 364 var NAME = $("[name=NAME]"); 365 366 function trim(str) { 367 str = str.replace(/\s\s+/g, " "); 368 return str.replace(/(^\s+)|(\s+$)/g, ""); 369 } 370 371 function lang_class(str) { 372 if (str.match(/[\u0370-\u03FF]/)) return "greek"; 373 if (str.match(/[\u0400-\u04FF]/)) return "cyrillic"; 374 if (str.match(/[\u0590-\u05FF]/)) return "hebrew"; 375 if (str.match(/[\u0600-\u06FF]/)) return "arabic"; 376 return "latin"; // No matched text implies latin :-) 377 } 378 379 // Generate a full name from the name components 380 function generate_name() { 381 var npfx = $("[name=NPFX]").val(); 382 var givn = $("[name=GIVN]").val(); 383 var spfx = $("[name=SPFX]").val(); 384 var surn = $("[name=SURN]").val(); 385 var nsfx = $("[name=NSFX]").val(); 386 387 if (SURNAME_TRADITION === "polish" && (gender === "F" || famtag === "WIFE")) { 388 surn = surn.replace(/ski$/, "ska"); 389 surn = surn.replace(/cki$/, "cka"); 390 surn = surn.replace(/dzki$/, "dzka"); 391 surn = surn.replace(/żki$/, "żka"); 392 } 393 394 // Commas are used in the GIVN and SURN field to separate lists of surnames. 395 // For example, to differentiate the two Spanish surnames from an English 396 // double-barred name. 397 // Commas *may* be used in other fields, and will form part of the NAME. 398 var locale = document.documentElement.lang; 399 if (locale === "vi" || locale === "hu") { 400 // Default format: /SURN/ GIVN 401 return trim(npfx + " /" + trim(spfx + " " + surn).replace(/ *, */g, " ") + "/ " + givn.replace(/ *, */g, " ") + " " + nsfx); 402 } else if (locale === "zh-Hans" || locale === "zh-Hant") { 403 // Default format: /SURN/GIVN 404 return npfx + "/" + spfx + surn + "/" + givn + nsfx; 405 } else { 406 // Default format: GIVN /SURN/ 407 return trim(npfx + " " + givn.replace(/ *, */g, " ") + " /" + trim(spfx + " " + surn).replace(/ *, */g, " ") + "/ " + nsfx); 408 } 409 } 410 411 // Update the NAME and _MARNM fields from the name components 412 // and also display the value in read-only "gedcom" format. 413 function updatewholename() { 414 // Don’t update the name if the user manually changed it 415 if (manualChange) { 416 return; 417 } 418 419 var npfx = $("[name=NPFX]").val(); 420 var givn = $("[name=GIVN]").val(); 421 var spfx = $("[name=SPFX]").val(); 422 var surn = $("[name=SURN]").val(); 423 var nsfx = $("[name=NSFX]").val(); 424 var name = generate_name(); 425 426 var display_id = NAME.attr("id") + "_display"; 427 428 NAME.val(name); 429 $("#" + display_id).text(name); 430 // Married names inherit some NSFX values, but not these 431 nsfx = nsfx.replace(/^(I|II|III|IV|V|VI|Junior|Jr\.?|Senior|Sr\.?)$/i, ""); 432 // Update _MARNM field from _MARNM_SURN field and display it 433 // Be careful of mixing latin/hebrew/etc. character sets. 434 var ip = document.getElementsByTagName("input"); 435 var locale = document.documentElement.lang; 436 var marnm_id = ""; 437 var romn = ""; 438 var heb = ""; 439 for (var i = 0; i < ip.length; i++) { 440 var val = trim(ip[i].value); 441 if (ip[i].id.indexOf("_HEB") === 0) 442 heb = val; 443 if (ip[i].id.indexOf("ROMN") === 0) 444 romn = val; 445 if (ip[i].id.indexOf("_MARNM") === 0) { 446 if (ip[i].id.indexOf("_MARNM_SURN") === 0) { 447 var msurn = ""; 448 if (val !== "") { 449 var lc = lang_class(document.getElementById(ip[i].id).value); 450 if (lang_class(name) === lc) { 451 // See generate_name() 452 if (locale === "vi" || locale === "hu") { 453 // Default format: /SURN/ GIVN 454 msurn = trim(npfx + " /" + val + "/ " + givn + " " + nsfx); 455 } else { 456 // Default format: GIVN /SURN/ 457 msurn = trim(npfx + " " + givn + " /" + val + "/ " + nsfx); 458 } 459 } else if (lc === "hebrew") { 460 msurn = heb.replace(/\/.*\//, "/" + val + "/"); 461 } else if (lang_class(romn) === lc) { 462 msurn = romn.replace(/\/.*\//, "/" + val + "/"); 463 } 464 } 465 document.getElementById(marnm_id).value = msurn; 466 document.getElementById(marnm_id + "_display").innerHTML = msurn; 467 } else { 468 marnm_id = ip[i].id; 469 } 470 } 471 } 472 } 473 474 // Toggle the name editor fields between 475 // <input type="hidden"> <span style="display:inline"> 476 // <input type="text"> <span style="display:none"> 477 478 var oldName = ""; 479 480 // Calls to generate_name() trigger an update - hence need to 481 // set the manual change to true first. We are probably 482 // listening to the wrong events on the input fields... 483 var manualChange = generate_name() !== NAME.val(); 484 485 function convertHidden(eid) { 486 var input1 = $("#" + eid); 487 var input2 = $("#" + eid + "_display"); 488 // Note that IE does not allow us to change the type of an input, so we must create a new one. 489 if (input1.attr("type") === "hidden") { 490 input1.replaceWith(input1.clone().attr("type", "text")); 491 input2.hide(); 492 } else { 493 input1.replaceWith(input1.clone().attr("type", "hidden")); 494 input2.show(); 495 } 496 } 497 498 /** 499 * if the user manually changed the NAME field, then update the textual 500 * HTML representation of it 501 * If the value changed set manualChange to true so that changing 502 * the other fields doesn’t change the NAME line 503 */ 504 function updateTextName(eid) { 505 var element = document.getElementById(eid); 506 if (element) { 507 if (element.value !== oldName) { 508 manualChange = true; 509 } 510 var delement = document.getElementById(eid + "_display"); 511 if (delement) { 512 delement.innerHTML = element.value; 513 } 514 } 515 } 516 517 function checkform() { 518 var ip = document.getElementsByTagName("input"); 519 for (var i = 0; i < ip.length; i++) { 520 // ADD slashes to _HEB and _AKA names 521 if (ip[i].id.indexOf("_AKA") === 0 || ip[i].id.indexOf("_HEB") === 0 || ip[i].id.indexOf("ROMN") === 0) 522 if (ip[i].value.indexOf("/") < 0 && ip[i].value !== "") 523 ip[i].value = ip[i].value.replace(/([^\s]+)\s*$/, "/$1/"); 524 // Blank out temporary _MARNM_SURN 525 if (ip[i].id.indexOf("_MARNM_SURN") === 0) 526 ip[i].value = ""; 527 // Convert "xxx yyy" and "xxx y yyy" surnames to "xxx,yyy" 528 if ((SURNAME_TRADITION === "spanish" || "SURNAME_TRADITION" === "portuguese") && ip[i].id.indexOf("SURN") === 0) { 529 ip[i].value = document.forms[0].SURN.value.replace(/^\s*([^\s,]{2,})\s+([iIyY] +)?([^\s,]{2,})\s*$/, "$1,$3"); 530 } 531 } 532 return true; 533 } 534 535 // If the name isn’t initially formed from the components in a standard way, 536 // then don’t automatically update it. 537 if (NAME.val() !== generate_name() && NAME.val() !== "//") { 538 convertHidden(NAME.attr("id")); 539 } 540</script> 541<?php View::endpush() ?> 542 543