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