. */ namespace Fisharebest\Webtrees\Functions; use Fisharebest\Webtrees\Auth; use Fisharebest\Webtrees\Bootstrap4; use Fisharebest\Webtrees\Census\Census; use Fisharebest\Webtrees\Census\CensusOfCzechRepublic; use Fisharebest\Webtrees\Census\CensusOfDenmark; use Fisharebest\Webtrees\Census\CensusOfDeutschland; use Fisharebest\Webtrees\Census\CensusOfEngland; use Fisharebest\Webtrees\Census\CensusOfFrance; use Fisharebest\Webtrees\Census\CensusOfScotland; use Fisharebest\Webtrees\Census\CensusOfUnitedStates; use Fisharebest\Webtrees\Census\CensusOfWales; use Fisharebest\Webtrees\Config; use Fisharebest\Webtrees\Date; use Fisharebest\Webtrees\Fact; use Fisharebest\Webtrees\Family; use Fisharebest\Webtrees\Filter; use Fisharebest\Webtrees\FontAwesome; use Fisharebest\Webtrees\GedcomCode\GedcomCodeAdop; use Fisharebest\Webtrees\GedcomCode\GedcomCodeName; use Fisharebest\Webtrees\GedcomCode\GedcomCodePedi; use Fisharebest\Webtrees\GedcomCode\GedcomCodeQuay; use Fisharebest\Webtrees\GedcomCode\GedcomCodeRela; use Fisharebest\Webtrees\GedcomCode\GedcomCodeStat; use Fisharebest\Webtrees\GedcomCode\GedcomCodeTemp; use Fisharebest\Webtrees\GedcomRecord; use Fisharebest\Webtrees\GedcomTag; use Fisharebest\Webtrees\Html; use Fisharebest\Webtrees\I18N; use Fisharebest\Webtrees\Individual; use Fisharebest\Webtrees\Media; use Fisharebest\Webtrees\Module; use Fisharebest\Webtrees\Module\CensusAssistantModule; use Fisharebest\Webtrees\Note; use Fisharebest\Webtrees\Repository; use Fisharebest\Webtrees\Select2; use Fisharebest\Webtrees\Source; use Fisharebest\Webtrees\User; use Fisharebest\Webtrees\View; use Ramsey\Uuid\Uuid; /** * Class FunctionsEdit - common functions for editing */ class FunctionsEdit { /** * Function edit_language_checkboxes * * @param string $parameter_name * @param array $accepted_languages * * @return string */ public static function editLanguageCheckboxes($parameter_name, $accepted_languages) { $html = ''; foreach (I18N::activeLocales() as $locale) { $html .= '
'; $html .= ''; $html .= '
'; } return $html; } /** * A list of access levels (e.g. for an edit control). * * @return string[] */ public static function optionsAccessLevels() { return [ Auth::PRIV_PRIVATE => I18N::translate('Show to visitors'), Auth::PRIV_USER => I18N::translate('Show to members'), Auth::PRIV_NONE => I18N::translate('Show to managers'), Auth::PRIV_HIDE => I18N::translate('Hide from everyone'), ]; } /** * A list of active languages (e.g. for an edit control). * * @return string[] */ public static function optionsActiveLanguages() { $languages = []; foreach (I18N::activeLocales() as $locale) { $languages[$locale->languageTag()] = $locale->endonym(); } return $languages; } /** * A list of calendar conversions (e.g. for an edit control). * * @return string[] */ public static function optionsCalendarConversions() { return ['none' => I18N::translate('No calendar conversion')] + Date::calendarNames(); } /** * A list of contact methods (e.g. for an edit control). * * @return string[] */ public static function optionsContactMethods() { return [ 'messaging' => I18N::translate('Internal messaging'), 'messaging2' => I18N::translate('Internal messaging with emails'), 'messaging3' => I18N::translate('webtrees sends emails with no storage'), 'mailto' => I18N::translate('Mailto link'), 'none' => I18N::translate('No contact'), ]; } /** * A list of hide/show options (e.g. for an edit control). * * @return string[] */ public static function optionsHideShow() { return [ '0' => I18N::translate('no'), '1' => I18N::translate('yes'), ]; } /** * A list of installed languages (e.g. for an edit control). * * @return string[] */ public static function optionsInstalledLanguages() { $languages = []; foreach (I18N::installedLocales() as $locale) { $languages[$locale->languageTag()] = $locale->endonym(); } return $languages; } /** * A list of integers (e.g. for an edit control). * * @param int[] $integers * * @return string[] */ public static function numericOptions($integers) { $array = []; foreach ($integers as $integer) { if ($integer === -1) { $array[$integer] = I18N::translate('All'); } else { $array[$integer] = I18N::number($integer); } } return $array; } /** * A list of no/yes options (e.g. for an edit control). * * @return string[] */ public static function optionsNoYes() { return [ '0' => I18N::translate('no'), '1' => I18N::translate('yes'), ]; } /** * A list of GEDCOM relationships (e.g. for an edit control). * * @param string $relationship * * @return string[] */ public static function optionsRelationships($relationship) { $relationships = GedcomCodeRela::getValues(); // The user is allowed to specify values that aren't in the list. if (!array_key_exists($relationship, $relationships)) { $relationships[$relationship] = I18N::translate($relationship); } return $relationships; } /** * A list of registration rules (e.g. for an edit control). * * @return string[] */ public static function optionsRegistrationRules() { return [ 0 => I18N::translate('No predefined text'), 1 => I18N::translate('Predefined text that states all users can request a user account'), 2 => I18N::translate('Predefined text that states admin will decide on each request for a user account'), 3 => I18N::translate('Predefined text that states only family members can request a user account'), 4 => I18N::translate('Choose user defined welcome text typed below'), ]; } /** * A list of GEDCOM restrictions (e.g. for an edit control). * * @param bool $include_empty * * @return string[] */ public static function optionsRestrictions($include_empty) { $options = [ 'none' => I18N::translate('Show to visitors'), // Not valid GEDCOM, but very useful 'privacy' => I18N::translate('Show to members'), 'confidential' => I18N::translate('Show to managers'), 'locked' => I18N::translate('Only managers can edit'), ]; if ($include_empty) { $options = ['' => ''] + $options; } return $options; } /** * A list mail transport options (e.g. for an edit control). * * @return string[] */ public static function optionsMailTransports() { return [ 'internal' => I18N::translate('Use PHP mail to send messages'), 'external' => I18N::translate('Use SMTP to send messages'), ]; } /** * A list SSL modes (e.g. for an edit control). * * @return string[] */ public static function optionsSslModes() { return [ 'none' => I18N::translate('none'), /* I18N: Secure Sockets Layer - a secure communications protocol*/ 'ssl' => I18N::translate('ssl'), /* I18N: Transport Layer Security - a secure communications protocol */ 'tls' => I18N::translate('tls'), ]; } /** * A list of temple options (e.g. for an edit control). * * @return string[] */ public static function optionsTemples() { return ['' => I18N::translate('No temple - living ordinance')] + GedcomCodeTemp::templeNames(); } /** * A list of user options (e.g. for an edit control). * * @return string[] */ public static function optionsUsers() { $options = ['' => '-']; foreach (User::all() as $user) { $options[$user->getUserName()] = $user->getRealName() . ' - ' . $user->getUserName(); } return $options; } /** * Create a form control to select a family. * * @param Family|null $family * @param string[] $attributes * * @return string */ public static function formControlFamily(Family $family = null, array $attributes = []) { $value = ''; $options = ['' => '']; if ($family !== null) { $value = $family->getXref(); $options = [$value => View::make('selects/family', ['family' => $family])]; } return Bootstrap4::select($options, $value, Select2::familyConfig() + $attributes); } /** * Create a form control to select a flag. * * @param string $flag * @param string[] $attributes * * @return string */ public static function formControlFlag($flag, array $attributes = []) { $value = ''; $options = ['' => '']; if ($flag !== '') { $value = $flag; $options = [$value => Select2::flagValue($flag)]; } return Bootstrap4::select($options, $value, Select2::flagConfig() + $attributes); } /** * Create a form control to select an individual. * * @param Individual|null $individual * @param string[] $attributes * * @return string */ public static function formControlIndividual(Individual $individual = null, array $attributes = []) { $value = ''; $options = ['' => '']; if ($individual !== null) { $value = $individual->getXref(); $options = [$value => View::make('selects/individual', ['individual' => $individual])]; } return Bootstrap4::select($options, $value, Select2::individualConfig() + $attributes); } /** * Create a form control to select a media object. * * @param Media|null $media * @param string[] $attributes * * @return string */ public static function formControlMediaObject(Media $media = null, array $attributes = []) { $value = ''; $options = ['' => '']; if ($media !== null) { $value = $media->getXref(); $options = [$value => View::make('selects/media', ['media' => $media])]; } return Bootstrap4::select($options, $value, Select2::mediaObjectConfig() + $attributes); } /** * Create a form control to select a note. * * @param Note|null $note * @param string[]|null $attributes * * @return string */ public static function formControlNote(Note $note = null, array $attributes = []) { $value = ''; $options = ['' => '']; if ($note !== null) { $value = $note->getXref(); $options = [$value => View::make('selects/note', ['note' => $note])]; } return Bootstrap4::select($options, $value, Select2::noteConfig() + $attributes); } /** * Create a form control to select a place. * * @param string $place * @param string[] $attributes * * @return string */ public static function formControlPlace($place, array $attributes = []) { $value = ''; $options = ['' => '']; if ($place !== '') { $options = [$place => $place]; } return Bootstrap4::select($options, $value, Select2::placeConfig() + $attributes); } /** * Create a form control to select a repository. * * @param Repository|null $repository * @param string[] $attributes * * @return string */ public static function formControlRepository(Repository $repository = null, array $attributes = []) { $value = ''; $options = ['' => '']; if ($repository !== null) { $value = $repository->getXref(); $options = [$value => View::make('selects/repository', ['repository' => $repository])]; } return Bootstrap4::select($options, $value, Select2::repositoryConfig() + $attributes); } /** * Create a form control to select a source. * * @param Source|null $source * @param string[] $attributes * * @return string */ public static function formControlSource(Source $source = null, array $attributes = []) { $value = ''; $options = ['' => '']; if ($source !== null) { $value = $source->getXref(); $options = [$value => View::make('selects/source', ['source' => $source])]; } return Bootstrap4::select($options, $value, Select2::sourceConfig() + $attributes); } /** * Create a form control to select a submitter. * * @param GedcomRecord|null $submitter * @param string[] $attributes * * @return string */ public static function formControlSubmitter(GedcomRecord $submitter = null, array $attributes = []) { $value = ''; $options = ['' => '']; if ($submitter !== null) { $value = $submitter->getXref(); $options = [$value => View::make('selects/submitter', ['submitter' => $submitter])]; } return Bootstrap4::select($options, $value, Select2::submitterConfig() + $attributes); } /** * Remove all links from $gedrec to $xref, and any sub-tags. * * @param string $gedrec * @param string $xref * * @return string */ public static function removeLinks($gedrec, $xref) { $gedrec = preg_replace('/\n1 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[2-9].*)*/', '', $gedrec); $gedrec = preg_replace('/\n2 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[3-9].*)*/', '', $gedrec); $gedrec = preg_replace('/\n3 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[4-9].*)*/', '', $gedrec); $gedrec = preg_replace('/\n4 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[5-9].*)*/', '', $gedrec); $gedrec = preg_replace('/\n5 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[6-9].*)*/', '', $gedrec); return $gedrec; } /** * Input addon to generate a calendar widget. * * @param string $id * * @return string */ public static function inputAddonCalendar($id) { return '' . FontAwesome::linkIcon('calendar', I18N::translate('Select a date'), ['href' => '#', 'onclick' => 'return calendarWidget("caldiv' . $id . '", "' . $id . '");']) . ''; } /** * Input addon to select a special characterr using a virtual keyboard * * @param string $id * * @return string */ public static function inputAddonKeyboard($id) { return '' . FontAwesome::linkIcon('keyboard', I18N::translate('Find a special character'), ['class' => 'wt-osk-trigger', 'href' => '#', 'data-id' => $id]) . ''; } /** * Input addon to generate a help link. * * @param string $fact * * @return string */ public static function inputAddonHelp($fact) { return '' . FunctionsPrint::helpLink($fact) . ''; } /** * add a new tag input field * * called for each fact to be edited on a form. * Fact level=0 means a new empty form : data are POSTed by name * else data are POSTed using arrays : * glevels[] : tag level * islink[] : tag is a link * tag[] : tag name * text[] : tag value * * @param string $tag fact record to edit (eg 2 DATE xxxxx) * @param string $upperlevel optional upper level tag (eg BIRT) * @param string $label An optional label to echo instead of the default * @param string $extra optional text to display after the input field * @param Individual $person For male/female translations * * @return string */ public static function addSimpleTag($tag, $upperlevel = '', $label = '', $extra = null, Individual $person = null) { global $tags, $xref, $bdm, $action, $WT_TREE; // Some form fields need access to previous form fields. static $previous_ids = ['SOUR' => '', 'PLAC' => '']; preg_match('/^(?:(\d+) (' . WT_REGEX_TAG . ') ?(.*))/', $tag, $match); list(, $level, $fact, $value) = $match; if ($level === '0') { if ($upperlevel) { $name = $upperlevel . '_' . $fact; } else { $name = $fact; } } else { $name = 'text[]'; } if ($level === '0') { $id = $fact; } else { $id = $fact . Uuid::uuid4(); } if ($upperlevel) { $id = $upperlevel . '_' . $fact . Uuid::uuid4(); } $previous_ids[$fact] = $id; // field value $islink = (substr($value, 0, 1) === '@' && substr($value, 0, 2) !== '@#'); if ($islink) { $value = trim(substr($tag, strlen($fact) + 3), ' @\r'); } else { $value = (string) substr($tag, strlen($fact) + 3); } if ($fact === 'REPO' || $fact === 'SOUR' || $fact === 'OBJE' || $fact === 'FAMC') { $islink = true; } if ($fact === 'SHARED_NOTE_EDIT' || $fact === 'SHARED_NOTE') { $islink = true; $fact = 'NOTE'; } $row_class = 'form-group row'; switch ($fact) { case 'DATA': case 'MAP': // These GEDCOM tags should have no data, just child tags. if ($value === '') { $row_class .= ' d-none'; } break; case 'LATI': case 'LONG': // Indicate that this row is a child of a previous row, so we can expand/collapse them. $row_class .= ' child_of_' . $previous_ids['PLAC']; if ($value === '') { $row_class .= ' collapse'; } break; } $html = ''; $html .= '
'; $html .= ''; // value $html .= '
'; // Show names for spouses in MARR/HUSB/AGE and MARR/WIFE/AGE if ($fact === 'HUSB' || $fact === 'WIFE') { $family = Family::getInstance($xref, $WT_TREE); if ($family) { $spouse_link = $family->getFirstFact($fact); if ($spouse_link) { $spouse = $spouse_link->getTarget(); if ($spouse) { $html .= $spouse->getFullName(); } } } } if (in_array($fact, Config::emptyFacts()) && ($value === '' || $value === 'Y' || $value === 'y')) { $html .= ''; if ($fact === 'CENS' && $value === 'Y') { $html .= self::censusDateSelector(WT_LOCALE, $xref); /** @var CensusAssistantModule $census_assistant */ $census_assistant = Module::getModuleByName('GEDFact_assistant'); $record = Individual::getInstance($xref, $WT_TREE); if ($census_assistant !== null && $record instanceof Individual) { $html .= $census_assistant->createCensusAssistant($record); } } } elseif ($fact === 'NPFX' || $fact === 'NSFX' || $fact === 'SPFX' || $fact === 'NICK') { $html .= ''; } elseif ($fact === 'GIVN') { $html .= ''; } elseif ($fact === 'SURN' || $fact === '_MARNM_SURN') { $html .= ''; } elseif ($fact === 'ADOP') { $html .= Bootstrap4::select(GedcomCodeAdop::getValues($person), $value, ['id' => $id, 'name' => $name]); } elseif ($fact === 'ALIA') { $html .= self::formControlIndividual(Individual::getInstance($value, $WT_TREE), ['id' => $id, 'name' => $name]); } elseif ($fact === 'ASSO' || $fact === '_ASSO') { $html .= '
' . '' . self::formControlIndividual(Individual::getInstance($value, $WT_TREE), ['id' => $id, 'name' => $name]) . '
'; if ($level === '1') { $html .= '

' . I18N::translate('An associate is another individual who was involved with this individual, such as a friend or an employer.') . '

'; } else { $html .= '

' . I18N::translate('An associate is another individual who was involved with this fact or event, such as a witness or a priest.') . '

'; } } elseif ($fact === 'DATE') { $html .= '
'; $html .= ''; $html .= self::inputAddonCalendar($id); $html .= self::inputAddonHelp('DATE'); $html .= '
'; $html .= ''; $html .= '

' . (new Date($value))->display() . '

'; } elseif ($fact === 'FAMC') { $html .= '
' . '' . self::formControlFamily(Family::getInstance($value, $WT_TREE), ['id' => $id, 'name' => $name]) . '
'; } elseif ($fact === 'LATI') { $html .= ''; } elseif ($fact === 'LONG') { $html .= ''; } elseif ($fact === 'NOTE' && $islink) { $html .= '
' . '' . self::formControlNote(Note::getInstance($value, $WT_TREE), ['id' => $id, 'name' => $name]) . '
'; } elseif ($fact === 'OBJE') { $html .= '
' . '' . self::formControlMediaObject(Media::getInstance($value, $WT_TREE), ['id' => $id, 'name' => $name]) . '
'; } elseif ($fact === 'PAGE') { $html .= ''; } elseif ($fact === 'PEDI') { $html .= Bootstrap4::select(GedcomCodePedi::getValues($person), $value, ['id' => $id, 'name' => $name]); } elseif ($fact === 'PLAC') { $html .= '
'; $html .= self::formControlPlace($value, ['id' => $id, 'name' => $name]); $html .= '' . FontAwesome::linkIcon('coordinates', I18N::translate('Latitude') . ' / ' . I18N::translate('Longitude'), ['data-toggle' => 'collapse', 'data-target' => '.child_of_' . $id]) . ''; $html .= self::inputAddonHelp('PLAC'); $html .= '
'; if (Module::getModuleByName('places_assistant')) { \PlacesAssistantModule::setup_place_subfields($id); \PlacesAssistantModule::print_place_subfields($id); } } elseif ($fact === 'QUAY') { $html .= Bootstrap4::select(GedcomCodeQuay::getValues(), $value, ['id' => $id, 'name' => $name]); } elseif ($fact === 'RELA') { $html .= Bootstrap4::select(FunctionsEdit::optionsRelationships($value), $value, ['id' => $id, 'name' => $name]); } elseif ($fact === 'REPO') { $html .= '
' . '' . self::formControlRepository(Individual::getInstance($value, $WT_TREE), ['id' => $id, 'name' => $name]) . '
'; } elseif ($fact === 'RESN') { $html .= '
'; $html .= Bootstrap4::select(FunctionsEdit::optionsRestrictions(true), $value, ['id' => $id, 'name' => $name]); $html .= self::inputAddonHelp('RESN'); $html .= ''; $html .= '
'; } elseif ($fact === 'SEX') { $html .= Bootstrap4::radioButtons($name, ['M' => I18N::translate('Male'), 'F' => I18N::translate('Female'), 'U' => I18N::translateContext('unknown gender', 'Unknown')], $value, true); } elseif ($fact === 'SOUR') { $html .= '
' . '' . self::formControlSource(Source::getInstance($value, $WT_TREE), ['id' => $id, 'name' => $name]) . '
'; } elseif ($fact === 'STAT') { $html .= Bootstrap4::select(GedcomCodeStat::statusNames($upperlevel), $value); } elseif ($fact === 'SUBM') { $html .= '
' . '' . self::formControlSubmitter(GedcomRecord::getInstance($value, $WT_TREE), ['id' => $id, 'name' => $name]) . '
'; } elseif ($fact === 'TEMP') { $html .= Bootstrap4::select(FunctionsEdit::optionsTemples(), $value, ['id' => $id, 'name' => $name]); } elseif ($fact === 'TIME') { $html .= ''; } elseif ($fact === '_WT_USER') { $html .= Bootstrap4::select(FunctionsEdit::optionsUsers(), $value, ['id' => $id, 'name' => $name]); } elseif ($fact === '_PRIM') { $html .= Bootstrap4::select(['' => '', 'Y' => I18N::translate('always'), 'N' => I18N::translate('never')], $value, ['id' => $id, 'name' => $name]); $html .= '

' . I18N::translate('Use this image for charts and on the individual’s page.') . '

'; } elseif ($fact === 'TYPE' && $level === '3') { //-- Build the selector for the Media 'TYPE' Fact $html .= ''; } elseif (($fact !== 'NAME' || $upperlevel === 'REPO' || $upperlevel === 'UNKNOWN') && $fact !== '_MARNM') { if ($fact === 'TEXT' || $fact === 'ADDR' || ($fact === 'NOTE' && !$islink)) { $html .= '
'; $html .= ''; $html .= self::inputAddonKeyboard($id); $html .= '
'; } else { // If using GEDFact-assistant window $html .= ''; } } else { // Populated in javascript from sub-tags $html .= ''; $html .= '' . Html::escape($value) . ''; $html .= ' '; } // MARRiage TYPE : hide text field and show a selection list if ($fact === 'TYPE' && $level === '2' && $tags[0] === 'MARR') { $html .= ''; $html .= ''; } elseif ($fact === 'TYPE' && $level === '0') { // NAME TYPE : hide text field and show a selection list $html .= Bootstrap4::select(GedcomCodeName::getValues($person), $value, ['id' => $id, 'name' => $name, 'oninput' => 'document.getElementById(\'' . $id . '\').value=this.value"']); $html .= ''; } // popup links switch ($fact) { case 'SOUR': //-- checkboxes to apply '1 SOUR' to BIRT/MARR/DEAT as '2 SOUR' if ($level === '1') { $html .= '
'; switch ($WT_TREE->getPreference('PREFER_LEVEL2_SOURCES')) { case '2': // records $level1_checked = 'checked'; $level2_checked = ''; break; case '1': // facts $level1_checked = ''; $level2_checked = 'checked'; break; case '0': // none default: $level1_checked = ''; $level2_checked = ''; break; } if (strpos($bdm, 'B') !== false) { $html .= ' '; if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('QUICK_REQUIRED_FACTS'), $matches)) { foreach ($matches[1] as $match) { if (!in_array($match, explode('|', WT_EVENTS_DEAT))) { $html .= ' '; } } } } if (strpos($bdm, 'D') !== false) { if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('QUICK_REQUIRED_FACTS'), $matches)) { foreach ($matches[1] as $match) { if (in_array($match, explode('|', WT_EVENTS_DEAT))) { $html .= ' '; } } } } if (strpos($bdm, 'M') !== false) { $html .= ' '; if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('QUICK_REQUIRED_FAMFACTS'), $matches)) { foreach ($matches[1] as $match) { $html .= ' '; } } } } break; } $html .= '
'; // pastable values if ($fact === 'FORM' && $upperlevel === 'OBJE') { FunctionsPrint::printAutoPasteLink($id, Config::fileFormats()); } $html .= '
' . $extra . '
'; return $html; } /** * Genearate a ' . $options . ''; } /** * Prints collapsable fields to add ASSO/RELA, SOUR, OBJE, etc. * * @param string $tag * @param int $level * @param string $parent_tag */ public static function printAddLayer($tag, $level = 2, $parent_tag = '') { global $WT_TREE; switch ($tag) { case 'SOUR': echo View::make('cards/add-source-citation', [ 'level' => $level, 'full_citations' => $WT_TREE->getPreference('FULL_SOURCES'), ]); break; case 'ASSO': case 'ASSO2': echo View::make('cards/add-associate', [ 'level' => $level, ]); break; case 'NOTE': echo View::make('cards/add-note', [ 'level' => $level, ]); break; case 'SHARED_NOTE': echo View::make('cards/add-shared-note', [ 'level' => $level, 'parent_tag' => $parent_tag, ]); break; case 'OBJE': if ($WT_TREE->getPreference('MEDIA_UPLOAD') >= Auth::accessLevel($WT_TREE)) { echo View::make('cards/add-media-object', [ 'level' => $level, ]); } break; case 'RESN': echo View::make('cards/add-restriction', [ 'level' => $level, ]); break; } } /** * Add some empty tags to create a new fact. * * @param string $fact */ public static function addSimpleTags($fact) { global $WT_TREE; // For new individuals, these facts default to "Y" if ($fact === 'MARR') { echo self::addSimpleTag('0 ' . $fact . ' Y'); } else { echo self::addSimpleTag('0 ' . $fact); } if (!in_array($fact, Config::nonDateFacts())) { echo self::addSimpleTag('0 DATE', $fact, GedcomTag::getLabel($fact . ':DATE')); } if (!in_array($fact, Config::nonPlaceFacts())) { echo self::addSimpleTag('0 PLAC', $fact, GedcomTag::getLabel($fact . ':PLAC')); if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_PLAC_FACTS'), $match)) { foreach ($match[1] as $tag) { echo self::addSimpleTag('0 ' . $tag, $fact, GedcomTag::getLabel($fact . ':PLAC:' . $tag)); } } echo self::addSimpleTag('0 MAP', $fact); echo self::addSimpleTag('0 LATI', $fact); echo self::addSimpleTag('0 LONG', $fact); } } /** * Assemble the pieces of a newly created record into gedcom * * @return string */ public static function addNewName() { global $WT_TREE; $gedrec = "\n1 NAME " . Filter::post('NAME'); $tags = ['NPFX', 'GIVN', 'SPFX', 'SURN', 'NSFX']; if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_NAME_FACTS'), $match)) { $tags = array_merge($tags, $match[1]); } // Paternal and Polish and Lithuanian surname traditions can also create a _MARNM $SURNAME_TRADITION = $WT_TREE->getPreference('SURNAME_TRADITION'); if ($SURNAME_TRADITION === 'paternal' || $SURNAME_TRADITION === 'polish' || $SURNAME_TRADITION === 'lithuanian') { $tags[] = '_MARNM'; } foreach (array_unique($tags) as $tag) { $TAG = Filter::post($tag); if ($TAG) { $gedrec .= "\n2 {$tag} {$TAG}"; } } return $gedrec; } /** * Create a form to add a sex record. * * @return string */ public static function addNewSex() { switch (Filter::post('SEX', '[MF]', 'U')) { case 'M': return "\n1 SEX M"; case 'F': return "\n1 SEX F"; default: return "\n1 SEX U"; } } /** * Create a form to add a new fact. * * @param string $fact * * @return string */ public static function addNewFact($fact) { global $WT_TREE; $FACT = Filter::post($fact); $DATE = Filter::post($fact . '_DATE'); $PLAC = Filter::post($fact . '_PLAC'); if ($DATE || $PLAC || $FACT && $FACT !== 'Y') { if ($FACT && $FACT !== 'Y') { $gedrec = "\n1 " . $fact . ' ' . $FACT; } else { $gedrec = "\n1 " . $fact; } if ($DATE) { $gedrec .= "\n2 DATE " . $DATE; } if ($PLAC) { $gedrec .= "\n2 PLAC " . $PLAC; if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_PLAC_FACTS'), $match)) { foreach ($match[1] as $tag) { $TAG = Filter::post($fact . '_' . $tag); if ($TAG) { $gedrec .= "\n3 " . $tag . ' ' . $TAG; } } } $LATI = Filter::post($fact . '_LATI'); $LONG = Filter::post($fact . '_LONG'); if ($LATI || $LONG) { $gedrec .= "\n3 MAP\n4 LATI " . $LATI . "\n4 LONG " . $LONG; } } if (Filter::postBool('SOUR_' . $fact)) { return self::updateSource($gedrec, 2); } else { return $gedrec; } } elseif ($FACT === 'Y') { if (Filter::postBool('SOUR_' . $fact)) { return self::updateSource("\n1 " . $fact . ' Y', 2); } else { return "\n1 " . $fact . ' Y'; } } else { return ''; } } /** * This function splits the $glevels, $tag, $islink, and $text arrays so that the * entries associated with a SOUR record are separate from everything else. * * Input arrays: * - $glevels[] - an array of the gedcom level for each line that was edited * - $tag[] - an array of the tags for each gedcom line that was edited * - $islink[] - an array of 1 or 0 values to indicate when the text is a link element * - $text[] - an array of the text data for each line * * Output arrays: * ** For the SOUR record: * - $glevelsSOUR[] - an array of the gedcom level for each line that was edited * - $tagSOUR[] - an array of the tags for each gedcom line that was edited * - $islinkSOUR[] - an array of 1 or 0 values to indicate when the text is a link element * - $textSOUR[] - an array of the text data for each line * ** For the remaining records: * - $glevelsRest[] - an array of the gedcom level for each line that was edited * - $tagRest[] - an array of the tags for each gedcom line that was edited * - $islinkRest[] - an array of 1 or 0 values to indicate when the text is a link element * - $textRest[] - an array of the text data for each line */ public static function splitSource() { global $glevels, $tag, $islink, $text; global $glevelsSOUR, $tagSOUR, $islinkSOUR, $textSOUR; global $glevelsRest, $tagRest, $islinkRest, $textRest; $glevelsSOUR = []; $tagSOUR = []; $islinkSOUR = []; $textSOUR = []; $glevelsRest = []; $tagRest = []; $islinkRest = []; $textRest = []; $inSOUR = false; for ($i = 0; $i < count($glevels); $i++) { if ($inSOUR) { if ($levelSOUR < $glevels[$i]) { $dest = 'S'; } else { $inSOUR = false; $dest = 'R'; } } else { if ($tag[$i] === 'SOUR') { $inSOUR = true; $levelSOUR = $glevels[$i]; $dest = 'S'; } else { $dest = 'R'; } } if ($dest === 'S') { $glevelsSOUR[] = $glevels[$i]; $tagSOUR[] = $tag[$i]; $islinkSOUR[] = $islink[$i]; $textSOUR[] = $text[$i]; } else { $glevelsRest[] = $glevels[$i]; $tagRest[] = $tag[$i]; $islinkRest[] = $islink[$i]; $textRest[] = $text[$i]; } } } /** * Add new GEDCOM lines from the $xxxSOUR interface update arrays, which * were produced by the splitSOUR() function. * See the FunctionsEdit::handle_updatesges() function for details. * * @param string $inputRec * @param string $levelOverride * * @return string */ public static function updateSource($inputRec, $levelOverride = 'no') { global $glevels, $tag, $islink, $text; global $glevelsSOUR, $tagSOUR, $islinkSOUR, $textSOUR; if (count($tagSOUR) === 0) { return $inputRec; // No update required } // Save original interface update arrays before replacing them with the xxxSOUR ones $glevelsSave = $glevels; $tagSave = $tag; $islinkSave = $islink; $textSave = $text; $glevels = $glevelsSOUR; $tag = $tagSOUR; $islink = $islinkSOUR; $text = $textSOUR; $myRecord = self::handleUpdates($inputRec, $levelOverride); // Now do the update // Restore the original interface update arrays (just in case ...) $glevels = $glevelsSave; $tag = $tagSave; $islink = $islinkSave; $text = $textSave; return $myRecord; } /** * Add new GEDCOM lines from the $xxxRest interface update arrays, which * were produced by the splitSOUR() function. * See the FunctionsEdit::handle_updatesges() function for details. * * @param string $inputRec * @param string $levelOverride * * @return string */ public static function updateRest($inputRec, $levelOverride = 'no') { global $glevels, $tag, $islink, $text; global $glevelsRest, $tagRest, $islinkRest, $textRest; if (count($tagRest) === 0) { return $inputRec; // No update required } // Save original interface update arrays before replacing them with the xxxRest ones $glevelsSave = $glevels; $tagSave = $tag; $islinkSave = $islink; $textSave = $text; $glevels = $glevelsRest; $tag = $tagRest; $islink = $islinkRest; $text = $textRest; $myRecord = self::handleUpdates($inputRec, $levelOverride); // Now do the update // Restore the original interface update arrays (just in case ...) $glevels = $glevelsSave; $tag = $tagSave; $islink = $islinkSave; $text = $textSave; return $myRecord; } /** * Add new gedcom lines from interface update arrays * The edit_interface and FunctionsEdit::add_simple_tag function produce the following * arrays incoming from the $_POST form * - $glevels[] - an array of the gedcom level for each line that was edited * - $tag[] - an array of the tags for each gedcom line that was edited * - $islink[] - an array of 1 or 0 values to tell whether the text is a link element and should be surrounded by @@ * - $text[] - an array of the text data for each line * With these arrays you can recreate the gedcom lines like this * $glevel[0].' '.$tag[0].' '.$text[0] * There will be an index in each of these arrays for each line of the gedcom * fact that is being edited. * If the $text[] array is empty for the given line, then it means that the * user removed that line during editing or that the line is supposed to be * empty (1 DEAT, 1 BIRT) for example. To know if the line should be removed * there is a section of code that looks ahead to the next lines to see if there * are sub lines. For example we don't want to remove the 1 DEAT line if it has * a 2 PLAC or 2 DATE line following it. If there are no sub lines, then the line * can be safely removed. * * @param string $newged the new gedcom record to add the lines to * @param string $levelOverride Override GEDCOM level specified in $glevels[0] * * @return string The updated gedcom record */ public static function handleUpdates($newged, $levelOverride = 'no') { global $glevels, $islink, $tag, $uploaded_files, $text; if ($levelOverride === 'no' || count($glevels) === 0) { $levelAdjust = 0; } else { $levelAdjust = $levelOverride - $glevels[0]; } for ($j = 0; $j < count($glevels); $j++) { // Look for empty SOUR reference with non-empty sub-records. // This can happen when the SOUR entry is deleted but its sub-records // were incorrectly left intact. // The sub-records should be deleted. if ($tag[$j] === 'SOUR' && ($text[$j] === '@@' || $text[$j] === '')) { $text[$j] = ''; $k = $j + 1; while (($k < count($glevels)) && ($glevels[$k] > $glevels[$j])) { $text[$k] = ''; $k++; } } if (trim($text[$j]) !== '') { $pass = true; } else { //-- for facts with empty values they must have sub records //-- this section checks if they have subrecords $k = $j + 1; $pass = false; while (($k < count($glevels)) && ($glevels[$k] > $glevels[$j])) { if ($text[$k] !== '') { if (($tag[$j] !== 'OBJE') || ($tag[$k] === 'FILE')) { $pass = true; break; } } if (($tag[$k] === 'FILE') && (count($uploaded_files) > 0)) { $filename = array_shift($uploaded_files); if (!empty($filename)) { $text[$k] = $filename; $pass = true; break; } } $k++; } } //-- if the value is not empty or it has sub lines //--- then write the line to the gedcom record //-- we have to let some emtpy text lines pass through... (DEAT, BIRT, etc) if ($pass) { $newline = $glevels[$j] + $levelAdjust . ' ' . $tag[$j]; if ($text[$j] !== '') { if ($islink[$j]) { $newline .= ' @' . $text[$j] . '@'; } else { $newline .= ' ' . $text[$j]; } } $newged .= "\n" . str_replace("\n", "\n" . (1 + substr($newline, 0, 1)) . ' CONT ', $newline); } } return $newged; } /** * builds the form for adding new facts * * @param string $fact the new fact we are adding */ public static function createAddForm($fact) { global $tags, $WT_TREE; $tags = []; // handle MARRiage TYPE if (substr($fact, 0, 5) === 'MARR_') { $tags[0] = 'MARR'; echo self::addSimpleTag('1 MARR'); self::insertMissingSubtags($fact); } else { $tags[0] = $fact; if ($fact === '_UID') { $fact .= ' ' . GedcomTag::createUid(); } // These new level 1 tags need to be turned into links if (in_array($fact, ['ALIA', 'ASSO'])) { $fact .= ' @'; } if (in_array($fact, Config::emptyFacts())) { echo self::addSimpleTag('1 ' . $fact . ' Y'); } else { echo self::addSimpleTag('1 ' . $fact); } self::insertMissingSubtags($tags[0]); //-- handle the special SOURce case for level 1 sources [ 1759246 ] if ($fact === 'SOUR') { echo self::addSimpleTag('2 PAGE'); echo self::addSimpleTag('3 TEXT'); if ($WT_TREE->getPreference('FULL_SOURCES')) { echo self::addSimpleTag('3 DATE', '', GedcomTag::getLabel('DATA:DATE')); echo self::addSimpleTag('2 QUAY'); } } } } /** * Create a form to edit a Fact object. * * @param Fact $fact */ public static function createEditForm(Fact $fact) { global $tags; $record = $fact->getParent(); $tags = []; $gedlines = explode("\n", $fact->getGedcom()); $linenum = 0; $fields = explode(' ', $gedlines[$linenum]); $glevel = $fields[0]; $level = $glevel; $type = $fact->getTag(); $level0type = $record::RECORD_TYPE; $level1type = $type; $i = $linenum; $inSource = false; $levelSource = 0; $add_date = true; // List of tags we would expect at the next level // NB insertMissingSubtags() already takes care of the simple cases // where a level 1 tag is missing a level 2 tag. Here we only need to // handle the more complicated cases. $expected_subtags = [ 'SOUR' => ['PAGE', 'DATA'], 'DATA' => ['TEXT'], 'PLAC' => ['MAP'], 'MAP' => ['LATI', 'LONG'], ]; if ($record->getTree()->getPreference('FULL_SOURCES')) { $expected_subtags['SOUR'][] = 'QUAY'; $expected_subtags['DATA'][] = 'DATE'; } if (GedcomCodeTemp::isTagLDS($level1type)) { $expected_subtags['STAT'] = ['DATE']; } if (in_array($level1type, Config::dateAndTime())) { $expected_subtags['DATE'] = ['TIME']; // TIME is NOT a valid 5.5.1 tag } if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $record->getTree()->getPreference('ADVANCED_PLAC_FACTS'), $match)) { $expected_subtags['PLAC'] = array_merge($match[1], $expected_subtags['PLAC']); } $stack = []; // Loop on existing tags : while (true) { // Keep track of our hierarchy, e.g. 1=>BIRT, 2=>PLAC, 3=>FONE $stack[$level] = $type; // Merge them together, e.g. BIRT:PLAC:FONE $label = implode(':', array_slice($stack, 0, $level)); $text = ''; for ($j = 2; $j < count($fields); $j++) { if ($j > 2) { $text .= ' '; } $text .= $fields[$j]; } $text = rtrim($text); while (($i + 1 < count($gedlines)) && (preg_match('/' . ($level + 1) . ' CONT ?(.*)/', $gedlines[$i + 1], $cmatch) > 0)) { $text .= "\n" . $cmatch[1]; $i++; } if ($type === 'SOUR') { $inSource = true; $levelSource = $level; } elseif ($levelSource >= $level) { $inSource = false; } if ($type !== 'CONT') { $tags[] = $type; $subrecord = $level . ' ' . $type . ' ' . $text; if ($inSource && $type === 'DATE') { echo self::addSimpleTag($subrecord, '', GedcomTag::getLabel($label, $record)); } elseif (!$inSource && $type === 'DATE') { echo self::addSimpleTag($subrecord, $level1type, GedcomTag::getLabel($label, $record)); if ($level === '2') { // We already have a date - no need to add one. $add_date = false; } } elseif ($type === 'STAT') { echo self::addSimpleTag($subrecord, $level1type, GedcomTag::getLabel($label, $record)); } else { echo self::addSimpleTag($subrecord, $level0type, GedcomTag::getLabel($label, $record)); } } // Get a list of tags present at the next level $subtags = []; for ($ii = $i + 1; isset($gedlines[$ii]) && preg_match('/^(\d+) (\S+)/', $gedlines[$ii], $mm) && $mm[1] > $level; ++$ii) { if ($mm[1] == $level + 1) { $subtags[] = $mm[2]; } } // Insert missing tags if (!empty($expected_subtags[$type])) { foreach ($expected_subtags[$type] as $subtag) { if (!in_array($subtag, $subtags)) { echo self::addSimpleTag(($level + 1) . ' ' . $subtag, '', GedcomTag::getLabel($label . ':' . $subtag)); if (!empty($expected_subtags[$subtag])) { foreach ($expected_subtags[$subtag] as $subsubtag) { echo self::addSimpleTag(($level + 2) . ' ' . $subsubtag, '', GedcomTag::getLabel($label . ':' . $subtag . ':' . $subsubtag)); } } } } } $i++; if (isset($gedlines[$i])) { $fields = explode(' ', $gedlines[$i]); $level = $fields[0]; if (isset($fields[1])) { $type = trim($fields[1]); } else { $level = 0; } } else { $level = 0; } if ($level <= $glevel) { break; } } if ($level1type !== '_PRIM') { self::insertMissingSubtags($level1type, $add_date); } } /** * Populates the global $tags array with any missing sub-tags. * * @param string $level1tag the type of the level 1 gedcom record * @param bool $add_date */ public static function insertMissingSubtags($level1tag, $add_date = false) { global $tags, $WT_TREE; // handle MARRiage TYPE $type_val = ''; if (substr($level1tag, 0, 5) === 'MARR_') { $type_val = substr($level1tag, 5); $level1tag = 'MARR'; } foreach (Config::levelTwoTags() as $key => $value) { if ($key === 'DATE' && in_array($level1tag, Config::nonDateFacts()) || $key === 'PLAC' && in_array($level1tag, Config::nonPlaceFacts())) { continue; } if (in_array($level1tag, $value) && !in_array($key, $tags)) { if ($key === 'TYPE') { echo self::addSimpleTag('2 TYPE ' . $type_val, $level1tag); } elseif ($level1tag === '_TODO' && $key === 'DATE') { echo self::addSimpleTag('2 ' . $key . ' ' . strtoupper(date('d M Y')), $level1tag); } elseif ($level1tag === '_TODO' && $key === '_WT_USER') { echo self::addSimpleTag('2 ' . $key . ' ' . Auth::user()->getUserName(), $level1tag); } elseif ($level1tag === 'TITL' && strstr($WT_TREE->getPreference('ADVANCED_NAME_FACTS'), $key) !== false) { echo self::addSimpleTag('2 ' . $key, $level1tag); } elseif ($level1tag === 'NAME' && strstr($WT_TREE->getPreference('ADVANCED_NAME_FACTS'), $key) !== false) { echo self::addSimpleTag('2 ' . $key, $level1tag); } elseif ($level1tag !== 'TITL' && $level1tag !== 'NAME') { echo self::addSimpleTag('2 ' . $key, $level1tag); } // Add level 3/4 tags as appropriate switch ($key) { case 'PLAC': if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_PLAC_FACTS'), $match)) { foreach ($match[1] as $tag) { echo self::addSimpleTag('3 ' . $tag, '', GedcomTag::getLabel($level1tag . ':PLAC:' . $tag)); } } echo self::addSimpleTag('3 MAP'); echo self::addSimpleTag('4 LATI'); echo self::addSimpleTag('4 LONG'); break; case 'FILE': echo self::addSimpleTag('3 FORM'); break; case 'EVEN': echo self::addSimpleTag('3 DATE'); echo self::addSimpleTag('3 PLAC'); break; case 'STAT': if (GedcomCodeTemp::isTagLDS($level1tag)) { echo self::addSimpleTag('3 DATE', '', GedcomTag::getLabel('STAT:DATE')); } break; case 'DATE': // TIME is NOT a valid 5.5.1 tag if (in_array($level1tag, Config::dateAndTime())) { echo self::addSimpleTag('3 TIME'); } break; case 'HUSB': case 'WIFE': echo self::addSimpleTag('3 AGE'); break; case 'FAMC': if ($level1tag === 'ADOP') { echo self::addSimpleTag('3 ADOP BOTH'); } break; } } elseif ($key === 'DATE' && $add_date) { echo self::addSimpleTag('2 DATE', $level1tag, GedcomTag::getLabel($level1tag . ':DATE')); } } // Do something (anything!) with unrecognized custom tags if (substr($level1tag, 0, 1) === '_' && $level1tag !== '_UID' && $level1tag !== '_PRIM' && $level1tag !== '_TODO') { foreach (['DATE', 'PLAC', 'ADDR', 'AGNC', 'TYPE', 'AGE'] as $tag) { if (!in_array($tag, $tags)) { echo self::addSimpleTag('2 ' . $tag); if ($tag === 'PLAC') { if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_PLAC_FACTS'), $match)) { foreach ($match[1] as $ptag) { echo self::addSimpleTag('3 ' . $ptag, '', GedcomTag::getLabel($level1tag . ':PLAC:' . $ptag)); } } echo self::addSimpleTag('3 MAP'); echo self::addSimpleTag('4 LATI'); echo self::addSimpleTag('4 LONG'); } } } } } }