summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGreg Roach <greg@subaqua.co.uk>2021-02-01 16:50:30 +0000
committerGreg Roach <greg@subaqua.co.uk>2021-02-02 16:52:32 +0000
commitc2ed51d13a57743094c11c8fe84befd9d4f158cd (patch)
tree4f2d541fed3550f5d64f9754e9d37f5e9b9ce447 /app
parent4d2b9c53c95ad846160295dd8f4136a5b53f9f7b (diff)
downloadwebtrees-c2ed51d13a57743094c11c8fe84befd9d4f158cd.tar.gz
webtrees-c2ed51d13a57743094c11c8fe84befd9d4f158cd.tar.bz2
webtrees-c2ed51d13a57743094c11c8fe84befd9d4f158cd.zip
Merge GEDCOM element code from 2.1 branch
Diffstat (limited to 'app')
-rw-r--r--app/Contracts/ElementFactoryInterface.php43
-rw-r--r--app/Contracts/ElementInterface.php108
-rw-r--r--app/Elements/AbstractElement.php308
-rw-r--r--app/Elements/AbstractEventElement.php70
-rw-r--r--app/Elements/AbstractXrefElement.php67
-rw-r--r--app/Elements/AddressCity.php29
-rw-r--r--app/Elements/AddressCountry.php31
-rw-r--r--app/Elements/AddressEmail.php48
-rw-r--r--app/Elements/AddressFax.php51
-rw-r--r--app/Elements/AddressLine.php63
-rw-r--r--app/Elements/AddressLine1.php30
-rw-r--r--app/Elements/AddressLine2.php30
-rw-r--r--app/Elements/AddressLine3.php30
-rw-r--r--app/Elements/AddressPostalCode.php44
-rw-r--r--app/Elements/AddressState.php29
-rw-r--r--app/Elements/AddressWebPage.php48
-rw-r--r--app/Elements/AdoptedByWhichParent.php62
-rw-r--r--app/Elements/Adoption.php37
-rw-r--r--app/Elements/AdultChristening.php36
-rw-r--r--app/Elements/AgeAtEvent.php63
-rw-r--r--app/Elements/AncestralFileNumber.php60
-rw-r--r--app/Elements/Annulment.php27
-rw-r--r--app/Elements/ApprovedSystemId.php47
-rw-r--r--app/Elements/AttributeDescriptor.php42
-rw-r--r--app/Elements/AutomatedRecordId.php32
-rw-r--r--app/Elements/Baptism.php36
-rw-r--r--app/Elements/BarMitzvah.php36
-rw-r--r--app/Elements/BasMitzvah.php36
-rw-r--r--app/Elements/Birth.php36
-rw-r--r--app/Elements/Blessing.php36
-rw-r--r--app/Elements/Burial.php35
-rw-r--r--app/Elements/CasteName.php40
-rw-r--r--app/Elements/CauseOfEvent.php31
-rw-r--r--app/Elements/Census.php36
-rw-r--r--app/Elements/CertaintyAssessment.php55
-rw-r--r--app/Elements/Change.php27
-rw-r--r--app/Elements/ChangeDate.php48
-rw-r--r--app/Elements/CharacterSet.php79
-rw-r--r--app/Elements/ChildLinkageStatus.php66
-rw-r--r--app/Elements/Christening.php37
-rw-r--r--app/Elements/Confirmation.php36
-rw-r--r--app/Elements/ContentDescription.php31
-rw-r--r--app/Elements/CopyrightFile.php30
-rw-r--r--app/Elements/CopyrightSourceData.php32
-rw-r--r--app/Elements/CountOfChildren.php52
-rw-r--r--app/Elements/CountOfMarriages.php51
-rw-r--r--app/Elements/Cremation.php35
-rw-r--r--app/Elements/CustomElement.php27
-rw-r--r--app/Elements/CustomEvent.php40
-rw-r--r--app/Elements/CustomFact.php39
-rw-r--r--app/Elements/DateLdsOrd.php34
-rw-r--r--app/Elements/DateValue.php103
-rw-r--r--app/Elements/Death.php37
-rw-r--r--app/Elements/DescriptiveTitle.php28
-rw-r--r--app/Elements/Divorce.php27
-rw-r--r--app/Elements/DivorceFiled.php27
-rw-r--r--app/Elements/Emigration.php36
-rw-r--r--app/Elements/EmptyElement.php45
-rw-r--r--app/Elements/Engagement.php27
-rw-r--r--app/Elements/EntryRecordingDate.php48
-rw-r--r--app/Elements/EventAttributeType.php33
-rw-r--r--app/Elements/EventDescriptor.php67
-rw-r--r--app/Elements/EventOrFactClassification.php50
-rw-r--r--app/Elements/EventTypeCitedFrom.php35
-rw-r--r--app/Elements/EventsRecorded.php171
-rw-r--r--app/Elements/FileName.php43
-rw-r--r--app/Elements/FirstCommunion.php36
-rw-r--r--app/Elements/Form.php47
-rw-r--r--app/Elements/Gedcom.php27
-rw-r--r--app/Elements/GenerationsOfAncestors.php46
-rw-r--r--app/Elements/GenerationsOfDescendants.php46
-rw-r--r--app/Elements/GovId.php45
-rw-r--r--app/Elements/Graduation.php37
-rw-r--r--app/Elements/Immigration.php36
-rw-r--r--app/Elements/LanguageId.php216
-rw-r--r--app/Elements/LdsBaptism.php35
-rw-r--r--app/Elements/LdsBaptismDateStatus.php80
-rw-r--r--app/Elements/LdsChildSealing.php35
-rw-r--r--app/Elements/LdsChildSealingDateStatus.php73
-rw-r--r--app/Elements/LdsConfirmation.php35
-rw-r--r--app/Elements/LdsEndowment.php35
-rw-r--r--app/Elements/LdsEndowmentDateStatus.php81
-rw-r--r--app/Elements/LdsSpouseSealing.php35
-rw-r--r--app/Elements/LdsSpouseSealingDateStatus.php80
-rw-r--r--app/Elements/Marriage.php38
-rw-r--r--app/Elements/MarriageBanns.php27
-rw-r--r--app/Elements/MarriageContract.php27
-rw-r--r--app/Elements/MarriageLicence.php27
-rw-r--r--app/Elements/MarriageSettlement.php27
-rw-r--r--app/Elements/MarriageType.php66
-rw-r--r--app/Elements/MediaRecord.php36
-rw-r--r--app/Elements/MultimediaFileReference.php34
-rw-r--r--app/Elements/MultimediaFormat.php35
-rw-r--r--app/Elements/NameOfBusiness.php30
-rw-r--r--app/Elements/NameOfFamilyFile.php59
-rw-r--r--app/Elements/NameOfProduct.php29
-rw-r--r--app/Elements/NameOfRepository.php29
-rw-r--r--app/Elements/NameOfSourceData.php31
-rw-r--r--app/Elements/NamePersonal.php145
-rw-r--r--app/Elements/NamePhoneticVariation.php33
-rw-r--r--app/Elements/NamePieceGiven.php67
-rw-r--r--app/Elements/NamePieceNickname.php54
-rw-r--r--app/Elements/NamePiecePrefix.php58
-rw-r--r--app/Elements/NamePieceSuffix.php58
-rw-r--r--app/Elements/NamePieceSurname.php56
-rw-r--r--app/Elements/NamePieceSurnamePrefix.php55
-rw-r--r--app/Elements/NameRomanizedVariation.php58
-rw-r--r--app/Elements/NameType.php66
-rw-r--r--app/Elements/NationOrTribalOrigin.php41
-rw-r--r--app/Elements/NationalIdNumber.php44
-rw-r--r--app/Elements/Naturalization.php36
-rw-r--r--app/Elements/NobilityTypeTitle.php39
-rw-r--r--app/Elements/NoteRecord.php33
-rw-r--r--app/Elements/NoteStructure.php39
-rw-r--r--app/Elements/Occupation.php38
-rw-r--r--app/Elements/OrdinanceProcessFlag.php59
-rw-r--r--app/Elements/Ordination.php36
-rw-r--r--app/Elements/PafUid.php66
-rw-r--r--app/Elements/PedigreeLinkageType.php82
-rw-r--r--app/Elements/PermanentRecordFileNumber.php38
-rw-r--r--app/Elements/PhoneNumber.php53
-rw-r--r--app/Elements/PhoneticType.php55
-rw-r--r--app/Elements/PhysicalDescription.php40
-rw-r--r--app/Elements/PlaceHierarchy.php39
-rw-r--r--app/Elements/PlaceLatitude.php51
-rw-r--r--app/Elements/PlaceLivingOrdinance.php30
-rw-r--r--app/Elements/PlaceLongtitude.php49
-rw-r--r--app/Elements/PlaceName.php76
-rw-r--r--app/Elements/PlacePhoneticVariation.php33
-rw-r--r--app/Elements/PlaceRomanizedVariation.php34
-rw-r--r--app/Elements/Possessions.php36
-rw-r--r--app/Elements/Probate.php35
-rw-r--r--app/Elements/PublicationDate.php29
-rw-r--r--app/Elements/ReceivingSystemName.php48
-rw-r--r--app/Elements/RelationIsDescriptor.php158
-rw-r--r--app/Elements/ReligiousAffiliation.php38
-rw-r--r--app/Elements/RepositoryRecord.php37
-rw-r--r--app/Elements/Residence.php35
-rw-r--r--app/Elements/ResponsibleAgency.php32
-rw-r--r--app/Elements/RestrictionNotice.php81
-rw-r--r--app/Elements/Retirement.php37
-rw-r--r--app/Elements/RoleInEvent.php34
-rw-r--r--app/Elements/RomanizedType.php51
-rw-r--r--app/Elements/ScholasticAchievement.php37
-rw-r--r--app/Elements/SexValue.php78
-rw-r--r--app/Elements/SocialSecurityNumber.php38
-rw-r--r--app/Elements/SourceCallNumber.php46
-rw-r--r--app/Elements/SourceData.php51
-rw-r--r--app/Elements/SourceDescriptiveTitle.php38
-rw-r--r--app/Elements/SourceFiledByEntry.php29
-rw-r--r--app/Elements/SourceJurisdictionPlace.php31
-rw-r--r--app/Elements/SourceMediaType.php84
-rw-r--r--app/Elements/SourceOriginator.php30
-rw-r--r--app/Elements/SourcePublicationFacts.php48
-rw-r--r--app/Elements/SourceRecord.php40
-rw-r--r--app/Elements/SubmissionRecord.php37
-rw-r--r--app/Elements/SubmitterName.php29
-rw-r--r--app/Elements/SubmitterRecord.php38
-rw-r--r--app/Elements/SubmitterRegisteredRfn.php30
-rw-r--r--app/Elements/SubmitterText.php57
-rw-r--r--app/Elements/TempleCode.php215
-rw-r--r--app/Elements/TextFromSource.php76
-rw-r--r--app/Elements/TimeValue.php33
-rw-r--r--app/Elements/TransmissionDate.php29
-rw-r--r--app/Elements/UnknownElement.php40
-rw-r--r--app/Elements/UserReferenceNumber.php30
-rw-r--r--app/Elements/UserReferenceType.php29
-rw-r--r--app/Elements/VersionNumber.php48
-rw-r--r--app/Elements/WebtreesUser.php41
-rw-r--r--app/Elements/WhereWithinSource.php55
-rw-r--r--app/Elements/Will.php35
-rw-r--r--app/Elements/XrefFamily.php67
-rw-r--r--app/Elements/XrefIndividual.php66
-rw-r--r--app/Elements/XrefLocation.php81
-rw-r--r--app/Elements/XrefMedia.php81
-rw-r--r--app/Elements/XrefNote.php81
-rw-r--r--app/Elements/XrefRepository.php81
-rw-r--r--app/Elements/XrefSource.php110
-rw-r--r--app/Elements/XrefSubmission.php81
-rw-r--r--app/Elements/XrefSubmitter.php81
-rw-r--r--app/Factories/ElementFactory.php772
-rw-r--r--app/Functions/FunctionsPrint.php18
-rw-r--r--app/GedcomRecord.php71
-rw-r--r--app/Http/Middleware/RegisterFactories.php2
-rw-r--r--app/Http/RequestHandlers/CreateLocationAction.php66
-rw-r--r--app/Http/RequestHandlers/CreateLocationModal.php50
-rw-r--r--app/Http/RequestHandlers/CreateSubmissionAction.php66
-rw-r--r--app/Http/RequestHandlers/CreateSubmissionModal.php48
-rw-r--r--app/Http/RequestHandlers/EditRecordAction.php87
-rw-r--r--app/Http/RequestHandlers/EditRecordPage.php65
-rw-r--r--app/Http/RequestHandlers/Select2Location.php79
-rw-r--r--app/Http/RequestHandlers/Select2Submission.php79
-rw-r--r--app/Http/Routes/WebRoutes.php16
-rw-r--r--app/Module/BranchesListModule.php3
-rw-r--r--app/Registry.php20
-rw-r--r--app/Services/GedcomEditService.php62
-rw-r--r--app/Statistics/Repository/EventRepository.php2
197 files changed, 10670 insertions, 22 deletions
diff --git a/app/Contracts/ElementFactoryInterface.php b/app/Contracts/ElementFactoryInterface.php
new file mode 100644
index 0000000000..74f3588c98
--- /dev/null
+++ b/app/Contracts/ElementFactoryInterface.php
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Contracts;
+
+/**
+ * Make a GEDCOM primitive element.
+ */
+interface ElementFactoryInterface
+{
+ /**
+ * Create a GEDCOM primitive object.
+ *
+ * @param string $tag
+ *
+ * @return ElementInterface
+ */
+ public function make(string $tag): ElementInterface;
+
+
+ /**
+ * Register more elements.
+ *
+ * @param array<string,ElementInterface> $elements
+ */
+ public function register(array $elements): void;
+}
diff --git a/app/Contracts/ElementInterface.php b/app/Contracts/ElementInterface.php
new file mode 100644
index 0000000000..b23ea45e83
--- /dev/null
+++ b/app/Contracts/ElementInterface.php
@@ -0,0 +1,108 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Contracts;
+
+use Fisharebest\Webtrees\Tree;
+
+/**
+ * A GEDCOM element is a tag/primitive in a GEDCOM file.
+ */
+interface ElementInterface
+{
+ /**
+ * Convert a value to a canonical form.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public function canonical(string $value): string;
+
+ /**
+ * Create a default value for this element.
+ *
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function default(Tree $tree): string;
+
+ /**
+ * An edit control for this data.
+ *
+ * @param string $id
+ * @param string $name
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function edit(string $id, string $name, string $value, Tree $tree): string;
+
+ /**
+ * Escape @ signs in a GEDCOM export.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public function escape(string $value): string;
+
+ /**
+ * Name for this GEDCOM primitive.
+ *
+ * @return string
+ */
+ public function label(): string;
+
+ /**
+ * Create a label/value pair for this element.
+ *
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function labelValue(string $value, Tree $tree): string;
+
+ /**
+ * @param Tree $tree
+ *
+ * @return array<string,string>
+ */
+ public function subtags(Tree $tree): array;
+
+ /**
+ * Display the value of this type of element.
+ *
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function value(string $value, Tree $tree): string;
+
+ /**
+ * A list of controlled values for this element
+ *
+ * @return array<int|string,string>
+ */
+ public function values(): array;
+}
diff --git a/app/Elements/AbstractElement.php b/app/Elements/AbstractElement.php
new file mode 100644
index 0000000000..1d3181a337
--- /dev/null
+++ b/app/Elements/AbstractElement.php
@@ -0,0 +1,308 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Contracts\ElementInterface;
+use Fisharebest\Webtrees\Html;
+use Fisharebest\Webtrees\I18N;
+use Fisharebest\Webtrees\Tree;
+use League\CommonMark\Block\Element\Document;
+use League\CommonMark\Block\Element\Paragraph;
+use League\CommonMark\Block\Renderer\DocumentRenderer;
+use League\CommonMark\Block\Renderer\ParagraphRenderer;
+use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Inline\Element\Link;
+use League\CommonMark\Inline\Element\Text;
+use League\CommonMark\Inline\Parser\AutolinkParser;
+use League\CommonMark\Inline\Renderer\LinkRenderer;
+use League\CommonMark\Inline\Renderer\TextRenderer;
+
+use function array_key_exists;
+use function array_map;
+use function e;
+use function is_numeric;
+use function preg_replace;
+use function strpos;
+use function trim;
+use function view;
+
+/**
+ * A GEDCOM element is a tag/primitive in a GEDCOM file.
+ */
+abstract class AbstractElement implements ElementInterface
+{
+ private const REGEX_URL = '~((https?|ftp]):)(//([^\s/?#<>]*))?([^\s?#<>]*)(\?([^\s#<>]*))?(#[^\s?#<>]+)?~';
+
+ // HTML attributes for an <input>
+ protected const MAX_LENGTH = false;
+
+ // Which child elements can appear under this element.
+ protected const SUBTAGS = [];
+
+ /** @var string A label to describe this element */
+ private $label;
+
+ /** @var array<string,string> */
+ private $subtags;
+
+ /**
+ * AbstractGedcomElement constructor.
+ *
+ * @param string $label
+ * @param array<string>|null $subtags
+ */
+ public function __construct(string $label, array $subtags = null)
+ {
+ $this->label = $label;
+ $this->subtags = $subtags ?? static::SUBTAGS;
+ }
+
+ /**
+ * Convert a value to a canonical form.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public function canonical(string $value): string
+ {
+ $value = strtr($value, ["\t" => ' ', "\r" => ' ', "\n" => ' ']);
+
+ while (strpos($value, ' ') !== false) {
+ $value = strtr($value, [' ' => ' ']);
+ }
+
+ return trim($value);
+ }
+
+ /**
+ * Create a default value for this element.
+ *
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function default(Tree $tree): string
+ {
+ return '';
+ }
+
+ /**
+ * An edit control for this data.
+ *
+ * @param string $id
+ * @param string $name
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function edit(string $id, string $name, string $value, Tree $tree): string
+ {
+ $values = $this->values();
+
+ if ($values !== []) {
+ $value = $this->canonical($value);
+
+ // Ensure the current data is in the list.
+ if (!array_key_exists($value, $values)) {
+ $values = [$value => $value] + $values;
+ }
+
+ // We may use markup to display values, but not when editing them.
+ $values = array_map('strip_tags', $values);
+
+ return view('components/select', [
+ 'id' => $id,
+ 'name' => $name,
+ 'options' => $values,
+ 'selected' => $value,
+ ]);
+ }
+
+ $attributes = [
+ 'class' => 'form-control',
+ 'type' => 'text',
+ 'id' => $id,
+ 'name' => $name,
+ 'value' => $value,
+ 'maxvalue' => static::MAX_LENGTH,
+ ];
+
+ return '<input ' . Html::attributes($attributes) . '">';
+ }
+
+ /**
+ * An edit control for this data.
+ *
+ * @param string $id
+ * @param string $name
+ * @param string $value
+ *
+ * @return string
+ */
+ public function editHidden(string $id, string $name, string $value): string
+ {
+ return '<input class="form-control" type="hidden" id="' . e($id) . '" name="' . e($name) . '" value="' . e($value) . '">';
+ }
+
+ /**
+ * An edit control for this data.
+ *
+ * @param string $id
+ * @param string $name
+ * @param string $value
+ *
+ * @return string
+ */
+ public function editTextArea(string $id, string $name, string $value): string
+ {
+ return '<textarea class="form-control" id="' . e($id) . '" name="' . e($name) . '" rows="5" dir="auto">' . e($value) . '</textarea>';
+ }
+
+ /**
+ * Escape @ signs in a GEDCOM export.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public function escape(string $value): string
+ {
+ return strtr($value, ['@' => '@@']);
+ }
+
+ /**
+ * Create a label for this element.
+ *
+ * @return string
+ */
+ public function label(): string
+ {
+ return $this->label;
+ }
+
+ /**
+ * Create a label/value pair for this element.
+ *
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function labelValue(string $value, Tree $tree): string
+ {
+ $label = '<span class="label">' . $this->label() . '</span>';
+ $value = '<span class="value">' . $this->value($value, $tree) . '</span>';
+ $html = I18N::translate(/* I18N: e.g. "Occupation: farmer" */ '%1$s: %2$s', $label, $value);
+
+ return '<div>' . $html . '</div>';
+ }
+
+ /**
+ * @param Tree $tree
+ *
+ * @return array<string,string>
+ */
+ public function subtags(Tree $tree): array
+ {
+ return $this->subtags;
+ }
+
+ /**
+ * Display the value of this type of element.
+ *
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function value(string $value, Tree $tree): string
+ {
+ $values = $this->values();
+
+ if ($values === []) {
+ if (str_contains($value, "\n")) {
+ return '<span dir="auto" class="d-inline-block" style="white-space: pre-wrap;">' . e($value) . '</span>';
+ }
+
+ return '<span dir="auto">' . e($value) . '</span>';
+ }
+
+ $canonical = $this->canonical($value);
+
+ return $values[$canonical] ?? '<span dir="auto">' . e($value) . '</span>';
+ }
+
+ /**
+ * A list of controlled values for this element
+ *
+ * @return array<int|string,string>
+ */
+ public function values(): array
+ {
+ return [];
+ }
+
+ /**
+ * Display the value of this type of element - convert URLs to links
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ protected function valueAutoLink(string $value): string
+ {
+ // Convert URLs into markdown auto-links.
+ $value = preg_replace(self::REGEX_URL, '<$0>', $value);
+
+ // Create a minimal commonmark processor - just add support for autolinks.
+ $environment = new Environment();
+ $environment
+ ->addBlockRenderer(Document::class, new DocumentRenderer())
+ ->addBlockRenderer(Paragraph::class, new ParagraphRenderer())
+ ->addInlineRenderer(Text::class, new TextRenderer())
+ ->addInlineRenderer(Link::class, new LinkRenderer())
+ ->addInlineParser(new AutolinkParser());
+
+ $converter = new CommonMarkConverter(['html_input' => Environment::HTML_INPUT_ESCAPE], $environment);
+
+ return $converter->convertToHtml($value);
+ }
+
+ /**
+ * Display the value of this type of element.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public function valueNumeric(string $value): string
+ {
+ $canonical = $this->canonical($value);
+
+ if (is_numeric($canonical)) {
+ return I18N::number((int) $canonical);
+ }
+
+ return e($value);
+ }
+}
diff --git a/app/Elements/AbstractEventElement.php b/app/Elements/AbstractEventElement.php
new file mode 100644
index 0000000000..c2ff3e17f0
--- /dev/null
+++ b/app/Elements/AbstractEventElement.php
@@ -0,0 +1,70 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\I18N;
+use Fisharebest\Webtrees\Tree;
+
+use function e;
+
+/**
+ * Events which can take "Y" to indicate that they occurred, but date/place are unknown.
+ */
+class AbstractEventElement extends AbstractElement
+{
+ /**
+ * Create a default value for this element.
+ *
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function default(Tree $tree): string
+ {
+ return 'Y';
+ }
+
+ /**
+ * An edit control for this data.
+ *
+ * @param string $id
+ * @param string $name
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function edit(string $id, string $name, string $value, Tree $tree): string
+ {
+ return
+ '<div class="form-check">' .
+ $this->editHidden($id, $name, $value ? 'Y' : '') .
+ '<input class="form-check-input" type="checkbox" value="Y" id="' . e($id) . '-check" ' . ($value ? 'checked' : '') . '>' .
+ '<label class="form-check-label" for="' . e($id) . '-check">' .
+ I18N::translate('This event occurred, but the details are unknown.') .
+ '</label>' .
+ '</div>' .
+ '<script>' .
+ 'document.getElementById("' . e($id) . '-check").addEventListener("change", function () {' .
+ 'document.getElementById("' . e($id) . '").value = this.checked ? "Y" : "";' .
+ '})' .
+ '</script>';
+ }
+}
diff --git a/app/Elements/AbstractXrefElement.php b/app/Elements/AbstractXrefElement.php
new file mode 100644
index 0000000000..021cc4df6f
--- /dev/null
+++ b/app/Elements/AbstractXrefElement.php
@@ -0,0 +1,67 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Gedcom;
+use Fisharebest\Webtrees\GedcomRecord;
+use Fisharebest\Webtrees\Tree;
+
+use function e;
+use function preg_match;
+
+/**
+ * Common behaviour for all XREF links
+ */
+class AbstractXrefElement extends AbstractElement
+{
+ /**
+ * Escape @ signs in a GEDCOM export.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public function escape(string $value): string
+ {
+ return $value;
+ }
+
+ /**
+ * Display the value of this type of element - convert XREFs to links.
+ *
+ * @param string $value
+ * @param Tree $tree
+ * @param mixed $factory We can type-hint this from PHP 7.4
+ *
+ * @return string
+ */
+ protected function valueXrefLink(string $value, Tree $tree, $factory): string
+ {
+ if (preg_match('/^@(' . Gedcom::REGEX_XREF . ')@$/', $value, $match)) {
+ $record = $factory->make($match[1], $tree);
+
+ if ($record instanceof GedcomRecord) {
+ return '<a href="' . e($record->url()) . '">' . $record->fullName() . '</a>';
+ }
+ }
+
+ return '<span class="error">' . e($value) . '</span>';
+ }
+}
diff --git a/app/Elements/AddressCity.php b/app/Elements/AddressCity.php
new file mode 100644
index 0000000000..ab5ee8fe1b
--- /dev/null
+++ b/app/Elements/AddressCity.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * ADDRESS_CITY := {Size=1:60}
+ * The name of the city used in the address. Isolated for sorting or indexing.
+ */
+class AddressCity extends AbstractElement
+{
+ protected const MAX_LENGTH = 60;
+}
diff --git a/app/Elements/AddressCountry.php b/app/Elements/AddressCountry.php
new file mode 100644
index 0000000000..cc45fcf378
--- /dev/null
+++ b/app/Elements/AddressCountry.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * ADDRESS_COUNTRY := {Size=1:60}
+ * The name of the country that pertains to the associated address. Isolated
+ * by some systems for sorting or indexing. Used in most cases to facilitate
+ * automatic sorting of mail.
+ */
+class AddressCountry extends AbstractElement
+{
+ protected const MAX_LENGTH = 60;
+}
diff --git a/app/Elements/AddressEmail.php b/app/Elements/AddressEmail.php
new file mode 100644
index 0000000000..13aab39921
--- /dev/null
+++ b/app/Elements/AddressEmail.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Tree;
+
+use function e;
+
+/**
+ * ADDRESS_EMAIL := {Size=5:120}
+ * An electronic address that can be used for contact such as an email address.
+ */
+class AddressEmail extends AbstractElement
+{
+ protected const MAX_LENGTH = 120;
+
+ /**
+ * Display the value of this type of element.
+ *
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function value(string $value, Tree $tree): string
+ {
+ $canonical = $this->canonical($value);
+
+ return '<a dir="ltr" href="mailto:' . e($canonical) . '">' . e($canonical) . '</a>';
+ }
+}
diff --git a/app/Elements/AddressFax.php b/app/Elements/AddressFax.php
new file mode 100644
index 0000000000..f33dc9f2fb
--- /dev/null
+++ b/app/Elements/AddressFax.php
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Tree;
+
+use function e;
+use function preg_match;
+
+/**
+ * ADDRESS_FAX := {Size=5:60}
+ * A FAX telephone number appropriate for sending data facsimiles.
+ */
+class AddressFax extends AbstractElement
+{
+ protected const MAX_LENGTH = 60;
+
+ /**
+ * Display the value of this type of element.
+ *
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function value(string $value, Tree $tree): string
+ {
+ if (preg_match('/^[+0-9-]+$/', $value)) {
+ return '<span dir="ltr">' . e($value) . '</span>';
+ }
+
+ return e($value);
+ }
+}
diff --git a/app/Elements/AddressLine.php b/app/Elements/AddressLine.php
new file mode 100644
index 0000000000..ea00c158d0
--- /dev/null
+++ b/app/Elements/AddressLine.php
@@ -0,0 +1,63 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Tree;
+
+/**
+ * ADDRESS_LINE := {Size=1:60}
+ * Typically used to define a mailing address of an individual when used
+ * subordinate to a RESIdent tag. When it is used subordinate to an event tag
+ * it is the address of the place where the event took place. The address
+ * lines usually contain the addressee’s name and other street and city
+ * information so that it forms an address that meets mailing requirements.
+ */
+class AddressLine extends AbstractElement
+{
+ protected const MAX_LENGTH = 60;
+
+ /**
+ * Convert a value to a canonical form.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public function canonical(string $value): string
+ {
+ // Browsers use MS-DOS line endings in multi-line data.
+ return strtr($value, ["\r\n" => "\n", "\r" => "\n"]);
+ }
+
+ /**
+ * An edit control for this data.
+ *
+ * @param string $id
+ * @param string $name
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function edit(string $id, string $name, string $value, Tree $tree): string
+ {
+ return $this->editTextArea($id, $name, $value);
+ }
+}
diff --git a/app/Elements/AddressLine1.php b/app/Elements/AddressLine1.php
new file mode 100644
index 0000000000..3f4a4f3305
--- /dev/null
+++ b/app/Elements/AddressLine1.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * ADDRESS_LINE1 := {Size=1:60}
+ * The first line of the address used for indexing. This is the value of the
+ * line corresponding to the ADDR tag line in the address structure.
+ */
+class AddressLine1 extends AbstractElement
+{
+ protected const MAX_LENGTH = 60;
+}
diff --git a/app/Elements/AddressLine2.php b/app/Elements/AddressLine2.php
new file mode 100644
index 0000000000..ef9369d232
--- /dev/null
+++ b/app/Elements/AddressLine2.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * ADDRESS_LINE2 := {Size=1:60}
+ * The second line of the address used for indexing. This is the value of
+ * the first CONT line subordinate to the ADDR tag in the address structure.
+ */
+class AddressLine2 extends AbstractElement
+{
+ protected const MAX_LENGTH = 60;
+}
diff --git a/app/Elements/AddressLine3.php b/app/Elements/AddressLine3.php
new file mode 100644
index 0000000000..8ae0cc69fa
--- /dev/null
+++ b/app/Elements/AddressLine3.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * ADDRESS_LINE3 := {Size=1:60}
+ * The third line of the address used for indexing. This is the value of
+ * the second CONT line subordinate to the ADDR tag in the address structure.
+ */
+class AddressLine3 extends AbstractElement
+{
+ protected const MAX_LENGTH = 60;
+}
diff --git a/app/Elements/AddressPostalCode.php b/app/Elements/AddressPostalCode.php
new file mode 100644
index 0000000000..338ef6e445
--- /dev/null
+++ b/app/Elements/AddressPostalCode.php
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use function strtoupper;
+
+/**
+ * ADDRESS_POST := {Size=1:10}
+ * The ZIP or postal code used by the various localities in handling of mail.
+ * Isolated for sorting or indexing.
+ */
+class AddressPostalCode extends AbstractElement
+{
+ protected const MAX_LENGTH = 10;
+
+ /**
+ * Convert a value to a canonical form.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public function canonical(string $value): string
+ {
+ return strtoupper(parent::canonical($value));
+ }
+}
diff --git a/app/Elements/AddressState.php b/app/Elements/AddressState.php
new file mode 100644
index 0000000000..1ca2cac192
--- /dev/null
+++ b/app/Elements/AddressState.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * ADDRESS_STATE := {Size=1:60}
+ * The name of the state used in the address. Isolated for sorting or indexing.
+ */
+class AddressState extends AbstractElement
+{
+ protected const MAX_LENGTH = 60;
+}
diff --git a/app/Elements/AddressWebPage.php b/app/Elements/AddressWebPage.php
new file mode 100644
index 0000000000..45847209a5
--- /dev/null
+++ b/app/Elements/AddressWebPage.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Tree;
+
+use function e;
+
+/**
+ * ADDRESS_WEB_PAGE := {Size=5:120}
+ * The world wide web page address.
+ */
+class AddressWebPage extends AbstractElement
+{
+ protected const MAX_LENGTH = 120;
+
+ /**
+ * Display the value of this type of element.
+ *
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function value(string $value, Tree $tree): string
+ {
+ $canonical = $this->canonical($value);
+
+ return '<a dir="auto" href="' . e($canonical) . '">' . e($canonical) . '</a>';
+ }
+}
diff --git a/app/Elements/AdoptedByWhichParent.php b/app/Elements/AdoptedByWhichParent.php
new file mode 100644
index 0000000000..e07ee13d74
--- /dev/null
+++ b/app/Elements/AdoptedByWhichParent.php
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\I18N;
+
+use function strtoupper;
+
+/**
+ * ADDRESS_WEB_PAGE := {Size=1:4}
+ * [ HUSB | WIFE | BOTH ]
+ * A code which shows which parent in the associated family record adopted this person. Where:
+ * HUSB = The HUSBand in the associated family adopted this person.
+ * WIFE = The WIFE in the associated family adopted this person.
+ * BOTH = Both HUSBand and WIFE adopted this person.
+ */
+class AdoptedByWhichParent extends AbstractElement
+{
+ /**
+ * Convert a value to a canonical form.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public function canonical(string $value): string
+ {
+ return strtoupper(parent::canonical($value));
+ }
+
+ /**
+ * A list of controlled values for this element
+ *
+ * @return array<int|string,string>
+ */
+ public function values(): array
+ {
+ return [
+ '' => '',
+ 'BOTH' => I18N::translate('Adopted by both parents'),
+ 'HUSB' => I18N::translate('Adopted by father'),
+ 'WIFE' => I18N::translate('Adopted by mother'),
+ ];
+ }
+}
diff --git a/app/Elements/Adoption.php b/app/Elements/Adoption.php
new file mode 100644
index 0000000000..2ae30c5c08
--- /dev/null
+++ b/app/Elements/Adoption.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * Adoption
+ */
+class Adoption extends AbstractEventElement
+{
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'AGE' => '0:1',
+ 'PLAC' => '0:1',
+ 'FAMC' => '0:1',
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+}
diff --git a/app/Elements/AdultChristening.php b/app/Elements/AdultChristening.php
new file mode 100644
index 0000000000..a4656145d7
--- /dev/null
+++ b/app/Elements/AdultChristening.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * Adult Christening
+ */
+class AdultChristening extends AbstractElement
+{
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'AGE' => '0:1',
+ 'PLAC' => '0:1',
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+}
diff --git a/app/Elements/AgeAtEvent.php b/app/Elements/AgeAtEvent.php
new file mode 100644
index 0000000000..bd762ee366
--- /dev/null
+++ b/app/Elements/AgeAtEvent.php
@@ -0,0 +1,63 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use function strtolower;
+use function strtoupper;
+
+/**
+ * AGE_AT_EVENT := {Size=1:12}
+ * [ < | > | <NULL>]
+ * [ YYy MMm DDDd | YYy | MMm | DDDd |
+ * YYy MMm | YYy DDDd | MMm DDDd |
+ * CHILD | INFANT | STILLBORN ]
+ * ]
+ * Where:
+ * > = greater than indicated age
+ * < = less than indicated age
+ * y = a label indicating years
+ * m = a label indicating months
+ * d = a label indicating days
+ * YY = number of full years
+ * MM = number of months
+ * DDD = number of days
+ * CHILD = age < 8 years
+ * INFANT = age<1year
+ * STILLBORN = died just prior, at, or near birth, 0 years
+ */
+class AgeAtEvent extends AbstractElement
+{
+ protected const MAX_LENGTH = 12;
+
+ public function canonical(string $value): string
+ {
+ $value = parent::canonical($value);
+
+ $upper = strtoupper($value);
+
+ if ($upper === 'CHILD' || $upper === 'INFANT' || $upper === 'STILLBORN') {
+ $value = $upper;
+ } else {
+ $value = strtolower($value);
+ }
+
+ return $value;
+ }
+}
diff --git a/app/Elements/AncestralFileNumber.php b/app/Elements/AncestralFileNumber.php
new file mode 100644
index 0000000000..f081f5c617
--- /dev/null
+++ b/app/Elements/AncestralFileNumber.php
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Tree;
+
+use function e;
+use function strtoupper;
+
+/**
+ * ANCESTRAL_FILE_NUMBER := {Size=1:12}
+ * A unique permanent record number of an individual record contained in the
+ * Family History Department's Ancestral File.
+ */
+class AncestralFileNumber extends AbstractElement
+{
+ protected const MAX_LENGTH = 12;
+
+ /**
+ * Convert a value to a canonical form.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public function canonical(string $value): string
+ {
+ return strtoupper(parent::canonical($value));
+ }
+
+ /**
+ * Display the value of this type of element.
+ *
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function value(string $value, Tree $tree): string
+ {
+ return '<span dir="ltr">' . e($this->canonical($value)) . '</span>';
+ }
+}
diff --git a/app/Elements/Annulment.php b/app/Elements/Annulment.php
new file mode 100644
index 0000000000..7f25dff851
--- /dev/null
+++ b/app/Elements/Annulment.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * Annulment
+ */
+class Annulment extends AbstractEventElement
+{
+}
diff --git a/app/Elements/ApprovedSystemId.php b/app/Elements/ApprovedSystemId.php
new file mode 100644
index 0000000000..55ed6355bd
--- /dev/null
+++ b/app/Elements/ApprovedSystemId.php
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Tree;
+use Fisharebest\Webtrees\Webtrees;
+
+/**
+ * ANCESTRAL_FILE_NUMBER := {Size=1:20}
+ * A system identification name which was obtained through the GEDCOM
+ * registration process. This name must be unique from any other product.
+ * Spaces within the name must be substituted with a 0x5F (underscore _)
+ * so as to create one word.
+ */
+class ApprovedSystemId extends AbstractElement
+{
+ protected const MAX_LENGTH = 20;
+
+ /**
+ * Create a default value for this element.
+ *
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function default(Tree $tree): string
+ {
+ return Webtrees::NAME;
+ }
+}
diff --git a/app/Elements/AttributeDescriptor.php b/app/Elements/AttributeDescriptor.php
new file mode 100644
index 0000000000..75fa2872a4
--- /dev/null
+++ b/app/Elements/AttributeDescriptor.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * ATTRIBUTE_DESCRIPTOR := {Size=1:90}
+ * Text describing a particular characteristic or attribute assigned to an
+ * individual. This attribute value is assigned to the FACT tag. The
+ * classification of this specific attribute or fact is specified by the value
+ * of the subordinate TYPE tag selected from the EVENT_DETAIL structure. For
+ * example if you were classifying the skills a person had obtained;
+ * 1 FACT Woodworking
+ * 2 TYPE Skills
+ */
+class AttributeDescriptor extends AbstractElement
+{
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'PLAC' => '0:1',
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+}
diff --git a/app/Elements/AutomatedRecordId.php b/app/Elements/AutomatedRecordId.php
new file mode 100644
index 0000000000..72805b9d29
--- /dev/null
+++ b/app/Elements/AutomatedRecordId.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * AUTOMATED_RECORD_ID := {Size=1:12}
+ * A unique record identification number assigned to the record by the source
+ * system. This number is intended to serve as a more sure means of
+ * identification of a record for reconciling differences in data between two
+ * interfacing systems.
+ */
+class AutomatedRecordId extends AbstractElement
+{
+ protected const MAX_LENGTH = 12;
+}
diff --git a/app/Elements/Baptism.php b/app/Elements/Baptism.php
new file mode 100644
index 0000000000..ee175dcdde
--- /dev/null
+++ b/app/Elements/Baptism.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * Baptism
+ */
+class Baptism extends AbstractEventElement
+{
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'AGE' => '0:1',
+ 'PLAC' => '0:1',
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+}
diff --git a/app/Elements/BarMitzvah.php b/app/Elements/BarMitzvah.php
new file mode 100644
index 0000000000..dddc70e60d
--- /dev/null
+++ b/app/Elements/BarMitzvah.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * Bar Mitzvah
+ */
+class BarMitzvah extends AbstractEventElement
+{
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'AGE' => '0:1',
+ 'PLAC' => '0:1',
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+}
diff --git a/app/Elements/BasMitzvah.php b/app/Elements/BasMitzvah.php
new file mode 100644
index 0000000000..160dd298a6
--- /dev/null
+++ b/app/Elements/BasMitzvah.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * Bas Mitzvah
+ */
+class BasMitzvah extends AbstractEventElement
+{
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'AGE' => '0:1',
+ 'PLAC' => '0:1',
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+}
diff --git a/app/Elements/Birth.php b/app/Elements/Birth.php
new file mode 100644
index 0000000000..cd7d3b7033
--- /dev/null
+++ b/app/Elements/Birth.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * Birth
+ */
+class Birth extends AbstractEventElement
+{
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'PLAC' => '0:1',
+ 'FAMC' => '0:1',
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+}
diff --git a/app/Elements/Blessing.php b/app/Elements/Blessing.php
new file mode 100644
index 0000000000..4b06d07e4e
--- /dev/null
+++ b/app/Elements/Blessing.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * Blessing
+ */
+class Blessing extends AbstractElement
+{
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'AGE' => '0:1',
+ 'PLAC' => '0:1',
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+}
diff --git a/app/Elements/Burial.php b/app/Elements/Burial.php
new file mode 100644
index 0000000000..5c2a548146
--- /dev/null
+++ b/app/Elements/Burial.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * Burial
+ */
+class Burial extends AbstractEventElement
+{
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'PLAC' => '0:1',
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+}
diff --git a/app/Elements/CasteName.php b/app/Elements/CasteName.php
new file mode 100644
index 0000000000..1716154b85
--- /dev/null
+++ b/app/Elements/CasteName.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * CASTE_NAME := {Size=1:90}
+ * A name assigned to a particular group that this person was associated with,
+ * such as a particular racial group, religious group, or a group with an
+ * inherited status.
+ */
+class CasteName extends AbstractElement
+{
+ protected const MAX_LENGTH = 90;
+
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'PLAC' => '0:1',
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+}
diff --git a/app/Elements/CauseOfEvent.php b/app/Elements/CauseOfEvent.php
new file mode 100644
index 0000000000..9eb3fe3204
--- /dev/null
+++ b/app/Elements/CauseOfEvent.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * CAUSE_OF_EVENT := {Size=1:90}
+ * Used in special cases to record the reasons which precipitated an event.
+ * Normally this will be used subordinate to a death event to show cause of
+ * death, such as might be listed on a death certificate.
+ */
+class CauseOfEvent extends AbstractElement
+{
+ protected const MAX_LENGTH = 90;
+}
diff --git a/app/Elements/Census.php b/app/Elements/Census.php
new file mode 100644
index 0000000000..772bb2edfe
--- /dev/null
+++ b/app/Elements/Census.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * Census
+ */
+class Census extends AbstractElement
+{
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'AGE' => '0:1',
+ 'PLAC' => '0:1',
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+}
diff --git a/app/Elements/CertaintyAssessment.php b/app/Elements/CertaintyAssessment.php
new file mode 100644
index 0000000000..bc4d68174f
--- /dev/null
+++ b/app/Elements/CertaintyAssessment.php
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\I18N;
+
+/**
+ * CERTAINTY_ASSESSMENT := {Size=1:1}
+ * [0|1|2|3]
+ * The QUAY tag's value conveys the submitter's quantitative evaluation of the
+ * credibility of a piece of information, based upon its supporting evidence.
+ * Some systems use this feature to rank multiple conflicting opinions for
+ * display of most likely information first. It is not intended to eliminate
+ * the receiver's need to evaluate the evidence for themselves.
+ * 0 = Unreliable evidence or estimated data
+ * 1 = Questionable reliability of evidence (interviews, census, oral
+ * genealogies, or potential for bias for example, an autobiography)
+ * 2 = Secondary evidence, data officially recorded sometime after event
+ * 3 = Direct and primary evidence used, or by dominance of the evidence
+ */
+class CertaintyAssessment extends AbstractElement
+{
+ /**
+ * A list of controlled values for this element
+ *
+ * @return array<int|string,string>
+ */
+ public function values(): array
+ {
+ return [
+ '' => '',
+ '0' => /* I18N: Quality of source information - GEDCOM tag “QUAY 0” */ I18N::translate('unreliable evidence'),
+ '1' => /* I18N: Quality of source information - GEDCOM tag “QUAY 1” */ I18N::translate('questionable evidence'),
+ '2' => /* I18N: Quality of source information - GEDCOM tag “QUAY 2” */ I18N::translate('secondary evidence'),
+ '3' => /* I18N: Quality of source information - GEDCOM tag “QUAY 3” */ I18N::translate('primary evidence'),
+ ];
+ }
+}
diff --git a/app/Elements/Change.php b/app/Elements/Change.php
new file mode 100644
index 0000000000..a7889d6d89
--- /dev/null
+++ b/app/Elements/Change.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * *:CHAN is an empty element with children; DATE and NOTE.
+ */
+class Change extends EmptyElement
+{
+}
diff --git a/app/Elements/ChangeDate.php b/app/Elements/ChangeDate.php
new file mode 100644
index 0000000000..767e8e063f
--- /dev/null
+++ b/app/Elements/ChangeDate.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Date;
+use Fisharebest\Webtrees\Tree;
+
+/**
+ * CHANGE_DATE := {Size=10:11}
+ * <DATE_EXACT>
+ * The date that this data was changed.
+ */
+class ChangeDate extends AbstractElement
+{
+ /**
+ * Display the value of this type of element.
+ *
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function value(string $value, Tree $tree): string
+ {
+ $canonical = $this->canonical($value);
+
+ $date = new Date($canonical);
+
+ return $date->display(false);
+ }
+}
diff --git a/app/Elements/CharacterSet.php b/app/Elements/CharacterSet.php
new file mode 100644
index 0000000000..e0da807994
--- /dev/null
+++ b/app/Elements/CharacterSet.php
@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Tree;
+
+use function strtoupper;
+
+/**
+ * CHARACTER_SET := {Size=1:8}
+ * [ ANSEL |UTF-8 | UNICODE | ASCII ]
+ * A code value that represents the character set to be used to interpret this
+ * data. Currently, the preferred character set is ANSEL, which includes ASCII
+ * as a subset. UNICODE is not widely supported by most operating systems;
+ * therefore, GEDCOM produced using the UNICODE character set will be limited
+ * in its interchangeability for a while but should eventually provide the
+ * international flexibility that is desired. See Chapter 3, starting on page
+ * 77.
+ * Note:The IBMPC character set is not allowed. This character set cannot be
+ * interpreted properly without knowing which code page the sender was using.
+ */
+class CharacterSet extends AbstractElement
+{
+ /**
+ * Convert a value to a canonical form.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public function canonical(string $value): string
+ {
+ return strtoupper(parent::canonical($value));
+ }
+
+ /**
+ * Create a default value for this element.
+ *
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function default(Tree $tree): string
+ {
+ return 'UTF-8';
+ }
+
+ /**
+ * A list of controlled values for this element
+ *
+ * @return array<int|string,string>
+ */
+ public function values(): array
+ {
+ return [
+ 'UTF-8' => 'UTF-8',
+ 'UNICODE' => 'UNICODE',
+ 'ANSEL' => 'ANSEL',
+ 'ASCII' => 'ASCII',
+ ];
+ }
+}
diff --git a/app/Elements/ChildLinkageStatus.php b/app/Elements/ChildLinkageStatus.php
new file mode 100644
index 0000000000..e967de2aac
--- /dev/null
+++ b/app/Elements/ChildLinkageStatus.php
@@ -0,0 +1,66 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\I18N;
+
+use function strtolower;
+
+/**
+ * CHILD_LINKAGE_STATUS := {Size=1:15}
+ * [challenged | disproven | proven]
+ * A status code that allows passing on the users opinion of the status of a
+ * child to family link.
+ * challenged = Linking this child to this family is suspect, but the linkage
+ * has been neither proven nor disproven.
+ * disproven = There has been a claim by some that this child belongs to this
+ * family, but the linkage has been disproven.
+ * proven = There has been a claim by some that this child does not belongs
+ * to this family, but the linkage has been proven.
+ */
+class ChildLinkageStatus extends AbstractElement
+{
+ /**
+ * Convert a value to a canonical form.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public function canonical(string $value): string
+ {
+ return strtolower(parent::canonical($value));
+ }
+
+ /**
+ * A list of controlled values for this element
+ *
+ * @return array<int|string,string>
+ */
+ public function values(): array
+ {
+ return [
+ '' => '',
+ 'challenged' => I18N::translate('challenged'),
+ 'disproven' => I18N::translate('disproven'),
+ 'proven' => I18N::translate('proven'),
+ ];
+ }
+}
diff --git a/app/Elements/Christening.php b/app/Elements/Christening.php
new file mode 100644
index 0000000000..a29aeb3e24
--- /dev/null
+++ b/app/Elements/Christening.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * Christening
+ */
+class Christening extends AbstractElement
+{
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'AGE' => '0:1',
+ 'PLAC' => '0:1',
+ 'FAMC' => '0:1',
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+}
diff --git a/app/Elements/Confirmation.php b/app/Elements/Confirmation.php
new file mode 100644
index 0000000000..c8fab8ee23
--- /dev/null
+++ b/app/Elements/Confirmation.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * Confirmation
+ */
+class Confirmation extends AbstractElement
+{
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'AGE' => '0:1',
+ 'PLAC' => '0:1',
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+}
diff --git a/app/Elements/ContentDescription.php b/app/Elements/ContentDescription.php
new file mode 100644
index 0000000000..5d0570d757
--- /dev/null
+++ b/app/Elements/ContentDescription.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * GEDCOM_CONTENT_DESCRIPTION := {Size=1:248}
+ * A note that a user enters to describe the contents of the lineage-linked
+ * file in terms of "ancestors or descendants of" so that the person
+ * receiving the data knows what genealogical information the transmission
+ * contains.
+ */
+class ContentDescription extends AbstractElement
+{
+}
diff --git a/app/Elements/CopyrightFile.php b/app/Elements/CopyrightFile.php
new file mode 100644
index 0000000000..d4fb0332c4
--- /dev/null
+++ b/app/Elements/CopyrightFile.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * COPYRIGHT_GEDCOM_FILE := {Size=1:90}
+ * A copyright statement needed to protect the copyrights of the submitter of
+ * this GEDCOM file.
+ */
+class CopyrightFile extends AbstractElement
+{
+ protected const MAX_LENGTH = 90;
+}
diff --git a/app/Elements/CopyrightSourceData.php b/app/Elements/CopyrightSourceData.php
new file mode 100644
index 0000000000..9305f06916
--- /dev/null
+++ b/app/Elements/CopyrightSourceData.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * COPYRIGHT_SOURCE_DATA := {Size=1:90}
+ * A copyright statement required by the owner of data from which this
+ * information was down- loaded. For example, when a GEDCOM down-load is
+ * requested from the Ancestral File, this would be the copyright statement to
+ * indicate that the data came from a copyrighted source.
+ */
+class CopyrightSourceData extends AbstractElement
+{
+ protected const MAX_LENGTH = 90;
+}
diff --git a/app/Elements/CountOfChildren.php b/app/Elements/CountOfChildren.php
new file mode 100644
index 0000000000..ebafc026c9
--- /dev/null
+++ b/app/Elements/CountOfChildren.php
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Tree;
+
+/**
+ * COUNT_OF_CHILDREN := {Size=1:3}
+ * The known number of children of this individual from all marriages or, if
+ * subordinate to a family record, the reported number of children known to
+ * belong to this family, regardless of whether the associated children are
+ * represented in the corresponding structure. This is not necessarily the
+ * count of children listed in a family structure.
+ */
+class CountOfChildren extends AbstractElement
+{
+ protected const SUBTAGS = [
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+ /**
+ * Display the value of this type of element.
+ *
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function value(string $value, Tree $tree): string
+ {
+ return $this->valueNumeric($value);
+ }
+}
diff --git a/app/Elements/CountOfMarriages.php b/app/Elements/CountOfMarriages.php
new file mode 100644
index 0000000000..517f821060
--- /dev/null
+++ b/app/Elements/CountOfMarriages.php
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Tree;
+
+/**
+ * COUNT_OF_MARRIAGES := {Size=1:3}
+ * The number of different families that this person was known to have been a
+ * member of as a spouse or parent, regardless of whether the associated
+ * families are represented in the GEDCOM file.
+ */
+class CountOfMarriages extends AbstractElement
+{
+ protected const SUBTAGS = [
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+
+ /**
+ * Display the value of this type of element.
+ *
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function value(string $value, Tree $tree): string
+ {
+ return $this->valueNumeric($value);
+ }
+}
diff --git a/app/Elements/Cremation.php b/app/Elements/Cremation.php
new file mode 100644
index 0000000000..e718948b68
--- /dev/null
+++ b/app/Elements/Cremation.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * Cremation
+ */
+class Cremation extends AbstractElement
+{
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'PLAC' => '0:1',
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+}
diff --git a/app/Elements/CustomElement.php b/app/Elements/CustomElement.php
new file mode 100644
index 0000000000..255ffd7802
--- /dev/null
+++ b/app/Elements/CustomElement.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * A custom GEDCOM element.
+ */
+class CustomElement extends AbstractElement
+{
+}
diff --git a/app/Elements/CustomEvent.php b/app/Elements/CustomEvent.php
new file mode 100644
index 0000000000..56b4929acb
--- /dev/null
+++ b/app/Elements/CustomEvent.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * CustomEvent
+ */
+class CustomEvent extends AbstractEventElement
+{
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'AGE' => '0:1',
+ 'PLAC' => '0:1',
+ 'ADDR' => '0:1',
+ 'AGNC' => '0:1',
+ 'CAUS' => '0:1',
+ 'RELI' => '0:1',
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+}
diff --git a/app/Elements/CustomFact.php b/app/Elements/CustomFact.php
new file mode 100644
index 0000000000..54bff382d4
--- /dev/null
+++ b/app/Elements/CustomFact.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * CustomFact
+ */
+class CustomFact extends AbstractElement
+{
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'PLAC' => '0:1',
+ 'ADDR' => '0:1',
+ 'AGNC' => '0:1',
+ 'CAUS' => '0:1',
+ 'RELI' => '0:1',
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+}
diff --git a/app/Elements/DateLdsOrd.php b/app/Elements/DateLdsOrd.php
new file mode 100644
index 0000000000..bf95246f6d
--- /dev/null
+++ b/app/Elements/DateLdsOrd.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * DATE := {Size=4:35}
+ * <DATE_VALUE>
+ * LDS ordinance dates use only the Gregorian date and most often use the form
+ * of day, month, and year. Only in rare instances is there a partial date. The
+ * temple tag and code should always accompany temple ordinance dates.
+ * Sometimes the LDS_(ordinance)_DATE_STATUS is used to indicate that an
+ * ordinance date and temple code is not required, such as when BIC is used.
+ * (See LDS_(ordinance)_DATE_STATUS definitions beginning on page 51.)
+ */
+class DateLdsOrd extends AbstractElement
+{
+}
diff --git a/app/Elements/DateValue.php b/app/Elements/DateValue.php
new file mode 100644
index 0000000000..9f1b70496a
--- /dev/null
+++ b/app/Elements/DateValue.php
@@ -0,0 +1,103 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Date;
+use Fisharebest\Webtrees\I18N;
+use Fisharebest\Webtrees\Services\LocalizationService;
+use Fisharebest\Webtrees\Tree;
+
+use function app;
+use function e;
+use function preg_replace_callback;
+use function view;
+
+/**
+ * DATE_VALUE := {Size=1:35}
+ * [ <DATE> | <DATE_PERIOD> | <DATE_RANGE>| <DATE_APPROXIMATED> | INT <DATE> (<DATE_PHRASE>) | (<DATE_PHRASE>) ]
+ * The DATE_VALUE represents the date of an activity, attribute, or event where:
+ * INT = Interpreted from knowledge about the associated date phrase included in parentheses.
+ * An acceptable alternative to the date phrase choice is to use one of the other choices such as
+ * <DATE_APPROXIMATED> choice as the DATE line value and then include the date phrase value as a
+ * NOTE value subordinate to the DATE line tag.
+ * The date value can take on the date form of just a date, an approximated date, between a date
+ * and another date, and from one date to another date. The preferred form of showing date
+ * imprecision, is to show, for example, MAY 1890 rather than ABT 12 MAY 1890. This is because
+ * limits have not been assigned to the precision of the prefixes such as ABT or EST.
+ */
+class DateValue extends AbstractElement
+{
+ /**
+ * An edit control for this data.
+ *
+ * @param string $id
+ * @param string $name
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function edit(string $id, string $name, string $value, Tree $tree): string
+ {
+ // Need to know if the user prefers DMY/MDY/YMD so we can validate dates properly.
+ $dmy = app(LocalizationService::class)->dateFormatToOrder(I18N::dateFormat());
+
+ return
+ '<div class="input-group">' .
+ '<input class="form-control" type="text" id="' . $id . '" name="' . $name . '" value="' . e($value) . '" onchange="webtrees.reformatDate(this, \'' . e($dmy) . '\')" dir="ltr">' .
+ view('edit/input-addon-calendar', ['id' => $id]) .
+ view('edit/input-addon-help', ['fact' => 'DATE']) .
+ '</div>' .
+ '<div id="caldiv' . $id . '" style="position:absolute;visibility:hidden;background-color:white;z-index:1000"></div>' .
+ '<p class="text-muted">' . (new Date($value))->display() . '</p>';
+ }
+
+ /**
+ * Escape @ signs in a GEDCOM export.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public function escape(string $value): string
+ {
+ // Only escape @ signs in an INT phrase
+ return preg_replace_callback('/\(.*@.*\)/', static function (array $matches): string {
+ return strtr($matches[0], ['@' => '@@']);
+ }, $value);
+ }
+
+ /**
+ * Display the value of this type of element.
+ *
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function value(string $value, Tree $tree): string
+ {
+ $canonical = $this->canonical($value);
+
+ $date = new Date($canonical);
+
+ return $date->display(true);
+ }
+}
diff --git a/app/Elements/Death.php b/app/Elements/Death.php
new file mode 100644
index 0000000000..88775e7984
--- /dev/null
+++ b/app/Elements/Death.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * Death
+ */
+class Death extends AbstractEventElement
+{
+ protected const SUBTAGS = [
+ 'CAUS' => '0:1',
+ 'DATE' => '0:1',
+ 'AGE' => '0:1',
+ 'PLAC' => '0:1',
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+}
diff --git a/app/Elements/DescriptiveTitle.php b/app/Elements/DescriptiveTitle.php
new file mode 100644
index 0000000000..21c02e2c36
--- /dev/null
+++ b/app/Elements/DescriptiveTitle.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * DESCRIPTIVE_TITLE := {Size=1:248}
+ * The title of a work, record, item, or object.
+ */
+class DescriptiveTitle extends AbstractElement
+{
+}
diff --git a/app/Elements/Divorce.php b/app/Elements/Divorce.php
new file mode 100644
index 0000000000..f401496888
--- /dev/null
+++ b/app/Elements/Divorce.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * Divorce
+ */
+class Divorce extends AbstractEventElement
+{
+}
diff --git a/app/Elements/DivorceFiled.php b/app/Elements/DivorceFiled.php
new file mode 100644
index 0000000000..f58e2bc423
--- /dev/null
+++ b/app/Elements/DivorceFiled.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * Divorce Filed
+ */
+class DivorceFiled extends AbstractEventElement
+{
+}
diff --git a/app/Elements/Emigration.php b/app/Elements/Emigration.php
new file mode 100644
index 0000000000..9c1cf9149c
--- /dev/null
+++ b/app/Elements/Emigration.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * Emigration
+ */
+class Emigration extends AbstractElement
+{
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'AGE' => '0:1',
+ 'PLAC' => '0:1',
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+}
diff --git a/app/Elements/EmptyElement.php b/app/Elements/EmptyElement.php
new file mode 100644
index 0000000000..74d94c6134
--- /dev/null
+++ b/app/Elements/EmptyElement.php
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Tree;
+
+use function e;
+
+/**
+ * An empty element with no data - only child elements.
+ */
+class EmptyElement extends AbstractElement
+{
+ /**
+ * An edit control for this data.
+ *
+ * @param string $id
+ * @param string $name
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function edit(string $id, string $name, string $value, Tree $tree): string
+ {
+ return '<input class="form-control" type="hidden" id="' . e($id) . '" name="' . e($name) . '" value="' . e($value) . '">';
+ }
+}
diff --git a/app/Elements/Engagement.php b/app/Elements/Engagement.php
new file mode 100644
index 0000000000..7ad78abad5
--- /dev/null
+++ b/app/Elements/Engagement.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * Engagement
+ */
+class Engagement extends AbstractElement
+{
+}
diff --git a/app/Elements/EntryRecordingDate.php b/app/Elements/EntryRecordingDate.php
new file mode 100644
index 0000000000..fe933263c0
--- /dev/null
+++ b/app/Elements/EntryRecordingDate.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Date;
+use Fisharebest\Webtrees\Tree;
+
+/**
+ * ENTRY_RECORDING_DATE := {Size=1:90}
+ * <DATE_VALUE>
+ * The date that this event data was entered into the original source document.
+ */
+class EntryRecordingDate extends AbstractElement
+{
+ /**
+ * Display the value of this type of element.
+ *
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function value(string $value, Tree $tree): string
+ {
+ $canonical = $this->canonical($value);
+
+ $date = new Date($canonical);
+
+ return $date->display(false);
+ }
+}
diff --git a/app/Elements/EventAttributeType.php b/app/Elements/EventAttributeType.php
new file mode 100644
index 0000000000..797158f2b5
--- /dev/null
+++ b/app/Elements/EventAttributeType.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * EVENT_ATTRIBUTE_TYPE := {Size=1:15}
+ * [ <EVENT_TYPE_INDIVIDUAL> | <EVENT_TYPE_FAMILY> | <ATTRIBUTE_TYPE> ]
+ * A code that classifies the principal event or happening that caused the
+ * source record entry to be created. If the event or attribute doesn't
+ * translate to one of these tag codes, then a user supplied value is
+ * expected and will be generally classified in the category of other.
+ */
+class EventAttributeType extends AbstractElement
+{
+ protected const MAX_LENGTH = 15;
+}
diff --git a/app/Elements/EventDescriptor.php b/app/Elements/EventDescriptor.php
new file mode 100644
index 0000000000..a27e774da9
--- /dev/null
+++ b/app/Elements/EventDescriptor.php
@@ -0,0 +1,67 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Tree;
+
+/**
+ * EVENT_DESCRIPTOR := {Size=1:90}
+ * Text describing a particular event pertaining to the individual or family.
+ * This event value is usually assigned to the EVEN tag. The classification as
+ * to the difference between this specific event and other occurrences of the
+ * EVENt tag is indicated by the use of a subordinate TYPE tag selected from
+ * the EVENT_DETAIL structure. For example;
+ * 1 EVEN Appointed Zoning Committee Chairperson
+ * 2 TYPE Civic Appointments
+ * 2 DATE FROM JAN 1952 TO JAN 1956
+ * 2 PLAC Cove, Cache, Utah
+ * 2 AGNC Cove City Redevelopment
+ */
+class EventDescriptor extends AbstractElement
+{
+ protected const MAX_LENGTH = 90;
+
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'PLAC' => '0:1',
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+
+ /**
+ * Display the value of this type of element.
+ *
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function value(string $value, Tree $tree): string
+ {
+ // This is a special value used for creating events of close relatives.
+ if ($value === 'CLOSE_RELATIVE') {
+ return '';
+ }
+
+ return parent::value($value, $tree);
+ }
+}
diff --git a/app/Elements/EventOrFactClassification.php b/app/Elements/EventOrFactClassification.php
new file mode 100644
index 0000000000..50a95091e6
--- /dev/null
+++ b/app/Elements/EventOrFactClassification.php
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * EVENT_OR_FACT_CLASSIFICA TION := {Size=1:90}
+ * A descriptive word or phrase used to further classify the parent event or
+ * attribute tag. This should be used whenever either of the generic EVEN or
+ * FACT tags are used. The value of this primative is responsible for
+ * classifying the generic event or fact being cited. For example, if the
+ * attribute being defined was one of the persons skills, such as woodworking,
+ * the FACT tag would have the value of `Woodworking', followed by a
+ * subordinate TYPE tag with the value `Skills.'
+ * 1 FACT Woodworking
+ * 2 TYPE Skills
+ * This groups the fact into a generic skills attribute, and in particular this
+ * entry records the fact that this individual possessed the skill of
+ * woodworking. Using the subordinate TYPE tag classification method with any
+ * of the other defined event tags provides a further classification of the
+ * parent tag but does not change the basic meaning of the parent tag. For
+ * example, a MARR tag could be subordinated with a TYPE tag with an
+ * EVENT_DESCRIPTOR value of `Common Law.'
+ * 1 MARR
+ * 2 TYPE Common Law
+ * This classifies the entry as a common law marriage but the event is still a
+ * marriage event. Other descriptor values might include, for example,
+ * `stillborn' as a qualifier to BIRTh or `Tribal Custom' as a qualifier to
+ * MARRiage.
+ */
+class EventOrFactClassification extends AbstractElement
+{
+ protected const MAX_LENGTH = 90;
+}
diff --git a/app/Elements/EventTypeCitedFrom.php b/app/Elements/EventTypeCitedFrom.php
new file mode 100644
index 0000000000..30eeace39e
--- /dev/null
+++ b/app/Elements/EventTypeCitedFrom.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * EVENT_OR_FACT_CLASSIFICATION := {Size=1:15}
+ * [ <EVENT_ATTRIBUTE_TYPE> ]
+ * A code that indicates the type of event which was responsible for the source
+ * entry being recorded. For example, if the entry was created to record a
+ * birth of a child, then the type would be BIRT regardless of the assertions
+ * made from that record, such as the mother's name or mother's birth date.
+ * This will allow a prioritized best view choice and a determination of the
+ * certainty associated with the source used in asserting the cited fact.
+ */
+class EventTypeCitedFrom extends AbstractElement
+{
+ protected const MAX_LENGTH = 15;
+}
diff --git a/app/Elements/EventsRecorded.php b/app/Elements/EventsRecorded.php
new file mode 100644
index 0000000000..4ff3efe2bc
--- /dev/null
+++ b/app/Elements/EventsRecorded.php
@@ -0,0 +1,171 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\I18N;
+use Fisharebest\Webtrees\Registry;
+use Fisharebest\Webtrees\Tree;
+use Illuminate\Support\Collection;
+use Ramsey\Uuid\Uuid;
+
+use function array_map;
+use function explode;
+use function implode;
+use function strtoupper;
+use function view;
+
+/**
+ * EVENTS_RECORDED := {Size=1:90}
+ * [<EVENT_ATTRIBUTE_TYPE> | <EVENTS_RECORDED>, <EVENT_ATTRIBUTE_TYPE>]
+ * An enumeration of the different kinds of events that were recorded in a
+ * particular source. Each enumeration is separated by a comma. Such as a
+ * parish register of births, deaths, and marriages would be BIRT, DEAT, MARR.
+ */
+class EventsRecorded extends AbstractElement
+{
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'PLAC' => '0:1',
+ ];
+
+ protected const EVENTS_RECORDED = [
+ 'INDI:ADOP',
+ 'INDI:BAPM',
+ 'INDI:BARM',
+ 'INDI:BASM',
+ 'INDI:BIRT',
+ 'INDI:BLES',
+ 'INDI:BURI',
+ 'INDI:CAST',
+ 'INDI:CHR',
+ 'INDI:CENS',
+ 'INDI:CHRA',
+ 'INDI:CONF',
+ 'INDI:CREM',
+ 'INDI:DEAT',
+ 'INDI:DSCR',
+ 'INDI:EDUC',
+ 'INDI:EMIG',
+ 'INDI:FCOM',
+ 'INDI:GRAD',
+ 'INDI:IDNO',
+ 'INDI:IMMI',
+ 'INDI:NATI',
+ 'INDI:NATU',
+ 'INDI:NCHI',
+ 'INDI:NMR',
+ 'INDI:OCCU',
+ 'INDI:ORDN',
+ 'INDI:PROB',
+ 'INDI:PROP',
+ 'INDI:RELI',
+ 'INDI:RESI',
+ 'INDI:RETI',
+ 'INDI:SSN',
+ 'INDI:TITL',
+ 'INDI:WILL',
+ 'FAM:ANUL',
+ 'FAM:DIV',
+ 'FAM:DIVF',
+ 'FAM:ENGA',
+ 'FAM:MARB',
+ 'FAM:MARC',
+ 'FAM:MARL',
+ 'FAM:MARS',
+ 'FAM:MARR',
+ ];
+
+ /**
+ * Convert a value to a canonical form.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public function canonical(string $value): string
+ {
+ return strtoupper(strtr(parent::canonical($value), [' ' => '']));
+ }
+
+ /**
+ * An edit control for this data.
+ *
+ * @param string $id
+ * @param string $name
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function edit(string $id, string $name, string $value, Tree $tree): string
+ {
+ $factory = Registry::elementFactory();
+
+ $options = Collection::make(self::EVENTS_RECORDED)
+ ->mapWithKeys(static function (string $tag) use ($factory): array {
+ return [explode(':', $tag)[1] => $factory->make($tag)->label()];
+ })
+ ->sort()
+ ->all();
+
+ $id2 = Uuid::uuid4()->toString();
+
+ // Our form element name contains "[]", and multiple selections would create multiple values.
+ $hidden = '<input type="hidden" id="' . e($id) . '" name="' . e($name) . '" value="' . e($value) . '">';
+ // Combine them into a single value.
+ // The change event doesn't seem to fire for select2 controls, so use form.submit instead.
+ $js = 'document.getElementById("' . $id2 . '").form.addEventListener("submit", function () { document.getElementById("' . $id . '").value = Array.from(document.getElementById("' . $id2 . '").selectedOptions).map(x => x.value).join(","); });';
+
+ return view('components/select', [
+ 'class' => 'select2',
+ 'name' => '',
+ 'id' => $id2,
+ 'options' => $options,
+ 'selected' => explode(',', strtr($value, [' ' => ''])),
+ ]) . $hidden . '<script>' . $js . '</script>';
+ }
+
+ /**
+ * Display the value of this type of element.
+ *
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function value(string $value, Tree $tree): string
+ {
+ $tags = explode(',', $this->canonical($value));
+
+ $events = array_map(static function (string $tag): string {
+ foreach (['INDI', 'FAM'] as $record_type) {
+ $element = Registry::elementFactory()->make($record_type . ':' . $tag);
+
+ if (!$element instanceof UnknownElement) {
+ return $element->label();
+ }
+ }
+
+ return $tag;
+ }, $tags);
+
+ return implode(I18N::$list_separator, $events);
+ }
+}
diff --git a/app/Elements/FileName.php b/app/Elements/FileName.php
new file mode 100644
index 0000000000..8e408d0b07
--- /dev/null
+++ b/app/Elements/FileName.php
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * FILE_NAME := {Size=1:90}
+ * The name of the GEDCOM transmission file. If the file name includes a file
+ * extension it must be shown in the form (filename.ext).
+ */
+class FileName extends AbstractElement
+{
+ protected const MAX_LENGTH = 90;
+
+ /**
+ * Convert a value to a canonical form.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public function canonical(string $value): string
+ {
+ // Don't change spaces. " Foo bar.jpeg" is a valid file name!
+ return $value;
+ }
+}
diff --git a/app/Elements/FirstCommunion.php b/app/Elements/FirstCommunion.php
new file mode 100644
index 0000000000..eb24946a88
--- /dev/null
+++ b/app/Elements/FirstCommunion.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * First Communion
+ */
+class FirstCommunion extends AbstractElement
+{
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'AGE' => '0:1',
+ 'PLAC' => '0:1',
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+}
diff --git a/app/Elements/Form.php b/app/Elements/Form.php
new file mode 100644
index 0000000000..e264b5daf9
--- /dev/null
+++ b/app/Elements/Form.php
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Tree;
+
+/**
+ * GEDCOM_FORM := {Size=14:20}
+ * [ LINEAGE-LINKED ]
+ * The GEDCOM form used to construct this transmission. There maybe other forms
+ * used such as CommSoft's "EVENT_LINEAGE_LINKED" but these specifications
+ * define only the LINEAGE-LINKED Form. Systems will use this value to specify
+ * GEDCOM compatible with these specifications.
+ */
+class Form extends AbstractElement
+{
+ protected const MAX_LENGTH = 20;
+
+ /**
+ * Create a default value for this element.
+ *
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function default(Tree $tree): string
+ {
+ return 'LINEAGE-LINKED';
+ }
+}
diff --git a/app/Elements/Gedcom.php b/app/Elements/Gedcom.php
new file mode 100644
index 0000000000..a1071a994c
--- /dev/null
+++ b/app/Elements/Gedcom.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * HEAD:GEDC is an empty element with children; VERS and FORM.
+ */
+class Gedcom extends EmptyElement
+{
+}
diff --git a/app/Elements/GenerationsOfAncestors.php b/app/Elements/GenerationsOfAncestors.php
new file mode 100644
index 0000000000..c79714053a
--- /dev/null
+++ b/app/Elements/GenerationsOfAncestors.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Tree;
+
+/**
+ * GENERATIONS_OF_ANCESTORS := {Size=1:4}
+ * The number of generations of ancestors included in this transmission. This
+ * value is usually provided when FamilySearch programs build a GEDCOM file for
+ * a patron requesting a download of ancestors.
+ */
+class GenerationsOfAncestors extends AbstractElement
+{
+ protected const MAX_LENGTH = 4;
+
+ /**
+ * Display the value of this type of element.
+ *
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function value(string $value, Tree $tree): string
+ {
+ return $this->valueNumeric($value);
+ }
+}
diff --git a/app/Elements/GenerationsOfDescendants.php b/app/Elements/GenerationsOfDescendants.php
new file mode 100644
index 0000000000..844a304375
--- /dev/null
+++ b/app/Elements/GenerationsOfDescendants.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Tree;
+
+/**
+ * GENERATIONS_OF_DESCENDANTS := {Size=1:4}
+ * The number of generations of descendants included in this transmission. This
+ * value is usually provided when FamilySearch programs build a GEDCOM file for
+ * a patron requesting a download of descendants.
+ */
+class GenerationsOfDescendants extends AbstractElement
+{
+ protected const MAX_LENGTH = 4;
+
+ /**
+ * Display the value of this type of element.
+ *
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function value(string $value, Tree $tree): string
+ {
+ return $this->valueNumeric($value);
+ }
+}
diff --git a/app/Elements/GovId.php b/app/Elements/GovId.php
new file mode 100644
index 0000000000..f1c8fe88cc
--- /dev/null
+++ b/app/Elements/GovId.php
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Tree;
+
+use function e;
+
+/**
+ * A custom field used in _LOC records
+ */
+class GovId extends AbstractElement
+{
+ /**
+ * Display the value of this type of element.
+ *
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function value(string $value, Tree $tree): string
+ {
+ $canonical = $this->canonical($value);
+
+ return '<a dir="ltr" href="https://gov.genealogy.net/item/show/' . e($canonical) . '">' . e($canonical) . '</a>';
+ }
+}
diff --git a/app/Elements/Graduation.php b/app/Elements/Graduation.php
new file mode 100644
index 0000000000..476211fc7f
--- /dev/null
+++ b/app/Elements/Graduation.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * Graduation
+ */
+class Graduation extends AbstractEventElement
+{
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'AGE' => '0:1',
+ 'PLAC' => '0:1',
+ 'AGNC' => '0:1',
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+}
diff --git a/app/Elements/Immigration.php b/app/Elements/Immigration.php
new file mode 100644
index 0000000000..d712fae3be
--- /dev/null
+++ b/app/Elements/Immigration.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * Immigration
+ */
+class Immigration extends AbstractElement
+{
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'AGE' => '0:1',
+ 'PLAC' => '0:1',
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+}
diff --git a/app/Elements/LanguageId.php b/app/Elements/LanguageId.php
new file mode 100644
index 0000000000..de931e31cd
--- /dev/null
+++ b/app/Elements/LanguageId.php
@@ -0,0 +1,216 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Localization\Locale\LocaleAf;
+use Fisharebest\Localization\Locale\LocaleAm;
+use Fisharebest\Localization\Locale\LocaleAng;
+use Fisharebest\Localization\Locale\LocaleAr;
+use Fisharebest\Localization\Locale\LocaleAs;
+use Fisharebest\Localization\Locale\LocaleBe;
+use Fisharebest\Localization\Locale\LocaleBg;
+use Fisharebest\Localization\Locale\LocaleBn;
+use Fisharebest\Localization\Locale\LocaleBo;
+use Fisharebest\Localization\Locale\LocaleCa;
+use Fisharebest\Localization\Locale\LocaleCaEsValencia;
+use Fisharebest\Localization\Locale\LocaleCs;
+use Fisharebest\Localization\Locale\LocaleCu;
+use Fisharebest\Localization\Locale\LocaleDa;
+use Fisharebest\Localization\Locale\LocaleDe;
+use Fisharebest\Localization\Locale\LocaleEl;
+use Fisharebest\Localization\Locale\LocaleEn;
+use Fisharebest\Localization\Locale\LocaleEo;
+use Fisharebest\Localization\Locale\LocaleEs;
+use Fisharebest\Localization\Locale\LocaleEt;
+use Fisharebest\Localization\Locale\LocaleFa;
+use Fisharebest\Localization\Locale\LocaleFi;
+use Fisharebest\Localization\Locale\LocaleFo;
+use Fisharebest\Localization\Locale\LocaleFr;
+use Fisharebest\Localization\Locale\LocaleGu;
+use Fisharebest\Localization\Locale\LocaleHaw;
+use Fisharebest\Localization\Locale\LocaleHe;
+use Fisharebest\Localization\Locale\LocaleHi;
+use Fisharebest\Localization\Locale\LocaleHu;
+use Fisharebest\Localization\Locale\LocaleHy;
+use Fisharebest\Localization\Locale\LocaleId;
+use Fisharebest\Localization\Locale\LocaleIs;
+use Fisharebest\Localization\Locale\LocaleIt;
+use Fisharebest\Localization\Locale\LocaleJa;
+use Fisharebest\Localization\Locale\LocaleKa;
+use Fisharebest\Localization\Locale\LocaleKm;
+use Fisharebest\Localization\Locale\LocaleKn;
+use Fisharebest\Localization\Locale\LocaleKo;
+use Fisharebest\Localization\Locale\LocaleKok;
+use Fisharebest\Localization\Locale\LocaleLo;
+use Fisharebest\Localization\Locale\LocaleLt;
+use Fisharebest\Localization\Locale\LocaleLv;
+use Fisharebest\Localization\Locale\LocaleMk;
+use Fisharebest\Localization\Locale\LocaleMl;
+use Fisharebest\Localization\Locale\LocaleMr;
+use Fisharebest\Localization\Locale\LocaleMy;
+use Fisharebest\Localization\Locale\LocaleNe;
+use Fisharebest\Localization\Locale\LocaleNl;
+use Fisharebest\Localization\Locale\LocaleNn;
+use Fisharebest\Localization\Locale\LocaleOr;
+use Fisharebest\Localization\Locale\LocalePa;
+use Fisharebest\Localization\Locale\LocalePl;
+use Fisharebest\Localization\Locale\LocalePs;
+use Fisharebest\Localization\Locale\LocalePt;
+use Fisharebest\Localization\Locale\LocaleRo;
+use Fisharebest\Localization\Locale\LocaleRu;
+use Fisharebest\Localization\Locale\LocaleSk;
+use Fisharebest\Localization\Locale\LocaleSl;
+use Fisharebest\Localization\Locale\LocaleSq;
+use Fisharebest\Localization\Locale\LocaleSr;
+use Fisharebest\Localization\Locale\LocaleSv;
+use Fisharebest\Localization\Locale\LocaleTa;
+use Fisharebest\Localization\Locale\LocaleTe;
+use Fisharebest\Localization\Locale\LocaleTh;
+use Fisharebest\Localization\Locale\LocaleTl;
+use Fisharebest\Localization\Locale\LocaleTr;
+use Fisharebest\Localization\Locale\LocaleUk;
+use Fisharebest\Localization\Locale\LocaleUr;
+use Fisharebest\Localization\Locale\LocaleVi;
+use Fisharebest\Localization\Locale\LocaleYi;
+use Fisharebest\Localization\Locale\LocaleYue;
+
+use function preg_replace_callback;
+
+/**
+ * LANGUAGE_ID := {Size=1:15}
+ * The human language in which the data in the transmission is normally read or
+ * written. It is used primarily by programs to select language-specific
+ * sorting sequences and phonetic name matching algorithms.
+ */
+class LanguageId extends AbstractElement
+{
+ /**
+ * Convert a value to a canonical form.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public function canonical(string $value): string
+ {
+ return preg_replace_callback('/[A-Za-z]+/', static function (array $match): string {
+ return ucwords($match[0]);
+ }, strtolower(parent::canonical($value)));
+ }
+
+
+ /**
+ * A list of controlled values for this element
+ *
+ * @return array<int|string,string>
+ */
+ public function values(): array
+ {
+ return [
+ '' => '',
+ 'Afrikaans' => (new LocaleAf())->endonym(),
+ 'Albanian' => (new LocaleSq())->endonym(),
+ 'Amharic' => (new LocaleAm())->endonym(),
+ 'Anglo-Saxon' => (new LocaleAng())->endonym(),
+ 'Arabic' => (new LocaleAr())->endonym(),
+ 'Armenian' => (new LocaleHy())->endonym(),
+ 'Assamese' => (new LocaleAs())->endonym(),
+ 'Belorusian' => (new LocaleBe())->endonym(),
+ 'Bengali' => (new LocaleBn())->endonym(),
+ //'Braj' => (new LocaleBra())->endonym(),
+ 'Bulgarian' => (new LocaleBg())->endonym(),
+ 'Burmese' => (new LocaleMy())->endonym(),
+ 'Cantonese' => (new LocaleYue())->endonym(),
+ 'Catalan' => (new LocaleCaEsValencia())->endonym(),
+ 'Catalan_Spn' => (new LocaleCa())->endonym(),
+ 'Church-Slavic' => (new LocaleCu())->endonym(),
+ 'Czech' => (new LocaleCs())->endonym(),
+ 'Danish' => (new LocaleDa())->endonym(),
+ //'Dogri' => (new LocaleDoi())->endonym(),
+ 'Dutch' => (new LocaleNl())->endonym(),
+ 'English' => (new LocaleEn())->endonym(),
+ 'Esperanto' => (new LocaleEo())->endonym(),
+ 'Estonian' => (new LocaleEt())->endonym(),
+ 'Faroese' => (new LocaleFo())->endonym(),
+ 'Finnish' => (new LocaleFi())->endonym(),
+ 'French' => (new LocaleFr())->endonym(),
+ 'Georgian' => (new LocaleKa())->endonym(),
+ 'German' => (new LocaleDe())->endonym(),
+ 'Greek' => (new LocaleEl())->endonym(),
+ 'Gujarati' => (new LocaleGu())->endonym(),
+ 'Hawaiian' => (new LocaleHaw())->endonym(),
+ 'Hebrew' => (new LocaleHe())->endonym(),
+ 'Hindi' => (new LocaleHi())->endonym(),
+ 'Hungarian' => (new LocaleHu())->endonym(),
+ 'Icelandic' => (new LocaleIs())->endonym(),
+ 'Indonesian' => (new LocaleId())->endonym(),
+ 'Italian' => (new LocaleIt())->endonym(),
+ 'Japanese' => (new LocaleJa())->endonym(),
+ 'Kannada' => (new LocaleKn())->endonym(),
+ 'Khmer' => (new LocaleKm())->endonym(),
+ 'Konkani' => (new LocaleKok())->endonym(),
+ 'Korean' => (new LocaleKo())->endonym(),
+ //'Lahnda' => (new LocaleLah())->endonym(),
+ 'Lao' => (new LocaleLo())->endonym(),
+ 'Latvian' => (new LocaleLv())->endonym(),
+ 'Lithuanian' => (new LocaleLt())->endonym(),
+ 'Macedonian' => (new LocaleMk())->endonym(),
+ //'Maithili' => (new LocaleMai())->endonym(),
+ 'Malayalam' => (new LocaleMl())->endonym(),
+ //'Mandrin' => (new LocaleCmn())->endonym(),
+ //'Manipuri' => (new LocaleMni())->endonym(),
+ 'Marathi' => (new LocaleMr())->endonym(),
+ //'Mewari' => (new LocaleMtr())->endonym(),
+ //'Navaho' => (new LocaleNv())->endonym(),
+ 'Nepali' => (new LocaleNe())->endonym(),
+ 'Norwegian' => (new LocaleNn())->endonym(),
+ 'Oriya' => (new LocaleOr())->endonym(),
+ //'Pahari' => (new LocalePhr())->endonym(),
+ //'Pali' => (new LocalePi())->endonym(),
+ 'Panjabi' => (new LocalePa())->endonym(),
+ 'Persian' => (new LocaleFa())->endonym(),
+ 'Polish' => (new LocalePl())->endonym(),
+ 'Portuguese' => (new LocalePt())->endonym(),
+ //'Prakrit' => (new LocalePra())->endonym(),
+ 'Pusto' => (new LocalePs())->endonym(),
+ //'Rajasthani' => (new LocaleRaj())->endonym(),
+ 'Romanian' => (new LocaleRo())->endonym(),
+ 'Russian' => (new LocaleRu())->endonym(),
+ //'Sanskrit' => (new LocaleSa())->endonym(),
+ 'Serb' => (new LocaleSr())->endonym(),
+ //'Serbo_Croa' => (new LocaleHbs())->endonym(),
+ 'Slovak' => (new LocaleSk())->endonym(),
+ 'Slovene' => (new LocaleSl())->endonym(),
+ 'Spanish' => (new LocaleEs())->endonym(),
+ 'Swedish' => (new LocaleSv())->endonym(),
+ 'Tagalog' => (new LocaleTl())->endonym(),
+ 'Tamil' => (new LocaleTa())->endonym(),
+ 'Telugu' => (new LocaleTe())->endonym(),
+ 'Thai' => (new LocaleTh())->endonym(),
+ 'Tibetan' => (new LocaleBo())->endonym(),
+ 'Turkish' => (new LocaleTr())->endonym(),
+ 'Ukrainian' => (new LocaleUk())->endonym(),
+ 'Urdu' => (new LocaleUr())->endonym(),
+ 'Vietnamese' => (new LocaleVi())->endonym(),
+ //'Wendic' => (new LocaleWen())->endonym(),
+ 'Yiddish' => (new LocaleYi())->endonym(),
+ ];
+ }
+}
diff --git a/app/Elements/LdsBaptism.php b/app/Elements/LdsBaptism.php
new file mode 100644
index 0000000000..40a858d2ae
--- /dev/null
+++ b/app/Elements/LdsBaptism.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * INDI:BAPL is an empty element with children; DATE, TEMP, PLAC, STAT, NOTE and SOUR.
+ */
+class LdsBaptism extends AbstractEventElement
+{
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'TEMP' => '0:1',
+ 'PLAC' => '0:1',
+ 'STAT' => '0:1',
+ 'NOTE' => '0:M',
+ 'SOUR' => '0:M',
+ ];
+}
diff --git a/app/Elements/LdsBaptismDateStatus.php b/app/Elements/LdsBaptismDateStatus.php
new file mode 100644
index 0000000000..5d921ac195
--- /dev/null
+++ b/app/Elements/LdsBaptismDateStatus.php
@@ -0,0 +1,80 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\I18N;
+
+use function strtoupper;
+
+/**
+ * LDS_BAPTISM_DATE_STATUS := {Size=5:10}
+ * [ CHILD | COMPLETED | EXCLUDED | PRE-1970 | STILLBORN | SUBMITTED | UNCLEARED ]
+ * A code indicating the status of an LDS baptism and confirmation date where:
+ * CHILD = Died before becoming eight years old, baptism not required.
+ * COMPLETED = Completed but the date is not known.
+ * EXCLUDED = Patron excluded this ordinance from being cleared in this submission.
+ * PRE-1970 = Ordinance is likely completed, another ordinance for this person
+ * was converted from temple records of work completed before 1970,
+ * therefore this ordinance is assumed to be complete until all
+ * records are converted.
+ * STILLBORN = Stillborn, baptism not required.
+ * SUBMITTED = Ordinance was previously submitted.
+ * UNCLEARED = Data for clearing ordinance request was insufficient.
+ */
+class LdsBaptismDateStatus extends AbstractElement
+{
+ /**
+ * Convert a value to a canonical form.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public function canonical(string $value): string
+ {
+ return strtoupper(parent::canonical($value));
+ }
+
+ /**
+ * A list of controlled values for this element
+ *
+ * @return array<int|string,string>
+ */
+ public function values(): array
+ {
+ return [
+ '' => '',
+ 'CHILD' => /* I18N: LDS sealing status; see https://en.wikipedia.org/wiki/Sealing_(Mormonism) */
+ I18N::translate('Died as a child: exempt'),
+ 'COMPLETED' => /* I18N: LDS sealing status; see https://en.wikipedia.org/wiki/Sealing_(Mormonism) */
+ I18N::translate('Completed; date unknown'),
+ 'EXCLUDED' => /* I18N: LDS sealing status; see https://en.wikipedia.org/wiki/Sealing_(Mormonism) */
+ I18N::translate('Excluded from this submission'),
+ 'PRE-1970' => /* I18N: LDS sealing status; see https://en.wikipedia.org/wiki/Sealing_(Mormonism) */
+ I18N::translate('Completed before 1970; date not available'),
+ 'STILLBORN' => /* I18N: LDS sealing status; see https://en.wikipedia.org/wiki/Sealing_(Mormonism) */
+ I18N::translate('Stillborn: exempt'),
+ 'SUBMITTED' => /* I18N: LDS sealing status; see https://en.wikipedia.org/wiki/Sealing_(Mormonism) */
+ I18N::translate('Submitted but not yet cleared'),
+ 'UNCLEARED' => /* I18N: LDS sealing status; see https://en.wikipedia.org/wiki/Sealing_(Mormonism) */
+ I18N::translate('Uncleared: insufficient data'),
+ ];
+ }
+}
diff --git a/app/Elements/LdsChildSealing.php b/app/Elements/LdsChildSealing.php
new file mode 100644
index 0000000000..4f6dd811f4
--- /dev/null
+++ b/app/Elements/LdsChildSealing.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * INDI:SLGC is an empty element with children; DATE, TEMP, PLAC, FAMC, STAT, NOTE and SOUR.
+ */
+class LdsChildSealing extends AbstractEventElement
+{
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'TEMP' => '0:1',
+ 'PLAC' => '0:1',
+ 'STAT' => '0:1',
+ 'NOTE' => '0:M',
+ 'SOUR' => '0:M',
+ ];
+}
diff --git a/app/Elements/LdsChildSealingDateStatus.php b/app/Elements/LdsChildSealingDateStatus.php
new file mode 100644
index 0000000000..0f06706614
--- /dev/null
+++ b/app/Elements/LdsChildSealingDateStatus.php
@@ -0,0 +1,73 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\I18N;
+
+use function strtoupper;
+
+/**
+ * LDS_BAPTISM_DATE_STATUS := {Size=5:10}
+ * [ BIC | COMPLETED | EXCLUDED | DNS | PRE-1970 | STILLBORN | SUBMITTED | UNCLEARED ]
+ * BIC = Born in the covenant receiving blessing of child to parent sealing.
+ * EXCLUDED = Patron excluded this ordinance from being cleared in this submission.
+ * PRE-1970 = (See pre-1970 under LDS_BAPTISM_DATE_STATUS on page 51.)
+ * STILLBORN = Stillborn, baptism not required.
+ * SUBMITTED = Ordinance was previously submitted.
+ * UNCLEARED = Data for clearing ordinance request was insufficient.
+ */
+class LdsChildSealingDateStatus extends AbstractElement
+{
+ /**
+ * Convert a value to a canonical form.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public function canonical(string $value): string
+ {
+ return strtoupper(parent::canonical($value));
+ }
+
+ /**
+ * A list of controlled values for this element
+ *
+ * @return array<int|string,string>
+ */
+ public function values(): array
+ {
+ return [
+ '' => '',
+ 'BIC' => /* I18N: LDS sealing status; see https://en.wikipedia.org/wiki/Sealing_(Mormonism) */
+ I18N::translate('Born in the covenant'),
+ 'EXCLUDED' => /* I18N: LDS sealing status; see https://en.wikipedia.org/wiki/Sealing_(Mormonism) */
+ I18N::translate('Excluded from this submission'),
+ 'PRE-1970' => /* I18N: LDS sealing status; see https://en.wikipedia.org/wiki/Sealing_(Mormonism) */
+ I18N::translate('Completed before 1970; date not available'),
+ 'STILLBORN' => /* I18N: LDS sealing status; see https://en.wikipedia.org/wiki/Sealing_(Mormonism) */
+ I18N::translate('Stillborn: exempt'),
+ 'SUBMITTED' => /* I18N: LDS sealing status; see https://en.wikipedia.org/wiki/Sealing_(Mormonism) */
+ I18N::translate('Submitted but not yet cleared'),
+ 'UNCLEARED' => /* I18N: LDS sealing status; see https://en.wikipedia.org/wiki/Sealing_(Mormonism) */
+ I18N::translate('Uncleared: insufficient data'),
+ ];
+ }
+}
diff --git a/app/Elements/LdsConfirmation.php b/app/Elements/LdsConfirmation.php
new file mode 100644
index 0000000000..905948e223
--- /dev/null
+++ b/app/Elements/LdsConfirmation.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * INDI:CONL is an empty element with children; DATE, TEMP, PLAC, STAT, NOTE and SOUR.
+ */
+class LdsConfirmation extends AbstractEventElement
+{
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'TEMP' => '0:1',
+ 'PLAC' => '0:1',
+ 'STAT' => '0:1',
+ 'NOTE' => '0:M',
+ 'SOUR' => '0:M',
+ ];
+}
diff --git a/app/Elements/LdsEndowment.php b/app/Elements/LdsEndowment.php
new file mode 100644
index 0000000000..3cc0680980
--- /dev/null
+++ b/app/Elements/LdsEndowment.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * INDI:ENDL is an empty element with children; DATE, TEMP, PLAC, STAT, NOTE and SOUR.
+ */
+class LdsEndowment extends AbstractEventElement
+{
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'TEMP' => '0:1',
+ 'PLAC' => '0:1',
+ 'STAT' => '0:1',
+ 'NOTE' => '0:M',
+ 'SOUR' => '0:M',
+ ];
+}
diff --git a/app/Elements/LdsEndowmentDateStatus.php b/app/Elements/LdsEndowmentDateStatus.php
new file mode 100644
index 0000000000..e5bef6046f
--- /dev/null
+++ b/app/Elements/LdsEndowmentDateStatus.php
@@ -0,0 +1,81 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\I18N;
+
+use function strtoupper;
+
+/**
+ * LDS_ENDOWMENT_DATE_STATUS := {Size=5:10}
+ * [ CHILD | COMPLETED | EXCLUDED | PRE-1970 | STILLBORN | SUBMITTED | UNCLEARED ]
+ * A code indicating the status of an LDS endowment ordinance where:
+ * CHILD = Died before eight years old.
+ * COMPLETED = Completed but the date is not known.
+ * EXCLUDED = Patron excluded this ordinance from being cleared in this submission.
+ * PRE-1970 = (See pre-1970 under LDS_BAPTISM_DATE_STATUS on page 51.)
+ * STILLBORN = Stillborn, baptism not required.
+ * SUBMITTED = Ordinance was previously submitted.
+ * UNCLEARED = Data for clearing ordinance request was insufficient.
+ */
+class LdsEndowmentDateStatus extends AbstractElement
+{
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ ];
+
+ /**
+ * Convert a value to a canonical form.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public function canonical(string $value): string
+ {
+ return strtoupper(parent::canonical($value));
+ }
+
+ /**
+ * A list of controlled values for this element
+ *
+ * @return array<int|string,string>
+ */
+ public function values(): array
+ {
+ return [
+ '' => '',
+ 'CHILD' => /* I18N: LDS sealing status; see https://en.wikipedia.org/wiki/Sealing_(Mormonism) */
+ I18N::translate('Died as a child: exempt'),
+ 'COMPLETED' => /* I18N: LDS sealing status; see https://en.wikipedia.org/wiki/Sealing_(Mormonism) */
+ I18N::translate('Completed; date unknown'),
+ 'EXCLUDED' => /* I18N: LDS sealing status; see https://en.wikipedia.org/wiki/Sealing_(Mormonism) */
+ I18N::translate('Excluded from this submission'),
+ 'PRE-1970' => /* I18N: LDS sealing status; see https://en.wikipedia.org/wiki/Sealing_(Mormonism) */
+ I18N::translate('Completed before 1970; date not available'),
+ 'STILLBORN' => /* I18N: LDS sealing status; see https://en.wikipedia.org/wiki/Sealing_(Mormonism) */
+ I18N::translate('Stillborn: exempt'),
+ 'SUBMITTED' => /* I18N: LDS sealing status; see https://en.wikipedia.org/wiki/Sealing_(Mormonism) */
+ I18N::translate('Submitted but not yet cleared'),
+ 'UNCLEARED' => /* I18N: LDS sealing status; see https://en.wikipedia.org/wiki/Sealing_(Mormonism) */
+ I18N::translate('Uncleared: insufficient data'),
+ ];
+ }
+}
diff --git a/app/Elements/LdsSpouseSealing.php b/app/Elements/LdsSpouseSealing.php
new file mode 100644
index 0000000000..74358093b1
--- /dev/null
+++ b/app/Elements/LdsSpouseSealing.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * FAM:SLGS is an empty element with children; DATE, TEMP, PLAC, STAT, NOTE and SOUR.
+ */
+class LdsSpouseSealing extends AbstractEventElement
+{
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'TEMP' => '0:1',
+ 'PLAC' => '0:1',
+ 'STAT' => '0:1',
+ 'NOTE' => '0:M',
+ 'SOUR' => '0:M',
+ ];
+}
diff --git a/app/Elements/LdsSpouseSealingDateStatus.php b/app/Elements/LdsSpouseSealingDateStatus.php
new file mode 100644
index 0000000000..b9b4028e2e
--- /dev/null
+++ b/app/Elements/LdsSpouseSealingDateStatus.php
@@ -0,0 +1,80 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\I18N;
+
+use function strtr;
+
+/**
+ * LDS_BAPTISM_DATE_STATUS := {Size=5:10}
+ * [ CANCELED | COMPLETED | DNS | EXCLUDED | DNS/CAN | PRE-1970 | SUBMITTED | UNCLEARED ]
+ * CANCELED = Canceled and considered invalid.
+ * COMPLETED = Completed but the date is not known.
+ * DNS = This ordinance is not authorized.
+ * EXCLUDED = Patron excluded this ordinance from being cleared in this submission.
+ * DNS/CAN = This ordinance is not authorized, previous sealing cancelled.
+ * PRE-1970 = (See pre-1970 under LDS_BAPTISM_DATE_STATUS on page 51.)
+ * SUBMITTED = Ordinance was previously submitted.
+ * UNCLEARED = Data for clearing ordinance request was insufficient.
+ */
+class LdsSpouseSealingDateStatus extends AbstractElement
+{
+ /**
+ * Convert a value to a canonical form.
+ * Some applications use the British spelling instead of the US spelling.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public function canonical(string $value): string
+ {
+ return strtr(strtoupper(parent::canonical($value)), ['CANCELLED' => 'CANCELED']);
+ }
+
+ /**
+ * A list of controlled values for this element
+ *
+ * @return array<int|string,string>
+ */
+ public function values(): array
+ {
+ return [
+ '' => '',
+ 'CANCELED' => /* I18N: LDS sealing status; see https://en.wikipedia.org/wiki/Sealing_(Mormonism) */
+ I18N::translate('Sealing canceled (divorce)'),
+ 'COMPLETED' => /* I18N: LDS sealing status; see https://en.wikipedia.org/wiki/Sealing_(Mormonism) */
+ I18N::translate('Completed; date unknown'),
+ 'DNS' => /* I18N: LDS sealing status; see https://en.wikipedia.org/wiki/Sealing_(Mormonism) */
+ I18N::translate('Do not seal: unauthorized'),
+ 'DNS/CAN' => /* I18N: LDS sealing status; see https://en.wikipedia.org/wiki/Sealing_(Mormonism) */
+ I18N::translate('Do not seal, previous sealing canceled'),
+ 'EXCLUDED' => /* I18N: LDS sealing status; see https://en.wikipedia.org/wiki/Sealing_(Mormonism) */
+ I18N::translate('Excluded from this submission'),
+ 'PRE-1970' => /* I18N: LDS sealing status; see https://en.wikipedia.org/wiki/Sealing_(Mormonism) */
+ I18N::translate('Completed before 1970; date not available'),
+ 'SUBMITTED' => /* I18N: LDS sealing status; see https://en.wikipedia.org/wiki/Sealing_(Mormonism) */
+ I18N::translate('Submitted but not yet cleared'),
+ 'UNCLEARED' => /* I18N: LDS sealing status; see https://en.wikipedia.org/wiki/Sealing_(Mormonism) */
+ I18N::translate('Uncleared: insufficient data'),
+ ];
+ }
+}
diff --git a/app/Elements/Marriage.php b/app/Elements/Marriage.php
new file mode 100644
index 0000000000..f750fe44b4
--- /dev/null
+++ b/app/Elements/Marriage.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * Marriage
+ */
+class Marriage extends AbstractEventElement
+{
+ protected const SUBTAGS = [
+ 'TYPE' => '0:1',
+ 'DATE' => '0:1',
+ 'HUSB' => '0:1',
+ 'WIFE' => '0:1',
+ 'PLAC' => '0:1',
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+}
diff --git a/app/Elements/MarriageBanns.php b/app/Elements/MarriageBanns.php
new file mode 100644
index 0000000000..1e6bf61145
--- /dev/null
+++ b/app/Elements/MarriageBanns.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * Marriage Banns
+ */
+class MarriageBanns extends AbstractElement
+{
+}
diff --git a/app/Elements/MarriageContract.php b/app/Elements/MarriageContract.php
new file mode 100644
index 0000000000..f4ba44519d
--- /dev/null
+++ b/app/Elements/MarriageContract.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * Marriage Contract
+ */
+class MarriageContract extends AbstractElement
+{
+}
diff --git a/app/Elements/MarriageLicence.php b/app/Elements/MarriageLicence.php
new file mode 100644
index 0000000000..624cecbdc6
--- /dev/null
+++ b/app/Elements/MarriageLicence.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * Marriage Licence
+ */
+class MarriageLicence extends AbstractElement
+{
+}
diff --git a/app/Elements/MarriageSettlement.php b/app/Elements/MarriageSettlement.php
new file mode 100644
index 0000000000..9c23378bdf
--- /dev/null
+++ b/app/Elements/MarriageSettlement.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * Marriage Settlement
+ */
+class MarriageSettlement extends AbstractElement
+{
+}
diff --git a/app/Elements/MarriageType.php b/app/Elements/MarriageType.php
new file mode 100644
index 0000000000..c3a9d44c40
--- /dev/null
+++ b/app/Elements/MarriageType.php
@@ -0,0 +1,66 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\I18N;
+
+use function strtolower;
+use function ucfirst;
+
+/**
+ * MARR:TYPE
+ */
+class MarriageType extends AbstractElement
+{
+ /**
+ * Convert a value to a canonical form.
+ * GEDCOM 5.5EL uses 'RELI' and 'CIVIL'
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public function canonical(string $value): string
+ {
+ $value = ucfirst(strtolower(parent::canonical($value)));
+
+ $canonical = [
+ 'Reli' => 'Religious'
+ ];
+
+ return $canonical[$value] ?? $value;
+ }
+
+ /**
+ * A list of controlled values for this element.
+ *
+ * @return array<int|string,string>
+ */
+ public function values(): array
+ {
+ return [
+ '' => '',
+ 'Civil' => I18N::translate('Civil marriage'),
+ 'Partners' => I18N::translate('Registered partnership'),
+ 'Religious' => I18N::translate('Religious marriage'),
+
+ ];
+ }
+}
diff --git a/app/Elements/MediaRecord.php b/app/Elements/MediaRecord.php
new file mode 100644
index 0000000000..ec597bcf04
--- /dev/null
+++ b/app/Elements/MediaRecord.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * A level 0 media record
+ */
+class MediaRecord extends AbstractElement
+{
+ protected const SUBTAGS = [
+ 'FILE' => '1:M',
+ 'REFN' => '0:M',
+ 'RIN' => '0:1',
+ 'NOTE' => '0:M',
+ 'SOUR' => '0:M',
+ 'CHAN' => '0:1',
+ 'RESN' => '0:1', /* *** webtrees extension */
+ ];
+}
diff --git a/app/Elements/MultimediaFileReference.php b/app/Elements/MultimediaFileReference.php
new file mode 100644
index 0000000000..7ae477f5db
--- /dev/null
+++ b/app/Elements/MultimediaFileReference.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * MULTIMEDIA_FILE_REFERENCE := {Size=1:30}
+ * A complete local or remote file reference to the auxiliary data to be linked
+ * to the GEDCOM context. Remote reference would include a network address
+ * where the multimedia data may be obtained.
+ */
+class MultimediaFileReference extends AbstractElement
+{
+ protected const SUBTAGS = [
+ 'FORM' => '0:1',
+ 'TITL' => '0:1',
+ ];
+}
diff --git a/app/Elements/MultimediaFormat.php b/app/Elements/MultimediaFormat.php
new file mode 100644
index 0000000000..1fa76cd480
--- /dev/null
+++ b/app/Elements/MultimediaFormat.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * MULTIMEDIA_FORMAT := {Size=3:4}
+ * [ bmp | gif | jpg | ole | pcx | tif | wav ]
+ * Indicates the format of the multimedia data associated with the specific
+ * GEDCOM context. This allows processors to determine whether they can process
+ * the data object. Any linked files should contain the data required, in the
+ * indicated format, to process the file data.
+ */
+class MultimediaFormat extends AbstractElement
+{
+ protected const SUBTAGS = [
+ 'TYPE' => '0:1',
+ ];
+}
diff --git a/app/Elements/NameOfBusiness.php b/app/Elements/NameOfBusiness.php
new file mode 100644
index 0000000000..1ea4a784da
--- /dev/null
+++ b/app/Elements/NameOfBusiness.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * NAME_OF_BUSINESS := {Size=1:90}
+ * Name of the business, corporation, or person that produced or commissioned
+ * the product.
+ */
+class NameOfBusiness extends AbstractElement
+{
+ protected const MAX_LENGTH = 90;
+}
diff --git a/app/Elements/NameOfFamilyFile.php b/app/Elements/NameOfFamilyFile.php
new file mode 100644
index 0000000000..ec2c6d0e06
--- /dev/null
+++ b/app/Elements/NameOfFamilyFile.php
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Tree;
+
+use function mb_substr;
+use function pathinfo;
+use function str_ends_with;
+use function strtolower;
+
+use const PATHINFO_EXTENSION;
+
+/**
+ * NAME_OF_FAMILY_FILE := {Size=1:120}
+ * Name under which family names for ordinances are stored in the temple's
+ * family file.
+ */
+class NameOfFamilyFile extends AbstractElement
+{
+ protected const MAX_LENGTH = 120;
+
+ /**
+ * Create a default value for this element.
+ *
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function default(Tree $tree): string
+ {
+ $value = mb_substr($tree->name(), 0, self::MAX_LENGTH);
+
+ $extension = strtolower(pathinfo($value, PATHINFO_EXTENSION));
+
+ if ($extension !== 'ged') {
+ $value = mb_substr($tree->name(), 0, self::MAX_LENGTH - 4) . '.ged';
+ }
+
+ return $value;
+ }
+}
diff --git a/app/Elements/NameOfProduct.php b/app/Elements/NameOfProduct.php
new file mode 100644
index 0000000000..7ada45ba11
--- /dev/null
+++ b/app/Elements/NameOfProduct.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * NAME_OF_PRODUCT := {Size=1:90}
+ * The name of the software product that produced this transmission.
+ */
+class NameOfProduct extends AbstractElement
+{
+ protected const MAX_LENGTH = 90;
+}
diff --git a/app/Elements/NameOfRepository.php b/app/Elements/NameOfRepository.php
new file mode 100644
index 0000000000..b702ce4d83
--- /dev/null
+++ b/app/Elements/NameOfRepository.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * NAME_OF_REPOSITORY := {Size=1:90}
+ * The official name of the archive in which the stated source material is stored.
+ */
+class NameOfRepository extends AbstractElement
+{
+ protected const MAX_LENGTH = 90;
+}
diff --git a/app/Elements/NameOfSourceData.php b/app/Elements/NameOfSourceData.php
new file mode 100644
index 0000000000..5d6d9165bb
--- /dev/null
+++ b/app/Elements/NameOfSourceData.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * NAME_OF_SOURCE_DATA := {Size=1:90}
+ * The name of the electronic data source that was used to obtain the data in
+ * this transmission. For example, the data may have been obtained from a
+ * CD-ROM disc that was named "U.S. 1880 CENSUS CD-ROM vol. 13."
+ */
+class NameOfSourceData extends AbstractElement
+{
+ protected const MAX_LENGTH = 90;
+}
diff --git a/app/Elements/NamePersonal.php b/app/Elements/NamePersonal.php
new file mode 100644
index 0000000000..0e79f72b45
--- /dev/null
+++ b/app/Elements/NamePersonal.php
@@ -0,0 +1,145 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\I18N;
+use Fisharebest\Webtrees\SurnameTradition;
+use Fisharebest\Webtrees\Tree;
+
+use function e;
+use function view;
+
+/**
+ * NAME_PERSONAL := {Size=1:120}
+ * [
+ * <NAME_TEXT> | /<NAME_TEXT>/ |
+ * <NAME_TEXT> /<NAME_TEXT>/ | /<NAME_TEXT>/ <NAME_TEXT> |
+ * <NAME_TEXT> /<NAME_TEXT>/ <NAME_TEXT> ]
+ * The surname of an individual, if known, is enclosed between two slash (/)
+ * characters. The order of the name parts should be the order that the person
+ * would, by custom of their culture, have used when giving it to a recorder.
+ * Early versions of Personal Ancestral File ® and other products did not use
+ * the trailing slash when the surname was the last element of the name. If
+ * part of name is illegible, that part is indicated by an ellipsis (...).
+ * Capitalize the name of a person or place in the conventional manner—
+ * capitalize the first letter of each part and lowercase the other letters,
+ * unless conventional usage is otherwise. For example: McMurray.
+ * Examples:
+ * William Lee (given name only or surname not known)
+ * /Parry/ (surname only)
+ * William Lee /Parry/
+ * William Lee /Mac Parry/ (both parts (Mac and Parry) are surname parts
+ * William /Lee/ Parry (surname imbedded in the name string)
+ * William Lee /Pa.../
+ */
+class NamePersonal extends AbstractElement
+{
+ protected const MAX_LENGTH = 120;
+
+ protected const SUBTAGS = [
+ 'TYPE' => '0:1',
+ 'NPFX' => '0:1',
+ 'GIVN' => '0:1',
+ 'SPFX' => '0:1',
+ 'SURN' => '0:1',
+ 'NSFX' => '0:1',
+ 'NICK' => '0:1',
+ ];
+
+ /**
+ * Create a default value for this element.
+ *
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function default(Tree $tree): string
+ {
+ $surname_tradition = SurnameTradition::create($tree->getPreference('SURNAME_TRADITION'));
+
+ if ($surname_tradition->hasSurnames()) {
+ return '//';
+ }
+
+ return '';
+ }
+
+ /**
+ * An edit control for this data.
+ *
+ * @param string $id
+ * @param string $name
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function edit(string $id, string $name, string $value, Tree $tree): string
+ {
+ return
+ '<div class="input-group">' .
+ view('edit/input-addon-edit-name', ['id' => $id]) .
+ '<input class="form-control" type="text" id="' . e($id) . '" name="' . e($name) . '" value="' . e($value) . '" readonly>' .
+ view('edit/input-addon-keyboard', ['id' => $id]) .
+ view('edit/input-addon-help', ['fact' => 'NAME']) .
+ '</div>';
+ }
+
+ /**
+ * @param Tree $tree
+ *
+ * @return array<string,string>
+ */
+ public function subtags(Tree $tree): array
+ {
+ $language = I18N::languageTag();
+
+ switch ($language) {
+ case 'hu':
+ case 'jp':
+ case 'ko':
+ case 'zh-Hans':
+ case 'zh-Hant':
+ $subtags = [
+ 'TYPE' => '0:1',
+ 'NPFX' => '0:1',
+ 'SPFX' => '0:1',
+ 'SURN' => '0:1',
+ 'GIVN' => '0:1',
+ 'NSFX' => '0:1',
+ 'NICK' => '0:1',
+ ];
+ break;
+ default:
+ $subtags = [
+ 'TYPE' => '0:1',
+ 'NPFX' => '0:1',
+ 'GIVN' => '0:1',
+ 'SPFX' => '0:1',
+ 'SURN' => '0:1',
+ 'NSFX' => '0:1',
+ 'NICK' => '0:1',
+ ];
+ break;
+ }
+
+ return $subtags;
+ }
+}
diff --git a/app/Elements/NamePhoneticVariation.php b/app/Elements/NamePhoneticVariation.php
new file mode 100644
index 0000000000..42fe464dc6
--- /dev/null
+++ b/app/Elements/NamePhoneticVariation.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * NAME_PHONETIC_VARIATION := {Size=1:120}
+ * The phonetic variation of the name is written in the same form as the was
+ * the name used in the superior <NAME_PERSONAL> primitive, but phonetically
+ * written using the method indicated by the subordinate <PHONETIC_TYPE> value,
+ * for example if hiragana was used to provide a reading of a name written in
+ * kanji, then the <PHONETIC_TYPE> value would indicate ‘kana’. See page 57.
+ */
+class NamePhoneticVariation extends AbstractElement
+{
+ protected const MAX_LENGTH = 120;
+}
diff --git a/app/Elements/NamePieceGiven.php b/app/Elements/NamePieceGiven.php
new file mode 100644
index 0000000000..3c6f4809aa
--- /dev/null
+++ b/app/Elements/NamePieceGiven.php
@@ -0,0 +1,67 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Tree;
+
+use function e;
+use function view;
+
+/**
+ * NAME_PERSONAL := {Size=1:120}
+ * [ <NAME_PIECE> | <NAME_PIECE_GIVEN>, <NAME_PIECE> ]
+ * Given name or earned name. Different given names are separated by a comma.
+ */
+class NamePieceGiven extends AbstractElement
+{
+ protected const MAX_LENGTH = 120;
+
+ /**
+ * An edit control for this data.
+ *
+ * @param string $id
+ * @param string $name
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function edit(string $id, string $name, string $value, Tree $tree): string
+ {
+ return
+ '<div class="input-group">' .
+ '<input class="form-control" type="text" id="' . e($id) . '" name="' . e($name) . '" value="' . e($value) . '">' .
+ view('edit/input-addon-keyboard', ['id' => $id]) .
+ '</div>';
+ }
+
+ /**
+ * Display the value of this type of element.
+ *
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function value(string $value, Tree $tree): string
+ {
+ return '<span dir="auto">' . preg_replace('/(\S*)\*/', '<span class="starredname">\\1</span>', e($value)) . '</span>';
+ }
+}
diff --git a/app/Elements/NamePieceNickname.php b/app/Elements/NamePieceNickname.php
new file mode 100644
index 0000000000..6f51448cb6
--- /dev/null
+++ b/app/Elements/NamePieceNickname.php
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Tree;
+
+use function e;
+use function view;
+
+/**
+ * NAME_PIECE_NICKNAME := {Size=1:30}
+ * [ <NAME_PIECE> | <NAME_PIECE_NICKNAME>, <NAME_PIECE> ]
+ * A descriptive or familiar name used in connection with one's proper name.
+ */
+class NamePieceNickname extends AbstractElement
+{
+ protected const MAX_LENGTH = 30;
+
+ /**
+ * An edit control for this data.
+ *
+ * @param string $id
+ * @param string $name
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function edit(string $id, string $name, string $value, Tree $tree): string
+ {
+ return
+ '<div class="input-group">' .
+ '<input class="form-control" type="text" id="' . e($id) . '" name="' . e($name) . '" value="' . e($value) . '">' .
+ view('edit/input-addon-keyboard', ['id' => $id]) .
+ '</div>';
+ }
+}
diff --git a/app/Elements/NamePiecePrefix.php b/app/Elements/NamePiecePrefix.php
new file mode 100644
index 0000000000..321291752e
--- /dev/null
+++ b/app/Elements/NamePiecePrefix.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Tree;
+
+use function e;
+use function view;
+
+/**
+ * NAME_PIECE_PREFIX := {Size=1:30}
+ * [ <NAME_PIECE> | <NAME_PIECE_PREFIX>, <NAME_PIECE> ]
+ * Non indexing name piece that appears preceding the given name and surname parts. Different name
+ * prefix parts are separated by a comma.
+ * For example:
+ * Lt. Cmndr. Joseph /Allen/ jr.
+ * In this example Lt. Cmndr. is considered as the name prefix portion.
+ */
+class NamePiecePrefix extends AbstractElement
+{
+ protected const MAX_LENGTH = 30;
+
+ /**
+ * An edit control for this data.
+ *
+ * @param string $id
+ * @param string $name
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function edit(string $id, string $name, string $value, Tree $tree): string
+ {
+ return
+ '<div class="input-group">' .
+ '<input class="form-control" type="text" id="' . e($id) . '" name="' . e($name) . '" value="' . e($value) . '">' .
+ view('edit/input-addon-keyboard', ['id' => $id]) .
+ '</div>';
+ }
+}
diff --git a/app/Elements/NamePieceSuffix.php b/app/Elements/NamePieceSuffix.php
new file mode 100644
index 0000000000..a1f1192828
--- /dev/null
+++ b/app/Elements/NamePieceSuffix.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Tree;
+
+use function e;
+use function view;
+
+/**
+ * NAME_PIECE_SUFFIX := {Size=1:30}
+ * [ <NAME_PIECE> | <NAME_PIECE_SUFFIX>, <NAME_PIECE> ]
+ * Non-indexing name piece that appears after the given name and surname parts. Different name
+ * suffix parts are separated by a comma.
+ * For example:
+ * Lt. Cmndr. Joseph /Allen/ jr.
+ * In this example jr. is considered as the name suffix portion.
+ */
+class NamePieceSuffix extends AbstractElement
+{
+ protected const MAX_LENGTH = 30;
+
+ /**
+ * An edit control for this data.
+ *
+ * @param string $id
+ * @param string $name
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function edit(string $id, string $name, string $value, Tree $tree): string
+ {
+ return
+ '<div class="input-group">' .
+ '<input class="form-control" type="text" id="' . e($id) . '" name="' . e($name) . '" value="' . e($value) . '">' .
+ view('edit/input-addon-keyboard', ['id' => $id]) .
+ '</div>';
+ }
+}
diff --git a/app/Elements/NamePieceSurname.php b/app/Elements/NamePieceSurname.php
new file mode 100644
index 0000000000..d54227e427
--- /dev/null
+++ b/app/Elements/NamePieceSurname.php
@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Http\RequestHandlers\AutoCompleteSurname;
+use Fisharebest\Webtrees\Tree;
+
+use function e;
+use function view;
+
+/**
+ * NAME_PIECE_SUFFIX := {Size=1:30}
+ * [ <NAME_PIECE> | <NAME_PIECE_SURNAME>, <NAME_PIECE> ]
+ * Surname or family name. Different surnames are separated by a comma.
+ */
+class NamePieceSurname extends AbstractElement
+{
+ protected const MAX_LENGTH = 30;
+
+ /**
+ * An edit control for this data.
+ *
+ * @param string $id
+ * @param string $name
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function edit(string $id, string $name, string $value, Tree $tree): string
+ {
+ return
+ '<div class="input-group">' .
+ '<input data-autocomplete-url="' . e(route(AutoCompleteSurname::class, ['tree' => $tree->name()])) . '" autocomplete="off" class="form-control" type="text" id="' . e($id) . '" name="' . e($name) . '" value="' . e($value) . '">' .
+ view('edit/input-addon-keyboard', ['id' => $id]) .
+ view('edit/input-addon-help', ['fact' => 'SURN']) .
+ '</div>';
+ }
+}
diff --git a/app/Elements/NamePieceSurnamePrefix.php b/app/Elements/NamePieceSurnamePrefix.php
new file mode 100644
index 0000000000..3c7bed7186
--- /dev/null
+++ b/app/Elements/NamePieceSurnamePrefix.php
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Tree;
+
+use function e;
+use function view;
+
+/**
+ * NAME_PIECE_SUFFIX := {Size=1:30}
+ * [ <NAME_PIECE> | <NAME_PIECE_SURNAME_PREFIX>, <NAME_PIECE> ]
+ * Surname prefix or article used in a family name. Different surname articles are separated by a
+ * comma, for example in the name "de la Cruz", this value would be "de, la".
+ */
+class NamePieceSurnamePrefix extends AbstractElement
+{
+ protected const MAX_LENGTH = 30;
+
+ /**
+ * An edit control for this data.
+ *
+ * @param string $id
+ * @param string $name
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function edit(string $id, string $name, string $value, Tree $tree): string
+ {
+ return
+ '<div class="input-group">' .
+ '<input class="form-control" type="text" id="' . e($id) . '" name="' . e($name) . '" value="' . e($value) . '">' .
+ view('edit/input-addon-keyboard', ['id' => $id]) .
+ '</div>';
+ }
+}
diff --git a/app/Elements/NameRomanizedVariation.php b/app/Elements/NameRomanizedVariation.php
new file mode 100644
index 0000000000..00d9a316fe
--- /dev/null
+++ b/app/Elements/NameRomanizedVariation.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Tree;
+
+use function e;
+use function view;
+
+/**
+ * NAME_ROMANIZED_VARIATION := {Size=1:120}
+ * The romanized variation of the name is written in the same form prescribed
+ * for the name used in the superior <NAME_PERSONAL> context. The method used
+ * to romanize the name is indicated by the line_value of the subordinate
+ * <ROMANIZED_TYPE>, for example if romaji was used to provide a reading of a
+ * name written in kanji, then the ROMANIZED_TYPE subordinate to the ROMN tag
+ * would indicate romaji. See page 61.
+ */
+class NameRomanizedVariation extends AbstractElement
+{
+ protected const MAX_LENGTH = 120;
+
+ /**
+ * An edit control for this data.
+ *
+ * @param string $id
+ * @param string $name
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function edit(string $id, string $name, string $value, Tree $tree): string
+ {
+ return
+ '<div class="input-group">' .
+ '<input class="form-control" type="text" id="' . e($id) . '" name="' . e($name) . '" value="' . e($value) . '">' .
+ view('help/link', ['topic' => 'ROMN']) .
+ '</div>';
+ }
+}
diff --git a/app/Elements/NameType.php b/app/Elements/NameType.php
new file mode 100644
index 0000000000..6410dca6bd
--- /dev/null
+++ b/app/Elements/NameType.php
@@ -0,0 +1,66 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\I18N;
+
+/**
+ * NAME_TYPE := {Size=5:30}
+ * [ aka | birth | immigrant | maiden | married | <user defined>]
+ * Indicates the name type, for example the name issued or assumed as an immigrant.
+ * aka = also known as, alias, etc.
+ * birth = name given on birth certificate.
+ * immigrant = name assumed at the time of immigration.
+ * maiden = maiden name, name before first marriage.
+ * married =name was persons previous married name.
+ * user_defined = other text name that defines the name type.
+ */
+class NameType extends AbstractElement
+{
+ /**
+ * A list of controlled values for this element
+ *
+ * @return array<int|string,string>
+ */
+ public function values(): array
+ {
+ return [
+ '' => '',
+ 'adopted' => /* I18N: The name given to a child by its adoptive parents */
+ I18N::translate('adopted name'),
+ 'aka' => /* I18N: The name by which an individual is also known. e.g. a professional name or a stage name */
+ I18N::translate('also known as'),
+ 'birth' => /* I18N: The name given to an individual at their birth */
+ I18N::translate('birth name'),
+ 'change' => /* I18N: A name chosen by an individual, to replace their existing name (whether legal or otherwise) */
+ I18N::translate('change of name'),
+ 'estate' => /* I18N: A name given to an individual, from the farm or estate on which they lived or worked */
+ I18N::translate('estate name'),
+ 'immigrant' => /* I18N: A name taken on immigration - e.g. migrants to the USA frequently anglicized their names */
+ I18N::translate('immigration name'),
+ 'maiden' => /* I18N: A woman’s name, before she marries (in cultures where women take their new husband’s name on marriage) */
+ I18N::translate('maiden name'),
+ 'married' => /* I18N: A name taken on marriage - usually the wife takes the husband’s surname */
+ I18N::translate('married name'),
+ 'religious' => /* I18N: A name taken when entering a religion or a religious order */
+ I18N::translate('religious name'),
+ ];
+ }
+}
diff --git a/app/Elements/NationOrTribalOrigin.php b/app/Elements/NationOrTribalOrigin.php
new file mode 100644
index 0000000000..e1fa3b5e70
--- /dev/null
+++ b/app/Elements/NationOrTribalOrigin.php
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * NATIONAL_OR_TRIBAL_ORIGIN := {Size=1:120}
+ * The person's division of national origin or other folk, house, kindred,
+ * lineage, or tribal interest. Examples: Irish, Swede, Egyptian Coptic, Sioux
+ * Dakota Rosebud, Apache Chiricawa, Navajo Bitter Water, Eastern Cherokee
+ * Taliwa Wolf, and so forth.
+ */
+class NationOrTribalOrigin extends AbstractElement
+{
+ protected const MAX_LENGTH = 120;
+
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'PLAC' => '0:1',
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+}
diff --git a/app/Elements/NationalIdNumber.php b/app/Elements/NationalIdNumber.php
new file mode 100644
index 0000000000..23483df026
--- /dev/null
+++ b/app/Elements/NationalIdNumber.php
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * NATIONAL_ID_NUMBER := {Size=1:30}
+ * A nationally-controlled number assigned to an individual. Commonly known
+ * national numbers should be assigned their own tag, such as SSN for U.S.
+ * Social Security Number. The use of the IDNO tag requires a subordinate TYPE
+ * tag to identify what kind of number is being stored.
+ * For example:
+ * n IDNO 43-456-1899
+ * +1 TYPE Canadian Health Registration
+ */
+class NationalIdNumber extends AbstractElement
+{
+ protected const MAX_LENGTH = 30;
+
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'PLAC' => '0:1',
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+}
diff --git a/app/Elements/Naturalization.php b/app/Elements/Naturalization.php
new file mode 100644
index 0000000000..e96c9c88ea
--- /dev/null
+++ b/app/Elements/Naturalization.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * Naturalization
+ */
+class Naturalization extends AbstractElement
+{
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'AGE' => '0:1',
+ 'PLAC' => '0:1',
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+}
diff --git a/app/Elements/NobilityTypeTitle.php b/app/Elements/NobilityTypeTitle.php
new file mode 100644
index 0000000000..7a396cad01
--- /dev/null
+++ b/app/Elements/NobilityTypeTitle.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * NOBILITY_TYPE_TITLE := {Size=1:120}
+ * The title given to or used by a person, especially of royalty or other noble
+ * class within a locality.
+ */
+class NobilityTypeTitle extends AbstractElement
+{
+ protected const MAX_LENGTH = 120;
+
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'PLAC' => '0:1',
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+}
diff --git a/app/Elements/NoteRecord.php b/app/Elements/NoteRecord.php
new file mode 100644
index 0000000000..e9aec25d27
--- /dev/null
+++ b/app/Elements/NoteRecord.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * A level 0 note record
+ */
+class NoteRecord extends AbstractElement
+{
+ protected const SUBTAGS = [
+ 'CONC' => '0:1',
+ 'REFN' => '0:1',
+ 'RIN' => '0:1',
+ 'SOUR' => '0:M',
+ ];
+}
diff --git a/app/Elements/NoteStructure.php b/app/Elements/NoteStructure.php
new file mode 100644
index 0000000000..27fbf7276d
--- /dev/null
+++ b/app/Elements/NoteStructure.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * NOTE can be text or an XREF.
+ */
+class NoteStructure extends AbstractElement
+{
+ /**
+ * Convert a value to a canonical form.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public function canonical(string $value): string
+ {
+ // Browsers use MS-DOS line endings in multi-line data.
+ return strtr($value, ["\r\n" => "\n", "\r" => "\n"]);
+ }
+}
diff --git a/app/Elements/Occupation.php b/app/Elements/Occupation.php
new file mode 100644
index 0000000000..31b98fd9cb
--- /dev/null
+++ b/app/Elements/Occupation.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * OCCUPATION := {Size=1:90}
+ * The kind of activity that an individual does for a job, profession, or
+ * principal activity.
+ */
+class Occupation extends AbstractElement
+{
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'PLAC' => '0:1',
+ 'AGNC' => '0:1',
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+}
diff --git a/app/Elements/OrdinanceProcessFlag.php b/app/Elements/OrdinanceProcessFlag.php
new file mode 100644
index 0000000000..b850caf9e9
--- /dev/null
+++ b/app/Elements/OrdinanceProcessFlag.php
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\I18N;
+
+use function strtolower;
+
+/**
+ * ORDINANCE_PROCESS_FLAG := {Size=2:3}
+ * [ yes | no ]
+ * A flag that indicates whether submission should be processed for clearing
+ * temple ordinances.
+ */
+class OrdinanceProcessFlag extends AbstractElement
+{
+ /**
+ * Convert a value to a canonical form.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public function canonical(string $value): string
+ {
+ return strtolower(parent::canonical($value));
+ }
+
+ /**
+ * A list of controlled values for this element
+ *
+ * @return array<int|string,string>
+ */
+ public function values(): array
+ {
+ return [
+ '' => '',
+ 'no' => I18N::translate('no'),
+ 'yes' => I18N::translate('yes'),
+ ];
+ }
+}
diff --git a/app/Elements/Ordination.php b/app/Elements/Ordination.php
new file mode 100644
index 0000000000..7bfe4dac16
--- /dev/null
+++ b/app/Elements/Ordination.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * Ordination
+ */
+class Ordination extends AbstractElement
+{
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'AGE' => '0:1',
+ 'PLAC' => '0:1',
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+}
diff --git a/app/Elements/PafUid.php b/app/Elements/PafUid.php
new file mode 100644
index 0000000000..1c1bd15870
--- /dev/null
+++ b/app/Elements/PafUid.php
@@ -0,0 +1,66 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Exception;
+use Fisharebest\Webtrees\Tree;
+use Ramsey\Uuid\Uuid;
+
+use function dechex;
+use function hexdec;
+use function strtoupper;
+use function strtr;
+use function substr;
+
+/**
+ * _UID fields, as created by PAF and other applications
+ */
+class PafUid extends AbstractElement
+{
+ protected const MAX_LENGTH = 34;
+
+ /**
+ * Create a default value for this element.
+ *
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function default(Tree $tree): string
+ {
+ try {
+ $uid = strtr(Uuid::uuid4()->toString(), ['-' => '']);
+ } catch (Exception $ex) {
+ // uuid4() can fail if there is insufficient entropy in the system.
+ return '';
+ }
+
+ $checksum_a = 0; // a sum of the bytes
+ $checksum_b = 0; // a sum of the incremental values of $checksum_a
+
+ // Compute checksums
+ for ($i = 0; $i < 32; $i += 2) {
+ $checksum_a += hexdec(substr($uid, $i, 2));
+ $checksum_b += $checksum_a & 0xff;
+ }
+
+ return strtoupper($uid . substr(dechex($checksum_a), -2) . substr(dechex($checksum_b), -2));
+ }
+}
diff --git a/app/Elements/PedigreeLinkageType.php b/app/Elements/PedigreeLinkageType.php
new file mode 100644
index 0000000000..a024b74d0a
--- /dev/null
+++ b/app/Elements/PedigreeLinkageType.php
@@ -0,0 +1,82 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\I18N;
+
+/**
+ * PEDIGREE_LINKAGE_TYPE := {Size=5:7}
+ * [ adopted | birth | foster | sealing ]
+ * A code used to indicate the child to family relationship for pedigree navigation purposes.
+ * Where:
+ * adopted = indicates adoptive parents.
+ * birth = indicates birth parents.
+ * foster = indicates child was included in a foster or guardian family.
+ * sealing = indicates child was sealed to parents other than birth parents.
+ */
+class PedigreeLinkageType extends AbstractElement
+{
+ protected const MAX_LENGTH = 7;
+
+ /**
+ * A list of controlled values for this element
+ *
+ * @param string $sex - the text depends on the sex of the individual
+ *
+ * @return array<int|string,string>
+ */
+ public function values(string $sex = 'U'): array
+ {
+ $values = [
+ 'M' => [
+ '' => '',
+ 'birth' => I18N::translateContext('Male pedigree', 'Birth'),
+ 'adopted' => I18N::translateContext('Male pedigree', 'Adopted'),
+ 'foster' => I18N::translateContext('Male pedigree', 'Foster'),
+ 'sealing' => /* I18N: “sealing” is a Mormon ceremony. */
+ I18N::translateContext('Male pedigree', 'Sealing'),
+ 'rada' => /* I18N: “rada” is an Arabic word, pronounced “ra DAH”. It is child-to-parent pedigree, established by wet-nursing. */
+ I18N::translateContext('Male pedigree', 'Rada'),
+ ],
+ 'F' => [
+ '' => '',
+ 'birth' => I18N::translateContext('Female pedigree', 'Birth'),
+ 'adopted' => I18N::translateContext('Female pedigree', 'Adopted'),
+ 'foster' => I18N::translateContext('Female pedigree', 'Foster'),
+ 'sealing' => /* I18N: “sealing” is a Mormon ceremony. */
+ I18N::translateContext('Female pedigree', 'Sealing'),
+ 'rada' => /* I18N: “rada” is an Arabic word, pronounced “ra DAH”. It is child-to-parent pedigree, established by wet-nursing. */
+ I18N::translateContext('Female pedigree', 'Rada'),
+ ],
+ 'U' => [
+ '' => '',
+ 'birth' => I18N::translateContext('Pedigree', 'Birth'),
+ 'adopted' => I18N::translateContext('Pedigree', 'Adopted'),
+ 'foster' => I18N::translateContext('Pedigree', 'Foster'),
+ 'sealing' => /* I18N: “sealing” is a Mormon ceremony. */
+ I18N::translateContext('Pedigree', 'Sealing'),
+ 'rada' => /* I18N: “rada” is an Arabic word, pronounced “ra DAH”. It is child-to-parent pedigree, established by wet-nursing. */
+ I18N::translateContext('Pedigree', 'Rada'),
+ ],
+ ];
+
+ return $values[$sex] ?? $values['U'];
+ }
+}
diff --git a/app/Elements/PermanentRecordFileNumber.php b/app/Elements/PermanentRecordFileNumber.php
new file mode 100644
index 0000000000..b335ec60ef
--- /dev/null
+++ b/app/Elements/PermanentRecordFileNumber.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * PERMANENT_RECORD_FILE_NUMBER := {Size=1:90}
+ * <REGISTERED_RESOURCE_IDENTIFIER>:<RECORD_IDENTIFIER>
+ * The record number that uniquely identifies this record within a registered
+ * network resource. The number will be usable as a cross-reference pointer.
+ * The use of the colon (:) is reserved to indicate the separation of the
+ * "registered resource identifier" (which precedes the colon) and the unique
+ * "record identifier" within that resource (which follows the colon). If the
+ * colon is used, implementations that check pointers should not expect to find
+ * a matching cross-reference identifier in the transmission but would find it
+ * in the indicated database within a network. Making resource files available
+ * to a public network is a future implementation.
+ */
+class PermanentRecordFileNumber extends AbstractElement
+{
+ protected const MAX_LENGTH = 90;
+}
diff --git a/app/Elements/PhoneNumber.php b/app/Elements/PhoneNumber.php
new file mode 100644
index 0000000000..71365be23c
--- /dev/null
+++ b/app/Elements/PhoneNumber.php
@@ -0,0 +1,53 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Tree;
+
+use function e;
+use function preg_match;
+
+/**
+ * PHONE_NUMBER := {Size=1:25}
+ * A phone number.
+ */
+class PhoneNumber extends AbstractElement
+{
+ protected const MAX_LENGTH = 25;
+
+ /**
+ * Display the value of this type of element.
+ *
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function value(string $value, Tree $tree): string
+ {
+ $canonical = $this->canonical($value);
+
+ if (preg_match('/^[+0-9-]+$/', $canonical)) {
+ return '<a dir="ltr" href="tel:' . e($canonical) . '">' . e($canonical) . '</a>';
+ }
+
+ return e($value);
+ }
+}
diff --git a/app/Elements/PhoneticType.php b/app/Elements/PhoneticType.php
new file mode 100644
index 0000000000..089ab80379
--- /dev/null
+++ b/app/Elements/PhoneticType.php
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use function strtolower;
+
+/**
+ * PHONETIC_TYPE := {Size=5:30}
+ * [<user defined> | hangul | kana]
+ * Indicates the method used in transforming the text to the phonetic variation.
+ * <user define> record method used to arrive at the phonetic variation of the name.
+ * hangul Phonetic method for sounding Korean glifs.
+ * kana Hiragana and/or Katakana characters were used in sounding the Kanji
+ * character used by japanese
+ */
+class PhoneticType extends AbstractElement
+{
+ protected const MAX_LENGTH = 30;
+
+ /**
+ * Convert a value to a canonical form.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public function canonical(string $value): string
+ {
+ $value = parent::canonical($value);
+ $lower = strtolower($value);
+
+ if ($lower === 'hangul' || $lower === 'kana') {
+ return $lower;
+ }
+
+ return $value;
+ }
+}
diff --git a/app/Elements/PhysicalDescription.php b/app/Elements/PhysicalDescription.php
new file mode 100644
index 0000000000..b58aea2ddc
--- /dev/null
+++ b/app/Elements/PhysicalDescription.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * PHYSICAL_DESCRIPTION := {Size=1:248}
+ * An unstructured list of the attributes that describe the physical
+ * characteristics of a person, place, or object. Commas separate each attribute.
+ * Example:
+ * 1 DSCR Hair Brown, Eyes Brown, Height 5 ft 8 in
+ * 2 DATE 23 JUL 1935
+ */
+class PhysicalDescription extends AbstractElement
+{
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'PLAC' => '0:1',
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+}
diff --git a/app/Elements/PlaceHierarchy.php b/app/Elements/PlaceHierarchy.php
new file mode 100644
index 0000000000..035e35ecfb
--- /dev/null
+++ b/app/Elements/PlaceHierarchy.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * PLACE_HIERARCHY := {Size=1:120}
+ * This shows the jurisdictional entities that are named in a sequence from the
+ * lowest to the highest jurisdiction. The jurisdictions are separated by
+ * commas, and any jurisdiction's name that is missing is still accounted for
+ * by a comma. When a PLAC.FORM structure is included in the HEADER of a GEDCOM
+ * transmission, it implies that all place names follow this jurisdictional
+ * format and each jurisdiction is accounted for by a comma, whether the name
+ * is known or not. When the PLAC.FORM is subordinate to an event, it
+ * temporarily overrides the implications made by the PLAC.FORM structure
+ * stated in the HEADER. This usage is not common and, therefore, not
+ * encouraged. It should only be used when a system has over-structured its
+ * place-names.
+ */
+class PlaceHierarchy extends AbstractElement
+{
+ protected const MAX_LENGTH = 120;
+}
diff --git a/app/Elements/PlaceLatitude.php b/app/Elements/PlaceLatitude.php
new file mode 100644
index 0000000000..66e80fa0fb
--- /dev/null
+++ b/app/Elements/PlaceLatitude.php
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Tree;
+
+/**
+ * PLACE_LATITUDE := {Size=5:8}
+ * The value specifying the latitudinal coordinate of the place name. The
+ * latitude coordinate is the direction North or South from the equator in
+ * degrees and fraction of degrees carried out to give the desired accuracy.
+ * For example: 18 degrees, 9 minutes, and 3.4 seconds North would be
+ * formatted as N18.150944. Minutes and seconds are converted by dividing
+ * the minutes value by 60 and the seconds value by 3600 and adding the
+ * results together. This sum becomes the fractional part of the degree’s
+ * value.
+ */
+class PlaceLatitude extends AbstractElement
+{
+ /**
+ * An edit control for this data.
+ *
+ * @param string $id
+ * @param string $name
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function edit(string $id, string $name, string $value, Tree $tree): string
+ {
+ return '<input class="form-control" type="text" id="' . $id . '" name="' . $name . '" value="' . e($value) . '" onchange="webtrees.reformatLatitude(this)">';
+ }
+}
diff --git a/app/Elements/PlaceLivingOrdinance.php b/app/Elements/PlaceLivingOrdinance.php
new file mode 100644
index 0000000000..f9eb0a80cb
--- /dev/null
+++ b/app/Elements/PlaceLivingOrdinance.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * PLACE_LIVING_ORDINANCE := {Size=1:120}
+ * <PLACE_NAME>
+ * The locality of the place where a living LDS ordinance took place.
+ * Typically, a living LDS baptism place would be recorded in this field.
+ */
+class PlaceLivingOrdinance extends AbstractElement
+{
+}
diff --git a/app/Elements/PlaceLongtitude.php b/app/Elements/PlaceLongtitude.php
new file mode 100644
index 0000000000..916fe385af
--- /dev/null
+++ b/app/Elements/PlaceLongtitude.php
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Tree;
+
+use function e;
+
+/**
+ * PLACE_LONGITUDE := {Size=5:8}
+ * The value specifying the longitudinal coordinate of the place name. The
+ * longitude coordinate is Degrees and fraction of degrees east or west of the
+ * zero or base meridian coordinate. For example: 168 degrees, 9 minutes, and
+ * 3.4 seconds East would be formatted as E168.150944.
+ */
+class PlaceLongtitude extends AbstractElement
+{
+ /**
+ * An edit control for this data.
+ *
+ * @param string $id
+ * @param string $name
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function edit(string $id, string $name, string $value, Tree $tree): string
+ {
+ return '<input class="form-control" type="text" id="' . $id . '" name="' . $name . '" value="' . e($value) . '" onchange="webtrees.reformatLongitude(this)">';
+ }
+}
diff --git a/app/Elements/PlaceName.php b/app/Elements/PlaceName.php
new file mode 100644
index 0000000000..5250e37a20
--- /dev/null
+++ b/app/Elements/PlaceName.php
@@ -0,0 +1,76 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Http\RequestHandlers\AutoCompletePlace;
+use Fisharebest\Webtrees\Tree;
+
+use function e;
+use function route;
+
+/**
+ * PLACE_NAME := {1,120}
+ * [ <PLACE_TEXT> | <PLACE_TEXT>, <PLACE_NAME> ]
+ * The jurisdictional name of the place where the event took place. Jurisdictions are separated by
+ * commas, for example, "Cove, Cache, Utah, USA." If the actual jurisdictional names of these
+ * places have been identified, they can be shown using a PLAC.FORM structure either in the HEADER
+ * or in the event structure. (See <PLACE_HIERARCHY>, page 58.)
+ */
+class PlaceName extends AbstractElement
+{
+ /**
+ * Convert a value to a canonical form.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public function canonical(string $value): string
+ {
+ $value = parent::canonical($value);
+
+ // Arabic, Chinese and Japanese commas.
+ $value = strtr($value, ['،' => ',', ',' => ',', '、' => ',']);
+
+ // Spaces before commas.
+ $value = strtr($value, [' ,' => ',']);
+
+ // Spaces after commas.
+ $value = strtr($value, [',' => ', ']);
+ $value = strtr($value, [', ' => ', ']);
+
+ return $value;
+ }
+
+ /**
+ * An edit control for this data.
+ *
+ * @param string $id
+ * @param string $name
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function edit(string $id, string $name, string $value, Tree $tree): string
+ {
+ return '<input data-autocomplete-url="' . e(route(AutoCompletePlace::class, ['tree' => $tree->name()])) . '" autocomplete="off" class="form-control" type="text" id="' . e($id) . '" name="' . e($name) . '" value="' . e($value) . '">';
+ }
+}
diff --git a/app/Elements/PlacePhoneticVariation.php b/app/Elements/PlacePhoneticVariation.php
new file mode 100644
index 0000000000..42b3a2c020
--- /dev/null
+++ b/app/Elements/PlacePhoneticVariation.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * PLACE_PHONETIC_VARIATION := {Size=1:120}
+ * The phonetic variation of the place name is written in the same form as was
+ * the place name used in the superior <PLACE_NAME> primitive, but phonetically
+ * written using the method indicated by the subordinate <PHONETIC_TYPE> value,
+ * for example if hiragana was used to provide a reading of a a name written in
+ * kanji, then the <PHONETIC_TYPE> value would indicate kana.
+ * (See <PHONETIC_TYPE> page 57.)
+ */
+class PlacePhoneticVariation extends AbstractElement
+{
+}
diff --git a/app/Elements/PlaceRomanizedVariation.php b/app/Elements/PlaceRomanizedVariation.php
new file mode 100644
index 0000000000..c45c49a255
--- /dev/null
+++ b/app/Elements/PlaceRomanizedVariation.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * PLACE_ROMANIZED_VARIATION := {Size=1:120}
+ * The romanized variation of the place name is written in the same form
+ * prescribed for the place name used in the superior <PLACE_NAME> context. The
+ * method used to romanize the name is indicated by the line_value of the
+ * subordinate <ROMANIZED_TYPE>, for example if romaji was used to provide a
+ * reading of a place name written in kanji, then the <ROMANIZED_TYPE>
+ * subordinate to the ROMN tag would indicate ‘romaji’. (See <ROMANIZED_TYPE>
+ * page 61.)
+ */
+class PlaceRomanizedVariation extends AbstractElement
+{
+}
diff --git a/app/Elements/Possessions.php b/app/Elements/Possessions.php
new file mode 100644
index 0000000000..077ddc94e7
--- /dev/null
+++ b/app/Elements/Possessions.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * POSSESSIONS := {Size=1:248}
+ * A list of possessions (real estate or other property) belonging to this individual.
+ */
+class Possessions extends AbstractElement
+{
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'PLAC' => '0:1',
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+}
diff --git a/app/Elements/Probate.php b/app/Elements/Probate.php
new file mode 100644
index 0000000000..47c44ebe26
--- /dev/null
+++ b/app/Elements/Probate.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * Probate
+ */
+class Probate extends AbstractElement
+{
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'PLAC' => '0:1',
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+}
diff --git a/app/Elements/PublicationDate.php b/app/Elements/PublicationDate.php
new file mode 100644
index 0000000000..5b631b2462
--- /dev/null
+++ b/app/Elements/PublicationDate.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * PUBLICATION_DATE := {Size=10:11}
+ * <DATE_EXACT>
+ * The date this source was published or created.
+ */
+class PublicationDate extends AbstractElement
+{
+}
diff --git a/app/Elements/ReceivingSystemName.php b/app/Elements/ReceivingSystemName.php
new file mode 100644
index 0000000000..79faff8e62
--- /dev/null
+++ b/app/Elements/ReceivingSystemName.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Tree;
+
+/**
+ * RECEIVING_SYSTEM_NAME := {Size=1:20}
+ * The name of the system expected to process the GEDCOM-compatible
+ * transmission. The registered RECEIVING_SYSTEM_NAME for all GEDCOM
+ * submissions to the Family History Department must be one of the following
+ * names:
+ * ! "ANSTFILE" when submitting to Ancestral File.
+ * ! "TempleReady" when submitting for temple ordinance clearance.
+ */
+class ReceivingSystemName extends AbstractElement
+{
+ protected const MAX_LENGTH = 20;
+
+ /**
+ * Create a default value for this element.
+ *
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function default(Tree $tree): string
+ {
+ return 'DISKETTE';
+ }
+}
diff --git a/app/Elements/RelationIsDescriptor.php b/app/Elements/RelationIsDescriptor.php
new file mode 100644
index 0000000000..bd3289d309
--- /dev/null
+++ b/app/Elements/RelationIsDescriptor.php
@@ -0,0 +1,158 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\I18N;
+
+/**
+ * RELATION_IS_DESCRIPTOR := {Size=1:25}
+ * A word or phrase that states object 1's relation is object 2. For example
+ * you would read the following as "Joe Jacob's great grandson is the submitter
+ * pointed to by the @XREF:SUBM@":
+ * 0 INDI
+ * 1 NAME Joe /Jacob/
+ * 1 ASSO @<XREF:SUBM>@
+ * 2 RELA great grandson
+ */
+class RelationIsDescriptor extends AbstractElement
+{
+ protected const MAX_LENGTH = 25;
+
+ /**
+ * A list of controlled values for this element
+ *
+ * @param string $sex - the text depends on the sex of the *linked* individual
+ *
+ * @return array<int|string,string>
+ */
+ public function values(string $sex = 'U'): array
+ {
+ $values = [
+ 'M' => [
+ '' => '',
+ 'attendant' => I18N::translateContext('MALE', 'Attendant'),
+ 'attending' => I18N::translateContext('MALE', 'Attending'),
+ 'best_man' => I18N::translate('Best man'),
+ 'bridesmaid' => I18N::translate('Bridesmaid'),
+ 'buyer' => I18N::translateContext('MALE', 'Buyer'),
+ 'circumciser' => I18N::translate('Circumciser'),
+ 'civil_registrar' => I18N::translateContext('MALE', 'Civil registrar'),
+ 'employee' => I18N::translateContext('MALE', 'Employee'),
+ 'employer' => I18N::translateContext('MALE', 'Employer'),
+ 'foster_child' => I18N::translate('Foster child'),
+ 'foster_father' => I18N::translate('Foster father'),
+ 'foster_mother' => I18N::translate('Foster mother'),
+ 'friend' => I18N::translateContext('MALE', 'Friend'),
+ 'godfather' => I18N::translate('Godfather'),
+ 'godmother' => I18N::translate('Godmother'),
+ 'godparent' => I18N::translate('Godparent'),
+ 'godson' => I18N::translate('Godson'),
+ 'goddaughter' => I18N::translate('Goddaughter'),
+ 'godchild' => I18N::translate('Godchild'),
+ 'guardian' => I18N::translateContext('MALE', 'Guardian'),
+ 'informant' => I18N::translateContext('MALE', 'Informant'),
+ 'lodger' => I18N::translateContext('MALE', 'Lodger'),
+ 'nanny' => I18N::translate('Nanny'),
+ 'nurse' => I18N::translateContext('MALE', 'Nurse'),
+ 'owner' => I18N::translateContext('MALE', 'Owner'),
+ 'priest' => I18N::translate('Priest'),
+ 'rabbi' => I18N::translate('Rabbi'),
+ 'registry_officer' => I18N::translateContext('MALE', 'Registry officer'),
+ 'seller' => I18N::translateContext('MALE', 'Seller'),
+ 'servant' => I18N::translateContext('MALE', 'Servant'),
+ 'slave' => I18N::translateContext('MALE', 'Slave'),
+ 'ward' => I18N::translateContext('MALE', 'Ward'),
+ 'witness' => I18N::translate('Witness'),
+ ],
+ 'F' => [
+ 'attendant' => I18N::translateContext('FEMALE', 'Attendant'),
+ 'attending' => I18N::translateContext('FEMALE', 'Attending'),
+ 'best_man' => I18N::translate('Best man'),
+ 'bridesmaid' => I18N::translate('Bridesmaid'),
+ 'buyer' => I18N::translateContext('FEMALE', 'Buyer'),
+ 'circumciser' => I18N::translate('Circumciser'),
+ 'civil_registrar' => I18N::translateContext('FEMALE', 'Civil registrar'),
+ 'employee' => I18N::translateContext('FEMALE', 'Employee'),
+ 'employer' => I18N::translateContext('FEMALE', 'Employer'),
+ 'foster_child' => I18N::translate('Foster child'),
+ 'foster_father' => I18N::translate('Foster father'),
+ 'foster_mother' => I18N::translate('Foster mother'),
+ 'friend' => I18N::translateContext('FEMALE', 'Friend'),
+ 'godfather' => I18N::translate('Godfather'),
+ 'godmother' => I18N::translate('Godmother'),
+ 'godparent' => I18N::translate('Godparent'),
+ 'godson' => I18N::translate('Godson'),
+ 'goddaughter' => I18N::translate('Goddaughter'),
+ 'godchild' => I18N::translate('Godchild'),
+ 'guardian' => I18N::translateContext('FEMALE', 'Guardian'),
+ 'informant' => I18N::translateContext('FEMALE', 'Informant'),
+ 'lodger' => I18N::translateContext('FEMALE', 'Lodger'),
+ 'nanny' => I18N::translate('Nanny'),
+ 'nurse' => I18N::translateContext('FEMALE', 'Nurse'),
+ 'owner' => I18N::translateContext('FEMALE', 'Owner'),
+ 'priest' => I18N::translate('Priest'),
+ 'rabbi' => I18N::translate('Rabbi'),
+ 'registry_officer' => I18N::translateContext('FEMALE', 'Registry officer'),
+ 'seller' => I18N::translateContext('FEMALE', 'Seller'),
+ 'servant' => I18N::translateContext('FEMALE', 'Servant'),
+ 'slave' => I18N::translateContext('FEMALE', 'Slave'),
+ 'ward' => I18N::translateContext('FEMALE', 'Ward'),
+ 'witness' => I18N::translate('Witness'),
+ ],
+ 'U' => [
+ 'attendant' => I18N::translate('Attendant'),
+ 'attending' => I18N::translate('Attending'),
+ 'best_man' => I18N::translate('Best man'),
+ 'bridesmaid' => I18N::translate('Bridesmaid'),
+ 'buyer' => I18N::translate('Buyer'),
+ 'circumciser' => I18N::translate('Circumciser'),
+ 'civil_registrar' => I18N::translate('Civil registrar'),
+ 'employee' => I18N::translate('Employee'),
+ 'employer' => I18N::translate('Employer'),
+ 'foster_child' => I18N::translate('Foster child'),
+ 'foster_father' => I18N::translate('Foster father'),
+ 'foster_mother' => I18N::translate('Foster mother'),
+ 'friend' => I18N::translate('Friend'),
+ 'godfather' => I18N::translate('Godfather'),
+ 'godmother' => I18N::translate('Godmother'),
+ 'godparent' => I18N::translate('Godparent'),
+ 'godson' => I18N::translate('Godson'),
+ 'goddaughter' => I18N::translate('Goddaughter'),
+ 'godchild' => I18N::translate('Godchild'),
+ 'guardian' => I18N::translate('Guardian'),
+ 'informant' => I18N::translate('Informant'),
+ 'lodger' => I18N::translate('Lodger'),
+ 'nanny' => I18N::translate('Nanny'),
+ 'nurse' => I18N::translate('Nurse'),
+ 'owner' => I18N::translate('Owner'),
+ 'priest' => I18N::translate('Priest'),
+ 'rabbi' => I18N::translate('Rabbi'),
+ 'registry_officer' => I18N::translate('Registry officer'),
+ 'seller' => I18N::translate('Seller'),
+ 'servant' => I18N::translate('Servant'),
+ 'slave' => I18N::translate('Slave'),
+ 'ward' => I18N::translate('Ward'),
+ 'witness' => I18N::translate('Witness'),
+ ],
+ ];
+
+ return $values[$sex] ?? $values['U'];
+ }
+}
diff --git a/app/Elements/ReligiousAffiliation.php b/app/Elements/ReligiousAffiliation.php
new file mode 100644
index 0000000000..7f667120e7
--- /dev/null
+++ b/app/Elements/ReligiousAffiliation.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * RELIGIOUS_AFFILIATION := {Size=1:120}
+ * A name of the religion with which this person, event, or record was affiliated.
+ */
+class ReligiousAffiliation extends AbstractElement
+{
+ protected const MAX_LENGTH = 120;
+
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'PLAC' => '0:1',
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+}
diff --git a/app/Elements/RepositoryRecord.php b/app/Elements/RepositoryRecord.php
new file mode 100644
index 0000000000..4f073ee648
--- /dev/null
+++ b/app/Elements/RepositoryRecord.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * A level 0 repository record
+ */
+class RepositoryRecord extends AbstractElement
+{
+ protected const SUBTAGS = [
+ 'NAME' => '0:1',
+ 'ADDR' => '0:1',
+ 'PHON' => '0:1',
+ 'EMAIL' => '0:1',
+ 'WWW' => '0:1',
+ 'NOTE' => '0:M',
+ 'REFN' => '0:1',
+ 'RIN' => '0:1',
+ ];
+}
diff --git a/app/Elements/Residence.php b/app/Elements/Residence.php
new file mode 100644
index 0000000000..b194f94d2f
--- /dev/null
+++ b/app/Elements/Residence.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * Residence
+ */
+class Residence extends AbstractElement
+{
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'PLAC' => '0:1',
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+}
diff --git a/app/Elements/ResponsibleAgency.php b/app/Elements/ResponsibleAgency.php
new file mode 100644
index 0000000000..d5c14414fa
--- /dev/null
+++ b/app/Elements/ResponsibleAgency.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * RESPONSIBLE_AGENCY := {Size=1:120}
+ * The organization, institution, corporation, person, or other entity that has
+ * responsibility for the associated context. For example, an employer of a
+ * person of an associated occupation, or a church that administered rites or
+ * events, or an organization responsible for creating and/or archiving records.
+ */
+class ResponsibleAgency extends AbstractElement
+{
+ protected const MAX_LENGTH = 120;
+}
diff --git a/app/Elements/RestrictionNotice.php b/app/Elements/RestrictionNotice.php
new file mode 100644
index 0000000000..f4b0df6fd3
--- /dev/null
+++ b/app/Elements/RestrictionNotice.php
@@ -0,0 +1,81 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\I18N;
+
+use function strtolower;
+
+/**
+ * RESTRICTION_NOTICE := {Size=6:7}
+ * [confidential | locked | privacy ]
+ * The restriction notice is defined for Ancestral File usage. Ancestral File
+ * download GEDCOM files may contain this data.
+ * Where:
+ * confidential = This data was marked as confidential by the user. In some systems data marked as
+ * confidential will be treated differently, for example, there might be an option
+ * that would stop confidential data from appearing on printed reports or would
+ * prevent that information from being exported.
+ * locked = Some records in Ancestral File have been satisfactorily proven by evidence, but
+ * because of source conflicts or incorrect traditions, there are repeated attempts
+ * to change this record. By arrangement, the Ancestral File Custodian can lock a
+ * record so that it cannot be changed without an agreement from the person assigned
+ * as the steward of such a record. The assigned steward is either the submitter
+ * listed for the record or Family History Support when no submitter is listed.
+ * privacy = Indicate that information concerning this record is not present due to rights of
+ * or an approved request for privacy. For example, data from requested downloads of
+ * the Ancestral File may have individuals marked with ‘privacy’ if they are assumed
+ * living, that is they were born within the last 110 years and there isn’t a death
+ * date. In certain cases family records may also be marked with the RESN tag of
+ * privacy if either individual acting in the role of HUSB or WIFE is assumed living.
+ */
+class RestrictionNotice extends AbstractElement
+{
+ /**
+ * Convert a value to a canonical form.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public function canonical(string $value): string
+ {
+ return strtolower(parent::canonical($value));
+ }
+
+ /**
+ * A list of controlled values for this element
+ *
+ * @return array<int|string,string>
+ */
+ public function values(): array
+ {
+ // Note: "1 RESN none" is not valid gedcom.
+ // However, webtrees privacy rules will interpret it as "show an otherwise private record to public".
+
+ return [
+ '' => '',
+ 'none' => '<i class="icon-resn-none"></i> ' . I18N::translate('Show to visitors'),
+ 'privacy' => '<i class="icon-resn-privacy"></i> ' . I18N::translate('Show to members'),
+ 'confidential' => '<i class="icon-resn-confidential"></i> ' . I18N::translate('Show to managers'),
+ 'locked' => '<i class="icon-resn-locked"></i> ' . I18N::translate('Only managers can edit'),
+ ];
+ }
+}
diff --git a/app/Elements/Retirement.php b/app/Elements/Retirement.php
new file mode 100644
index 0000000000..15aa242a4a
--- /dev/null
+++ b/app/Elements/Retirement.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * Retirement
+ */
+class Retirement extends AbstractElement
+{
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'AGE' => '0:1',
+ 'PLAC' => '0:1',
+ 'AGNC' => '0:1',
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+}
diff --git a/app/Elements/RoleInEvent.php b/app/Elements/RoleInEvent.php
new file mode 100644
index 0000000000..5fe1a1fbf2
--- /dev/null
+++ b/app/Elements/RoleInEvent.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * ROLE_IN_EVENT := {Size=1:15}
+ * [ CHIL | HUSB | WIFE | MOTH | FATH | SPOU | (<ROLE_DESCRIPTOR>) ]
+ * Indicates what role this person played in the event that is being cited in this context. For
+ * example, if you cite a child's birth record as the source of the mother's name, the value for
+ * this field is "MOTH." If you describe the groom of a marriage, the role is "HUSB." If the role
+ * is something different than one of the six relationship role tags listed above then enclose the
+ * role name within matching parentheses.
+ */
+class RoleInEvent extends AbstractElement
+{
+ protected const MAX_LENGTH = 15;
+}
diff --git a/app/Elements/RomanizedType.php b/app/Elements/RomanizedType.php
new file mode 100644
index 0000000000..2df79b16f8
--- /dev/null
+++ b/app/Elements/RomanizedType.php
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use function strtolower;
+
+/**
+ * ROMANIZED_TYPE := {Size=5:30}
+ * [<user defined> | pinyin | romaji | wadegiles]
+ * Indicates the method used in transforming the text to a romanized variation.
+ */
+class RomanizedType extends AbstractElement
+{
+ protected const MAX_LENGTH = 30;
+
+ /**
+ * Convert a value to a canonical form.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public function canonical(string $value): string
+ {
+ $value = parent::canonical($value);
+ $lower = strtolower($value);
+
+ if ($lower === 'pinyin' || $lower === 'romaji' || $lower === 'wadegiles') {
+ return $lower;
+ }
+
+ return $value;
+ }
+}
diff --git a/app/Elements/ScholasticAchievement.php b/app/Elements/ScholasticAchievement.php
new file mode 100644
index 0000000000..c6be60a909
--- /dev/null
+++ b/app/Elements/ScholasticAchievement.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * SCHOLASTIC_ACHIEVEMENT := {Size=1:248}
+ * A description of a scholastic or educational achievement or pursuit.
+ */
+class ScholasticAchievement extends AbstractElement
+{
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'PLAC' => '0:1',
+ 'AGNC' => '0:1',
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+}
diff --git a/app/Elements/SexValue.php b/app/Elements/SexValue.php
new file mode 100644
index 0000000000..34975ceafc
--- /dev/null
+++ b/app/Elements/SexValue.php
@@ -0,0 +1,78 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\I18N;
+use Fisharebest\Webtrees\Tree;
+
+use function strtoupper;
+use function view;
+
+/**
+ * SEX_VALUE := {Size=1:7}
+ * A code that indicates the sex of the individual
+ */
+class SexValue extends AbstractElement
+{
+ /**
+ * Convert a value to a canonical form.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public function canonical(string $value): string
+ {
+ return strtoupper(parent::canonical($value));
+ }
+
+ /**
+ * An edit control for this data.
+ *
+ * @param string $id
+ * @param string $name
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function edit(string $id, string $name, string $value, Tree $tree): string
+ {
+ return view('components/radios-inline', [
+ 'name' => $name,
+ 'options' => $this->values(),
+ 'selected' => $value,
+ ]);
+ }
+
+ /**
+ * A list of controlled values for this element
+ *
+ * @return array<int|string,string>
+ */
+ public function values(): array
+ {
+ return [
+ 'M' => I18N::translate('Male'),
+ 'F' => I18N::translate('Female'),
+ 'U' => I18N::translateContext('unknown gender', 'Unknown'),
+ ];
+ }
+}
diff --git a/app/Elements/SocialSecurityNumber.php b/app/Elements/SocialSecurityNumber.php
new file mode 100644
index 0000000000..0cefa6fc40
--- /dev/null
+++ b/app/Elements/SocialSecurityNumber.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * SOCIAL_SECURITY_NUMBER := {Size=10:11}
+ * A number assigned to a person in the United States for identification purposes.
+ */
+class SocialSecurityNumber extends AbstractElement
+{
+ protected const MAX_LENGTH = 11;
+
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'PLAC' => '0:1',
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+}
diff --git a/app/Elements/SourceCallNumber.php b/app/Elements/SourceCallNumber.php
new file mode 100644
index 0000000000..0028496d4c
--- /dev/null
+++ b/app/Elements/SourceCallNumber.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Tree;
+
+/**
+ * SOURCE_CALL_NUMBER := {Size=1:120}
+ * An identification or reference description used to file and retrieve items from the holdings of
+ * a repository.
+ */
+class SourceCallNumber extends AbstractElement
+{
+ protected const MAX_LENGTH = 120;
+ /**
+ * Display the value of this type of element.
+ *
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function value(string $value, Tree $tree): string
+ {
+ $canonical = $this->canonical($value);
+
+ return $this->valueAutoLink($canonical);
+ }
+}
diff --git a/app/Elements/SourceData.php b/app/Elements/SourceData.php
new file mode 100644
index 0000000000..80019f1137
--- /dev/null
+++ b/app/Elements/SourceData.php
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Tree;
+
+/**
+ * SOUR:DATA is an empty element with children; EVEN, AGNC and NOTE.
+ */
+class SourceData extends EmptyElement
+{
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'TEXT' => '0:M',
+ ];
+
+ protected const ABRIDGED_SUBTAGS = [
+ 'TEXT' => '0:M',
+ ];
+
+ /**
+ * @param Tree $tree
+ *
+ * @return array<string,string>
+ */
+ public function subtags(Tree $tree): array
+ {
+ if ($tree->getPreference('FULL_SOURCES') === '1') {
+ return static::SUBTAGS;
+ }
+
+ return static::ABRIDGED_SUBTAGS;
+ }
+}
diff --git a/app/Elements/SourceDescriptiveTitle.php b/app/Elements/SourceDescriptiveTitle.php
new file mode 100644
index 0000000000..4725a4a138
--- /dev/null
+++ b/app/Elements/SourceDescriptiveTitle.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * SOURCE_DESCRIPTIVE_TITLE := {Size=1:248}
+ * The title of the work, record, or item and, when appropriate, the title of the larger work or
+ * series of which it is a part.
+ * For a published work, a book for example, might have a title plus the title of the series of
+ * which the book is a part. A magazine article would have a title plus the title of the magazine
+ * that published the article.
+ * For An unpublished work, such as:
+ * ! A letter might include the date, the sender, and the receiver.
+ * ! A transaction between a buyer and seller might have their names and the transaction date.
+ * ! A family Bible containing genealogical information might have past and present owners and a
+ * physical description of the book.
+ * ! A personal interview would cite the informant and interviewer.
+ */
+class SourceDescriptiveTitle extends AbstractElement
+{
+}
diff --git a/app/Elements/SourceFiledByEntry.php b/app/Elements/SourceFiledByEntry.php
new file mode 100644
index 0000000000..43dd2da544
--- /dev/null
+++ b/app/Elements/SourceFiledByEntry.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * SOURCE_FILED_BY_ENTRY := {Size=1:60}
+ * This entry is to provide a short title used for sorting, filing, and retrieving source records.
+ */
+class SourceFiledByEntry extends AbstractElement
+{
+ protected const MAX_LENGTH = 60;
+}
diff --git a/app/Elements/SourceJurisdictionPlace.php b/app/Elements/SourceJurisdictionPlace.php
new file mode 100644
index 0000000000..bdcbaf13d6
--- /dev/null
+++ b/app/Elements/SourceJurisdictionPlace.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * PLACE_NAME := {Size=1:120}
+ * The name of the lowest jurisdiction that encompasses all lower-level places named in this
+ * source. For example, "Oneida, Idaho" would be used as a source jurisdiction place for events
+ * occurring in the various towns within Oneida County. "Idaho" would be the source jurisdiction
+ * place if the events recorded took place in other counties as well as Oneida County.
+ */
+class SourceJurisdictionPlace extends PlaceName
+{
+}
diff --git a/app/Elements/SourceMediaType.php b/app/Elements/SourceMediaType.php
new file mode 100644
index 0000000000..3b72426517
--- /dev/null
+++ b/app/Elements/SourceMediaType.php
@@ -0,0 +1,84 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\I18N;
+
+use function strtolower;
+use function uasort;
+
+/**
+ * SOURCE_MEDIA_TYPE := {Size=1:15}
+ * [ audio | book | card | electronic | fiche | film | magazine |
+ * manuscript | map | newspaper | photo | tombstone | video ]
+ * A code, selected from one of the media classifications choices above, that indicates the type of
+ * material in which the referenced source is stored.
+ */
+class SourceMediaType extends AbstractElement
+{
+ protected const MAX_LENGTH = 15;
+
+ /**
+ * Convert a value to a canonical form.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public function canonical(string $value): string
+ {
+ return strtolower(parent::canonical($value));
+ }
+
+ /**
+ * A list of controlled values for this element
+ *
+ * @return array<int|string,string>
+ */
+ public function values(): array
+ {
+ // *** indicates custom values
+ $values = [
+ '' => '',
+ 'audio' => /* I18N: Type of media object */ I18N::translate('Audio'),
+ 'book' => /* I18N: Type of media object */ I18N::translate('Book'),
+ 'card' => /* I18N: Type of media object */ I18N::translate('Card'),
+ 'certificate' => /* I18N: Type of media object */ I18N::translate('Certificate'), // ***
+ 'coat' => /* I18N: Type of media object */ I18N::translate('Coat of arms'), // ***
+ 'document' => /* I18N: Type of media object */ I18N::translate('Document'), // ***
+ 'electronic' => /* I18N: Type of media object */ I18N::translate('Electronic'),
+ 'fiche' => /* I18N: Type of media object */ I18N::translate('Microfiche'),
+ 'film' => /* I18N: Type of media object */ I18N::translate('Microfilm'),
+ 'magazine' => /* I18N: Type of media object */ I18N::translate('Magazine'),
+ 'manuscript' => /* I18N: Type of media object */ I18N::translate('Manuscript'),
+ 'map' => /* I18N: Type of media object */ I18N::translate('Map'),
+ 'newspaper' => /* I18N: Type of media object */ I18N::translate('Newspaper'),
+ 'other' => /* I18N: Type of media object */ I18N::translate('Other'), // ***
+ 'photo' => /* I18N: Type of media object */ I18N::translate('Photo'),
+ 'painting' => /* I18N: Type of media object */ I18N::translate('Painting'), // ***
+ 'tombstone' => /* I18N: Type of media object */ I18N::translate('Tombstone'),
+ 'video' => /* I18N: Type of media object */ I18N::translate('Video'),
+ ];
+
+ uasort($values, '\Fisharebest\Webtrees\I18N::strcasecmp');
+
+ return $values;
+ }
+}
diff --git a/app/Elements/SourceOriginator.php b/app/Elements/SourceOriginator.php
new file mode 100644
index 0000000000..8deea58662
--- /dev/null
+++ b/app/Elements/SourceOriginator.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * SOURCE_ORIGINATOR := {Size=1:248}
+ * The person, agency, or entity who created the record. For a published work, this could be the
+ * author, compiler, transcriber, abstractor, or editor. For an unpublished source, this may be an
+ * individual, a government agency, church organization, or private organization, etc.
+ */
+class SourceOriginator extends AbstractElement
+{
+}
diff --git a/app/Elements/SourcePublicationFacts.php b/app/Elements/SourcePublicationFacts.php
new file mode 100644
index 0000000000..e90c38d319
--- /dev/null
+++ b/app/Elements/SourcePublicationFacts.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Tree;
+
+/**
+ * SOURCE_PUBLICATION_FACTS := {Size=1:248}
+ * When and where the record was created. For published works, this includes information such as
+ * the city of publication, name of the publisher, and year of publication.
+ * For an unpublished work, it includes the date the record was created and the place where it was
+ * created. For example, the county and state of residence of a person making a declaration for a
+ * pension or the city and state of residence of the writer of a letter.
+ */
+class SourcePublicationFacts extends AbstractElement
+{
+ /**
+ * Display the value of this type of element.
+ *
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function value(string $value, Tree $tree): string
+ {
+ $canonical = $this->canonical($value);
+
+ return $this->valueAutoLink($canonical);
+ }
+}
diff --git a/app/Elements/SourceRecord.php b/app/Elements/SourceRecord.php
new file mode 100644
index 0000000000..b71c0ecd77
--- /dev/null
+++ b/app/Elements/SourceRecord.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * A level 0 source record
+ */
+class SourceRecord extends AbstractElement
+{
+ protected const SUBTAGS = [
+ 'TITL' => '0:1',
+ 'ABBR' => '0:1',
+ 'AUTH' => '0:1',
+ 'PUBL' => '0:1',
+ 'REPO' => '0:M',
+ 'TEXT' => '0:1',
+ 'OBJE' => '0:M',
+ 'NOTE' => '0:M',
+ 'DATA' => '0:1',
+ 'REFN' => '0:1',
+ 'RIN' => '0:1',
+ ];
+}
diff --git a/app/Elements/SubmissionRecord.php b/app/Elements/SubmissionRecord.php
new file mode 100644
index 0000000000..2265c34e71
--- /dev/null
+++ b/app/Elements/SubmissionRecord.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * A level 0 submission record
+ */
+class SubmissionRecord extends AbstractElement
+{
+ protected const SUBTAGS = [
+ 'SUBM' => '0:1',
+ 'FAMF' => '0:1',
+ 'TEMP' => '0:1',
+ 'ANCE' => '0:1',
+ 'DESC' => '0:1',
+ 'ORDI' => '0:1',
+ 'RIN' => '0:1',
+ 'NOTE' => '0:1',
+ ];
+}
diff --git a/app/Elements/SubmitterName.php b/app/Elements/SubmitterName.php
new file mode 100644
index 0000000000..fd09564553
--- /dev/null
+++ b/app/Elements/SubmitterName.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * SUBMITTER_NAME := {Size=1:60}
+ * The name of the submitter formatted for display and address generation.
+ */
+class SubmitterName extends AbstractElement
+{
+ protected const MAX_LENGTH = 60;
+}
diff --git a/app/Elements/SubmitterRecord.php b/app/Elements/SubmitterRecord.php
new file mode 100644
index 0000000000..c78018b03d
--- /dev/null
+++ b/app/Elements/SubmitterRecord.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * A level 0 submitter record
+ */
+class SubmitterRecord extends AbstractElement
+{
+ protected const SUBTAGS = [
+ 'NAME' => '0:1',
+ 'ADDR' => '0:1',
+ 'PHON' => '0:1',
+ 'EMAIL' => '0:1',
+ 'WWW' => '0:1',
+ 'LANG' => '0:3',
+ 'RFN' => '0:1',
+ 'RIN' => '0:1',
+ 'NOTE' => '0:1',
+ ];
+}
diff --git a/app/Elements/SubmitterRegisteredRfn.php b/app/Elements/SubmitterRegisteredRfn.php
new file mode 100644
index 0000000000..6c0c90f8be
--- /dev/null
+++ b/app/Elements/SubmitterRegisteredRfn.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * SUBMITTER_REGISTERED_RFN := {Size=1:30}
+ * A registered number of a submitter of Ancestral File data. This number is used in subsequent
+ * submissions or inquiries by the submitter for identification purposes.
+ */
+class SubmitterRegisteredRfn extends AbstractElement
+{
+ protected const MAX_LENGTH = 30;
+}
diff --git a/app/Elements/SubmitterText.php b/app/Elements/SubmitterText.php
new file mode 100644
index 0000000000..583d9c937c
--- /dev/null
+++ b/app/Elements/SubmitterText.php
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Tree;
+
+/**
+ * SUBMITTER_TEXT:= {Size=1:248}
+ * Comments or opinions from the submitter.
+ */
+class SubmitterText extends AbstractElement
+{
+ /**
+ * Convert a value to a canonical form.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public function canonical(string $value): string
+ {
+ // Browsers use MS-DOS line endings in multi-line data.
+ return strtr($value, ["\r\n" => "\n", "\r" => "\n"]);
+ }
+
+ /**
+ * An edit control for this data.
+ *
+ * @param string $id
+ * @param string $name
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function edit(string $id, string $name, string $value, Tree $tree): string
+ {
+ return $this->editTextArea($id, $name, $value);
+ }
+}
diff --git a/app/Elements/TempleCode.php b/app/Elements/TempleCode.php
new file mode 100644
index 0000000000..8ac67d4f76
--- /dev/null
+++ b/app/Elements/TempleCode.php
@@ -0,0 +1,215 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\I18N;
+
+use function strtoupper;
+
+/**
+ * TEMPLE_CODE := {Size=4:5}
+ * An abbreviation of the temple in which the LDS ordinances were performed.
+ * (See Appendix B, page 96.)
+ */
+class TempleCode extends AbstractElement
+{
+ /**
+ * Convert a value to a canonical form.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public function canonical(string $value): string
+ {
+ return strtoupper(parent::canonical($value));
+ }
+
+ /**
+ * A list of controlled values for this element
+ *
+ * @return array<int|string,string>
+ */
+ public function values(): array
+ {
+ $values = [
+ 'ABA' => /* I18N: Location of an LDS church temple */ I18N::translate('Aba, Nigeria'),
+ 'ACCRA' => /* I18N: Location of an LDS church temple */ I18N::translate('Accra, Ghana'),
+ 'ADELA' => /* I18N: Location of an LDS church temple */ I18N::translate('Adelaide, Australia'),
+ 'ALBER' => /* I18N: Location of an LDS church temple */ I18N::translate('Cardston, Alberta, Canada'),
+ 'ALBUQ' => /* I18N: Location of an LDS church temple */ I18N::translate('Albuquerque, New Mexico, United States'),
+ 'ANCHO' => /* I18N: Location of an LDS church temple */ I18N::translate('Anchorage, Alaska, United States'),
+ 'APIA' => /* I18N: Location of an LDS church temple */ I18N::translate('Apia, Samoa'),
+ 'ARIZO' => /* I18N: Location of an LDS church temple */ I18N::translate('Mesa, Arizona, United States'),
+ 'ASUNC' => /* I18N: Location of an LDS church temple */ I18N::translate('Asuncion, Paraguay'),
+ 'ATLAN' => /* I18N: Location of an LDS church temple */ I18N::translate('Atlanta, Georgia, United States'),
+ 'BAIRE' => /* I18N: Location of an LDS church temple */ I18N::translate('Buenos Aires, Argentina'),
+ 'BILLI' => /* I18N: Location of an LDS church temple */ I18N::translate('Billings, Montana, United States'),
+ 'BIRMI' => /* I18N: Location of an LDS church temple */ I18N::translate('Birmingham, Alabama, United States'),
+ 'BISMA' => /* I18N: Location of an LDS church temple */ I18N::translate('Bismarck, North Dakota, United States'),
+ 'BOGOT' => /* I18N: Location of an LDS church temple */ I18N::translate('Bogota, Colombia'),
+ 'BOISE' => /* I18N: Location of an LDS church temple */ I18N::translate('Boise, Idaho, United States'),
+ 'BOSTO' => /* I18N: Location of an LDS church temple */ I18N::translate('Boston, Massachusetts, United States'),
+ 'BOUNT' => /* I18N: Location of an LDS church temple */ I18N::translate('Bountiful, Utah, United States'),
+ 'BRIGH' => /* I18N: Location of an LDS church temple */ I18N::translate('Brigham City, Utah, United States'),
+ 'BRISB' => /* I18N: Location of an LDS church temple */ I18N::translate('Brisbane, Australia'),
+ 'BROUG' => /* I18N: Location of an LDS church temple */ I18N::translate('Baton Rouge, Louisiana, United States'),
+ 'CALGA' => /* I18N: Location of an LDS church temple */ I18N::translate('Calgary, Alberta, Canada'),
+ 'CAMPI' => /* I18N: Location of an LDS church temple */ I18N::translate('Campinas, Brazil'),
+ 'CARAC' => /* I18N: Location of an LDS church temple */ I18N::translate('Caracas, Venezuela'),
+ 'CEBUP' => /* I18N: Location of an LDS church temple */ I18N::translate('Cebu City, Philippines'),
+ 'CHICA' => /* I18N: Location of an LDS church temple */ I18N::translate('Chicago, Illinois, United States'),
+ 'CIUJU' => /* I18N: Location of an LDS church temple */ I18N::translate('Ciudad Juarez, Mexico'),
+ 'COCHA' => /* I18N: Location of an LDS church temple */ I18N::translate('Cochabamba, Bolivia'),
+ 'COLJU' => /* I18N: Location of an LDS church temple */ I18N::translate('Colonia Juarez, Mexico'),
+ 'COLSC' => /* I18N: Location of an LDS church temple */ I18N::translate('Columbia, South Carolina, United States'),
+ 'COLUM' => /* I18N: Location of an LDS church temple */ I18N::translate('Columbus, Ohio, United States'),
+ 'COPEN' => /* I18N: Location of an LDS church temple */ I18N::translate('Copenhagen, Denmark'),
+ 'CORDO' => /* I18N: Location of an LDS church temple */ I18N::translate('Cordoba, Argentina'),
+ 'CRIVE' => /* I18N: Location of an LDS church temple */ I18N::translate('Columbia River, Washington, United States'),
+ 'CURIT' => /* I18N: Location of an LDS church temple */ I18N::translate('Curitiba, Brazil'),
+ 'DALLA' => /* I18N: Location of an LDS church temple */ I18N::translate('Dallas, Texas, United States'),
+ 'DENVE' => /* I18N: Location of an LDS church temple */ I18N::translate('Denver, Colorado, United States'),
+ 'DETRO' => /* I18N: Location of an LDS church temple */ I18N::translate('Detroit, Michigan, United States'),
+ 'DRAPE' => /* I18N: Location of an LDS church temple */ I18N::translate('Draper, Utah, United States'),
+ 'EDMON' => /* I18N: Location of an LDS church temple */ I18N::translate('Edmonton, Alberta, Canada'),
+ 'EHOUS' => /* I18N: Location of an historic LDS church temple - http://en.wikipedia.org/wiki/Endowment_house */ I18N::translate('Endowment House'),
+ 'FORTL' => /* I18N: Location of an LDS church temple */ I18N::translate('Fort Lauderdale, Florida, United States'),
+ 'FRANK' => /* I18N: Location of an LDS church temple */ I18N::translate('Frankfurt am Main, Germany'),
+ 'FREIB' => /* I18N: Location of an LDS church temple */ I18N::translate('Freiburg, Germany'),
+ 'FRESN' => /* I18N: Location of an LDS church temple */ I18N::translate('Fresno, California, United States'),
+ 'FUKUO' => /* I18N: Location of an LDS church temple */ I18N::translate('Fukuoka, Japan'),
+ 'GILAV' => /* I18N: Location of an LDS church temple */ I18N::translate('Gila Valley, Arizona, United States'),
+ 'GILBE' => /* I18N: Location of an LDS church temple */ I18N::translate('Gilbert, Arizona, United States'),
+ 'GUADA' => /* I18N: Location of an LDS church temple */ I18N::translate('Guadalajara, Mexico'),
+ 'GUATE' => /* I18N: Location of an LDS church temple */ I18N::translate('Guatemala City, Guatemala'),
+ 'GUAYA' => /* I18N: Location of an LDS church temple */ I18N::translate('Guayaquil, Ecuador'),
+ 'HAGUE' => /* I18N: Location of an LDS church temple */ I18N::translate('The Hague, Netherlands'),
+ 'HALIF' => /* I18N: Location of an LDS church temple */ I18N::translate('Halifax, Nova Scotia, Canada'),
+ 'HARTF' => /* I18N: Location of an LDS church temple */ I18N::translate('Hartford, Connecticut, United States'),
+ 'HAWAI' => /* I18N: Location of an LDS church temple */ I18N::translate('Laie, Hawaii, United States'),
+ 'HELSI' => /* I18N: Location of an LDS church temple */ I18N::translate('Helsinki, Finland'),
+ 'HERMO' => /* I18N: Location of an LDS church temple */ I18N::translate('Hermosillo, Mexico'),
+ 'HKONG' => /* I18N: Location of an LDS church temple */ I18N::translate('Hong Kong'),
+ 'HOUST' => /* I18N: Location of an LDS church temple */ I18N::translate('Houston, Texas, United States'),
+ 'IFALL' => /* I18N: Location of an LDS church temple */ I18N::translate('Idaho Falls, Idaho, United States'),
+ 'INDIA' => /* I18N: Location of an LDS church temple */ I18N::translate('Indianapolis, Indiana, United States'),
+ 'JOHAN' => /* I18N: Location of an LDS church temple */ I18N::translate('Johannesburg, South Africa'),
+ 'JRIVE' => /* I18N: Location of an LDS church temple */ I18N::translate('Jordan River, Utah, United States'),
+ 'KANSA' => /* I18N: Location of an LDS church temple */ I18N::translate('Kansas City, Missouri, United States'),
+ 'KONA' => /* I18N: Location of an LDS church temple */ I18N::translate('Kona, Hawaii, United States'),
+ 'KYIV' => /* I18N: Location of an LDS church temple */ I18N::translate('Kiev, Ukraine'),
+ 'LANGE' => /* I18N: Location of an LDS church temple */ I18N::translate('Los Angeles, California, United States'),
+ 'LIMA' => /* I18N: Location of an LDS church temple */ I18N::translate('Lima, Peru'),
+ 'LOGAN' => /* I18N: Location of an LDS church temple */ I18N::translate('Logan, Utah, United States'),
+ 'LONDO' => /* I18N: Location of an LDS church temple */ I18N::translate('London, England'),
+ 'LOUIS' => /* I18N: Location of an LDS church temple */ I18N::translate('Louisville, Kentucky, United States'),
+ 'LUBBO' => /* I18N: Location of an LDS church temple */ I18N::translate('Lubbock, Texas, United States'),
+ 'LVEGA' => /* I18N: Location of an LDS church temple */ I18N::translate('Las Vegas, Nevada, United States'),
+ 'MADRI' => /* I18N: Location of an LDS church temple */ I18N::translate('Madrid, Spain'),
+ 'MANAU' => /* I18N: Location of an LDS church temple */ I18N::translate('Manaus, Brazil'),
+ 'MANHA' => /* I18N: Location of an LDS church temple */ I18N::translate('Manhattan, New York, United States'),
+ 'MANIL' => /* I18N: Location of an LDS church temple */ I18N::translate('Manila, Philippines'),
+ 'MANTI' => /* I18N: Location of an LDS church temple */ I18N::translate('Manti, Utah, United States'),
+ 'MEDFO' => /* I18N: Location of an LDS church temple */ I18N::translate('Medford, Oregon, United States'),
+ 'MELBO' => /* I18N: Location of an LDS church temple */ I18N::translate('Melbourne, Australia'),
+ 'MEMPH' => /* I18N: Location of an LDS church temple */ I18N::translate('Memphis, Tennessee, United States'),
+ 'MERID' => /* I18N: Location of an LDS church temple */ I18N::translate('Merida, Mexico'),
+ 'MEXIC' => /* I18N: Location of an LDS church temple */ I18N::translate('Mexico City, Mexico'),
+ 'MNTVD' => /* I18N: Location of an LDS church temple */ I18N::translate('Montevideo, Uruguay'),
+ 'MONTE' => /* I18N: Location of an LDS church temple */ I18N::translate('Monterrey, Mexico'),
+ 'MONTI' => /* I18N: Location of an LDS church temple */ I18N::translate('Monticello, Utah, United States'),
+ 'MONTR' => /* I18N: Location of an LDS church temple */ I18N::translate('Montreal, Quebec, Canada'),
+ 'MTIMP' => /* I18N: Location of an LDS church temple */ I18N::translate('Mount Timpanogos, Utah, United States'),
+ 'NASHV' => /* I18N: Location of an LDS church temple */ I18N::translate('Nashville, Tennessee, United States'),
+ 'NAUV2' => /* I18N: Location of an LDS church temple */ I18N::translate('Nauvoo (new), Illinois, United States'),
+ 'NAUVO' => /* I18N: Location of an LDS church temple */ I18N::translate('Nauvoo (original), Illinois, United States'),
+ 'NBEAC' => /* I18N: Location of an LDS church temple */ I18N::translate('Newport Beach, California, United States'),
+ 'NUKUA' => /* I18N: Location of an LDS church temple */ I18N::translate('Nuku’Alofa, Tonga'),
+ 'NYORK' => /* I18N: Location of an LDS church temple */ I18N::translate('New York, New York, United States'),
+ 'NZEAL' => /* I18N: Location of an LDS church temple */ I18N::translate('Hamilton, New Zealand'),
+ 'OAKLA' => /* I18N: Location of an LDS church temple */ I18N::translate('Oakland, California, United States'),
+ 'OAXAC' => /* I18N: Location of an LDS church temple */ I18N::translate('Oaxaca, Mexico'),
+ 'OGDEN' => /* I18N: Location of an LDS church temple */ I18N::translate('Ogden, Utah, United States'),
+ 'OKLAH' => /* I18N: Location of an LDS church temple */ I18N::translate('Oklahoma City, Oklahoma, United States'),
+ 'OQUIR' => /* I18N: Location of an LDS church temple */ I18N::translate('Oquirrh Mountain, Utah, United States'),
+ 'ORLAN' => /* I18N: Location of an LDS church temple */ I18N::translate('Orlando, Florida, United States'),
+ 'PALEG' => /* I18N: Location of an LDS church temple */ I18N::translate('Porto Alegre, Brazil'),
+ 'PALMY' => /* I18N: Location of an LDS church temple */ I18N::translate('Palmyra, New York, United States'),
+ 'PANAM' => /* I18N: Location of an LDS church temple */ I18N::translate('Panama City, Panama'),
+ 'PAPEE' => /* I18N: Location of an LDS church temple */ I18N::translate('Papeete, Tahiti'),
+ 'PAYSO' => /* I18N: Location of an LDS church temple */ I18N::translate('Payson, Utah, United States'),
+ 'PERTH' => /* I18N: Location of an LDS church temple */ I18N::translate('Perth, Australia'),
+ 'PHOEN' => /* I18N: Location of an LDS church temple */ I18N::translate('Phoenix, Arizona, United States'),
+ 'POFFI' => /* I18N: Location of an historic LDS church temple - http://en.wikipedia.org/wiki/President_of_the_Church */ I18N::translate('President’s Office'),
+ 'PORTL' => /* I18N: Location of an LDS church temple */ I18N::translate('Portland, Oregon, United States'),
+ 'PREST' => /* I18N: Location of an LDS church temple */ I18N::translate('Preston, England'),
+ 'PROCC' => /* I18N: Location of an LDS church temple */ I18N::translate('Provo City Center, Utah, United States'),
+ 'PROVO' => /* I18N: Location of an LDS church temple */ I18N::translate('Provo, Utah, United States'),
+ 'QUETZ' => /* I18N: Location of an LDS church temple */ I18N::translate('Quetzaltenango, Guatemala'),
+ 'RALEI' => /* I18N: Location of an LDS church temple */ I18N::translate('Raleigh, North Carolina, United States'),
+ 'RECIF' => /* I18N: Location of an LDS church temple */ I18N::translate('Recife, Brazil'),
+ 'REDLA' => /* I18N: Location of an LDS church temple */ I18N::translate('Redlands, California, United States'),
+ 'REGIN' => /* I18N: Location of an LDS church temple */ I18N::translate('Regina, Saskatchewan, Canada'),
+ 'RENO' => /* I18N: Location of an LDS church temple */ I18N::translate('Reno, Nevada, United States'),
+ 'REXBU' => /* I18N: Location of an LDS church temple */ I18N::translate('Rexburg, Idaho, United States'),
+ 'SACRA' => /* I18N: Location of an LDS church temple */ I18N::translate('Sacramento, California, United States'),
+ 'SANSA' => /* I18N: Location of an LDS church temple */ I18N::translate('San Salvador, El Salvador'),
+ 'SANTI' => /* I18N: Location of an LDS church temple */ I18N::translate('Santiago, Chile'),
+ 'SANTO' => /* I18N: Location of an LDS church temple */ I18N::translate('San Antonio, Texas, United States'),
+ 'SDIEG' => /* I18N: Location of an LDS church temple */ I18N::translate('San Diego, California, United States'),
+ 'SDOMI' => /* I18N: Location of an LDS church temple */ I18N::translate('Santo Domingo, Dominican Republic'),
+ 'SEATT' => /* I18N: Location of an LDS church temple */ I18N::translate('Seattle, Washington, United States'),
+ 'SEOUL' => /* I18N: Location of an LDS church temple */ I18N::translate('Seoul, Korea'),
+ 'SGEOR' => /* I18N: Location of an LDS church temple */ I18N::translate('St. George, Utah, United States'),
+ 'SJOSE' => /* I18N: Location of an LDS church temple */ I18N::translate('San Jose, Costa Rica'),
+ 'SLAKE' => /* I18N: Location of an LDS church temple */ I18N::translate('Salt Lake City, Utah, United States'),
+ 'SLOUI' => /* I18N: Location of an LDS church temple */ I18N::translate('St. Louis, Missouri, United States'),
+ 'SNOWF' => /* I18N: Location of an LDS church temple */ I18N::translate('Snowflake, Arizona, United States'),
+ 'SPAUL' => /* I18N: Location of an LDS church temple */ I18N::translate('Sao Paulo, Brazil'),
+ 'SPMIN' => /* I18N: Location of an LDS church temple */ I18N::translate('St. Paul, Minnesota, United States'),
+ 'SPOKA' => /* I18N: Location of an LDS church temple */ I18N::translate('Spokane, Washington, United States'),
+ 'STOCK' => /* I18N: Location of an LDS church temple */ I18N::translate('Stockholm, Sweden'),
+ 'SUVA' => /* I18N: Location of an LDS church temple */ I18N::translate('Suva, Fiji'),
+ 'SWISS' => /* I18N: Location of an LDS church temple */ I18N::translate('Bern, Switzerland'),
+ 'SYDNE' => /* I18N: Location of an LDS church temple */ I18N::translate('Sydney, Australia'),
+ 'TAIPE' => /* I18N: Location of an LDS church temple */ I18N::translate('Taipei, Taiwan'),
+ 'TAMPI' => /* I18N: Location of an LDS church temple */ I18N::translate('Tampico, Mexico'),
+ 'TEGUC' => /* I18N: Location of an LDS church temple */ I18N::translate('Tegucigalpa, Honduras'),
+ 'TGUTI' => /* I18N: Location of an LDS church temple */ I18N::translate('Tuxtla Gutierrez, Mexico'),
+ 'TIJUA' => /* I18N: Location of an LDS church temple */ I18N::translate('Tijuana, Mexico'),
+ 'TOKYO' => /* I18N: Location of an LDS church temple */ I18N::translate('Tokyo, Japan'),
+ 'TORNO' => /* I18N: Location of an LDS church temple */ I18N::translate('Toronto, Ontario, Canada'),
+ 'TRUJI' => /* I18N: Location of an LDS church temple */ I18N::translate('Trujillo, Peru'),
+ 'TWINF' => /* I18N: Location of an LDS church temple */ I18N::translate('Twin Falls, Idaho, United States'),
+ 'VANCO' => /* I18N: Location of an LDS church temple */ I18N::translate('Vancouver, British Columbia, Canada'),
+ 'VERAC' => /* I18N: Location of an LDS church temple */ I18N::translate('Veracruz, Mexico'),
+ 'VERNA' => /* I18N: Location of an LDS church temple */ I18N::translate('Vernal, Utah, United States'),
+ 'VILLA' => /* I18N: Location of an LDS church temple */ I18N::translate('Villa Hermosa, Mexico'),
+ 'WASHI' => /* I18N: Location of an LDS church temple */ I18N::translate('Washington, District of Columbia, United States'),
+ 'WINTE' => /* I18N: Location of an LDS church temple */ I18N::translate('Winter Quarters, Nebraska, United States'),
+ ];
+
+ uasort($values, [I18N::class, 'strcasecmp']);
+ $values = ['' => I18N::translate('No temple - living ordinance')] + $values;
+
+ return $values;
+ }
+}
diff --git a/app/Elements/TextFromSource.php b/app/Elements/TextFromSource.php
new file mode 100644
index 0000000000..3bbd5092ec
--- /dev/null
+++ b/app/Elements/TextFromSource.php
@@ -0,0 +1,76 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\Tree;
+
+/**
+ * TEXT_FROM_SOURCE := {Size=1:248}
+ * <TEXT>
+ * A verbatim copy of any description contained within the source. This indicates notes or text
+ * that are actually contained in the source document, not the submitter's opinion about the
+ * source. This should be, from the evidence point of view, "what the original record keeper
+ * said" as opposed to the researcher's interpretation. The word TEXT, in this case, means from
+ * the text which appeared in the source record including labels.
+ */
+class TextFromSource extends AbstractElement
+{
+ /**
+ * Convert a value to a canonical form.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public function canonical(string $value): string
+ {
+ // Browsers use MS-DOS line endings in multi-line data.
+ return strtr($value, ["\r\n" => "\n", "\r" => "\n"]);
+ }
+
+ /**
+ * An edit control for this data.
+ *
+ * @param string $id
+ * @param string $name
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function edit(string $id, string $name, string $value, Tree $tree): string
+ {
+ return $this->editTextArea($id, $name, $value);
+ }
+
+ /**
+ * Display the value of this type of element.
+ *
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function value(string $value, Tree $tree): string
+ {
+ return Filter::formatText($value, $tree);
+ }
+}
diff --git a/app/Elements/TimeValue.php b/app/Elements/TimeValue.php
new file mode 100644
index 0000000000..f0f25da13f
--- /dev/null
+++ b/app/Elements/TimeValue.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * TIME_VALUE := {Size=1:12}
+ * [ hh:mm:ss.fs ]
+ * The time of a specific event, usually a computer-timed event, where:
+ * hh = hours on a 24-hour clock
+ * mm = minutes
+ * ss = seconds (optional)
+ * fs = decimal fraction of a second (optional)
+ */
+class TimeValue extends AbstractElement
+{
+}
diff --git a/app/Elements/TransmissionDate.php b/app/Elements/TransmissionDate.php
new file mode 100644
index 0000000000..6989d56f1e
--- /dev/null
+++ b/app/Elements/TransmissionDate.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * TRANSMISSION_DATE := {Size=11:12}
+ * <DATE_EXACT>
+ * The date that this transmission was created.
+ */
+class TransmissionDate extends AbstractElement
+{
+}
diff --git a/app/Elements/UnknownElement.php b/app/Elements/UnknownElement.php
new file mode 100644
index 0000000000..dbe1b8c90e
--- /dev/null
+++ b/app/Elements/UnknownElement.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\I18N;
+
+/**
+ * An unrecognised GEDCOM element.
+ */
+class UnknownElement extends AbstractElement
+{
+ /**
+ * Name for this GEDCOM primitive.
+ *
+ * @return string
+ */
+ public function label(): string
+ {
+ $title = I18N::translate('Unrecognized GEDCOM code');
+
+ return '<span class="error" title="' . $title . '">' . parent::label() . '</span>';
+ }
+}
diff --git a/app/Elements/UserReferenceNumber.php b/app/Elements/UserReferenceNumber.php
new file mode 100644
index 0000000000..c0b3012b7d
--- /dev/null
+++ b/app/Elements/UserReferenceNumber.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * USER_REFERENCE_NUMBER := {Size=1:20}
+ * A user-defined number or text that the submitter uses to identify this record. For instance, it
+ * may be a record number within the submitter's automated or manual system, or it may be a page
+ * and position number on a pedigree chart.
+ */
+class UserReferenceNumber extends AbstractElement
+{
+}
diff --git a/app/Elements/UserReferenceType.php b/app/Elements/UserReferenceType.php
new file mode 100644
index 0000000000..f577c7d9cf
--- /dev/null
+++ b/app/Elements/UserReferenceType.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * USER_REFERENCE_TYPE := {Size=1:40}
+ * A user-defined definition of the USER_REFERENCE_NUMBER.
+ */
+class UserReferenceType extends AbstractElement
+{
+ protected const MAX_LENGTH = 40;
+}
diff --git a/app/Elements/VersionNumber.php b/app/Elements/VersionNumber.php
new file mode 100644
index 0000000000..6493188a2f
--- /dev/null
+++ b/app/Elements/VersionNumber.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Tree;
+
+use function e;
+
+/**
+ * VERSION_NUMBER := {Size=1:15}
+ * An identifier that represents the version level assigned to the associated product. It is
+ * defined and changed by the creators of the product.
+ */
+class VersionNumber extends AbstractElement
+{
+ protected const MAX_LENGTH = 15;
+ /**
+ * Display the value of this type of element.
+ *
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function value(string $value, Tree $tree): string
+ {
+ $canonical = $this->canonical($value);
+
+ return '<span dir="ltr">' . e($canonical) . '</span>';
+ }
+}
diff --git a/app/Elements/WebtreesUser.php b/app/Elements/WebtreesUser.php
new file mode 100644
index 0000000000..bef049e038
--- /dev/null
+++ b/app/Elements/WebtreesUser.php
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Auth;
+use Fisharebest\Webtrees\Tree;
+
+/**
+ * The webtrees user for a CHAN or _TODO element
+ */
+class WebtreesUser extends AbstractElement
+{
+ /**
+ * Create a default value for this element.
+ *
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function default(Tree $tree): string
+ {
+ return Auth::user()->userName();
+ }
+}
diff --git a/app/Elements/WhereWithinSource.php b/app/Elements/WhereWithinSource.php
new file mode 100644
index 0000000000..fb2d380e87
--- /dev/null
+++ b/app/Elements/WhereWithinSource.php
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Http\RequestHandlers\AutoCompleteCitation;
+use Fisharebest\Webtrees\Tree;
+
+use function e;
+use function route;
+
+/**
+ * WHERE_WITHIN_SOURCE := {Size=1:248}
+ * Specific location with in the information referenced. For a published work, this could include
+ * the volume of a multi-volume work and the page number(s). For a periodical, it could include
+ * volume, issue, and page numbers. For a newspaper, it could include a column number and page
+ * number. For an unpublished source or microfilmed works, this could be a film or sheet number,
+ * page number, frame number, etc. A census record might have an enumerating district, page number,
+ * line number, dwelling number, and family number. The data in this field should be in the form of
+ * a label and value pair, such as Label1: value, Label2: value, with each pair being separated by
+ * a comma. For example, Film: 1234567, Frame: 344, Line: 28.
+ */
+class WhereWithinSource extends AbstractElement
+{
+ /**
+ * An edit control for this data.
+ *
+ * @param string $id
+ * @param string $name
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function edit(string $id, string $name, string $value, Tree $tree): string
+ {
+ return '<input data-autocomplete-url="' . e(route(AutoCompleteCitation::class, ['tree' => $tree->name()])) . '" data-autocomplete-extra="SOUR" autocomplete="off" class="form-control" type="text" id="' . e($id) . '" name="' . e($name) . '" value="' . e($value) . '">';
+ }
+}
diff --git a/app/Elements/Will.php b/app/Elements/Will.php
new file mode 100644
index 0000000000..f6e2fd5e26
--- /dev/null
+++ b/app/Elements/Will.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+/**
+ * Will
+ */
+class Will extends AbstractElement
+{
+ protected const SUBTAGS = [
+ 'DATE' => '0:1',
+ 'PLAC' => '0:1',
+ 'NOTE' => '0:M',
+ 'OBJE' => '0:M',
+ 'SOUR' => '0:M',
+ 'RESN' => '0:1',
+ ];
+}
diff --git a/app/Elements/XrefFamily.php b/app/Elements/XrefFamily.php
new file mode 100644
index 0000000000..26d8cb66a6
--- /dev/null
+++ b/app/Elements/XrefFamily.php
@@ -0,0 +1,67 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Registry;
+use Fisharebest\Webtrees\Tree;
+
+use function trim;
+use function view;
+
+/**
+ * XREF:FAM := {Size=1:22}
+ * A pointer to, or a cross-reference identifier of, a family record.
+ */
+class XrefFamily extends AbstractXrefElement
+{
+ /**
+ * An edit control for this data.
+ *
+ * @param string $id
+ * @param string $name
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function edit(string $id, string $name, string $value, Tree $tree): string
+ {
+ return view('components/select-family', [
+ 'id' => $id,
+ 'name' => $name,
+ 'family' => Registry::familyFactory()->make(trim($value, '@'), $tree),
+ 'tree' => $tree,
+ 'at' => '@',
+ ]);
+ }
+
+ /**
+ * Display the value of this type of element.
+ *
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function value(string $value, Tree $tree): string
+ {
+ return $this->valueXrefLink($value, $tree, Registry::familyFactory());
+ }
+}
diff --git a/app/Elements/XrefIndividual.php b/app/Elements/XrefIndividual.php
new file mode 100644
index 0000000000..a52aca06be
--- /dev/null
+++ b/app/Elements/XrefIndividual.php
@@ -0,0 +1,66 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Registry;
+use Fisharebest\Webtrees\Tree;
+
+use function view;
+
+/**
+ * XREF:INDI := {Size=1:22}
+ * A pointer to, or a cross-reference identifier of, an individual record.
+ */
+class XrefIndividual extends AbstractXrefElement
+{
+ /**
+ * An edit control for this data.
+ *
+ * @param string $id
+ * @param string $name
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function edit(string $id, string $name, string $value, Tree $tree): string
+ {
+ return view('components/select-individual', [
+ 'id' => $id,
+ 'name' => $name,
+ 'individual' => Registry::individualFactory()->make(trim($value, '@'), $tree),
+ 'tree' => $tree,
+ 'at' => '@',
+ ]);
+ }
+
+ /**
+ * Display the value of this type of element.
+ *
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function value(string $value, Tree $tree): string
+ {
+ return $this->valueXrefLink($value, $tree, Registry::individualFactory());
+ }
+}
diff --git a/app/Elements/XrefLocation.php b/app/Elements/XrefLocation.php
new file mode 100644
index 0000000000..50bb654a0f
--- /dev/null
+++ b/app/Elements/XrefLocation.php
@@ -0,0 +1,81 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Http\RequestHandlers\CreateLocationModal;
+use Fisharebest\Webtrees\I18N;
+use Fisharebest\Webtrees\Registry;
+use Fisharebest\Webtrees\Tree;
+
+use function e;
+use function route;
+use function trim;
+use function view;
+
+/**
+ * XREF:_LOC := {Size=1:22}
+ * A pointer to, or a cross-reference identifier of, a location record.
+ */
+class XrefLocation extends AbstractXrefElement
+{
+ /**
+ * An edit control for this data.
+ *
+ * @param string $id
+ * @param string $name
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function edit(string $id, string $name, string $value, Tree $tree): string
+ {
+ $select = view('components/select-location', [
+ 'id' => $id,
+ 'name' => $name,
+ 'location' => Registry::locationFactory()->make(trim($value, '@'), $tree),
+ 'tree' => $tree,
+ 'at' => '@',
+ ]);
+
+ return
+ '<div class="input-group">' .
+ '<div class="input-group-prepend">' .
+ '<button class="btn btn-secondary" type="button" data-toggle="modal" data-backdrop="static" data-target="#wt-ajax-modal" data-href="' . e(route(CreateLocationModal::class, ['tree' => $tree->name()])) . '" data-select-id="' . $id . '" title="' . I18N::translate('Create a location') . '">' .
+ view('icons/add') .
+ '</button>' .
+ '</div>' .
+ $select .
+ '</div>';
+ }
+
+ /**
+ * Display the value of this type of element.
+ *
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function value(string $value, Tree $tree): string
+ {
+ return $this->valueXrefLink($value, $tree, Registry::locationFactory());
+ }
+}
diff --git a/app/Elements/XrefMedia.php b/app/Elements/XrefMedia.php
new file mode 100644
index 0000000000..532b3daa47
--- /dev/null
+++ b/app/Elements/XrefMedia.php
@@ -0,0 +1,81 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Http\RequestHandlers\CreateMediaObjectModal;
+use Fisharebest\Webtrees\I18N;
+use Fisharebest\Webtrees\Registry;
+use Fisharebest\Webtrees\Tree;
+
+use function e;
+use function route;
+use function trim;
+use function view;
+
+/**
+ * XREF:OBJE := {Size=1:22}
+ * A pointer to, or a cross-reference identifier of, a multimedia object.
+ */
+class XrefMedia extends AbstractXrefElement
+{
+ /**
+ * An edit control for this data.
+ *
+ * @param string $id
+ * @param string $name
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function edit(string $id, string $name, string $value, Tree $tree): string
+ {
+ $select = view('components/select-media', [
+ 'id' => $id,
+ 'name' => $name,
+ 'media' => Registry::mediaFactory()->make(trim($value, '@'), $tree),
+ 'tree' => $tree,
+ 'at' => '@',
+ ]);
+
+ return
+ '<div class="input-group">' .
+ '<div class="input-group-prepend">' .
+ '<button class="btn btn-secondary" type="button" data-toggle="modal" data-backdrop="static" data-target="#wt-ajax-modal" data-href="' . e(route(CreateMediaObjectModal::class, ['tree' => $tree->name()])) . '" data-select-id="' . $id . '" title="' . I18N::translate('Create a media object') . '">' .
+ view('icons/add') .
+ '</button>' .
+ '</div>' .
+ $select .
+ '</div>';
+ }
+
+ /**
+ * Display the value of this type of element.
+ *
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function value(string $value, Tree $tree): string
+ {
+ return $this->valueXrefLink($value, $tree, Registry::mediaFactory());
+ }
+}
diff --git a/app/Elements/XrefNote.php b/app/Elements/XrefNote.php
new file mode 100644
index 0000000000..38d148abb2
--- /dev/null
+++ b/app/Elements/XrefNote.php
@@ -0,0 +1,81 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Http\RequestHandlers\CreateNoteModal;
+use Fisharebest\Webtrees\I18N;
+use Fisharebest\Webtrees\Registry;
+use Fisharebest\Webtrees\Tree;
+
+use function e;
+use function route;
+use function trim;
+use function view;
+
+/**
+ * XREF:NOTE := {Size=1:22}
+ * A pointer to, or a cross-reference identifier of, a note record.
+ */
+class XrefNote extends AbstractXrefElement
+{
+ /**
+ * An edit control for this data.
+ *
+ * @param string $id
+ * @param string $name
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function edit(string $id, string $name, string $value, Tree $tree): string
+ {
+ $select = view('components/select-note', [
+ 'id' => $id,
+ 'name' => $name,
+ 'note' => Registry::noteFactory()->make(trim($value, '@'), $tree),
+ 'tree' => $tree,
+ 'at' => '@',
+ ]);
+
+ return
+ '<div class="input-group">' .
+ '<div class="input-group-prepend">' .
+ '<button class="btn btn-secondary" type="button" data-toggle="modal" data-backdrop="static" data-target="#wt-ajax-modal" data-href="' . e(route(CreateNoteModal::class, ['tree' => $tree->name()])) . '" data-select-id="' . $id . '" title="' . I18N::translate('Create a shared note') . '">' .
+ view('icons/add') .
+ '</button>' .
+ '</div>' .
+ $select .
+ '</div>';
+ }
+
+ /**
+ * Display the value of this type of element.
+ *
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function value(string $value, Tree $tree): string
+ {
+ return $this->valueXrefLink($value, $tree, Registry::noteFactory());
+ }
+}
diff --git a/app/Elements/XrefRepository.php b/app/Elements/XrefRepository.php
new file mode 100644
index 0000000000..99d33693c9
--- /dev/null
+++ b/app/Elements/XrefRepository.php
@@ -0,0 +1,81 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Http\RequestHandlers\CreateRepositoryModal;
+use Fisharebest\Webtrees\I18N;
+use Fisharebest\Webtrees\Registry;
+use Fisharebest\Webtrees\Tree;
+
+use function e;
+use function route;
+use function trim;
+use function view;
+
+/**
+ * XREF:REPO := {Size=1:22}
+ * A pointer to, or a cross-reference identifier of, a repository record.
+ */
+class XrefRepository extends AbstractXrefElement
+{
+ /**
+ * An edit control for this data.
+ *
+ * @param string $id
+ * @param string $name
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function edit(string $id, string $name, string $value, Tree $tree): string
+ {
+ $select = view('components/select-repository', [
+ 'id' => $id,
+ 'name' => $name,
+ 'repository' => Registry::repositoryFactory()->make(trim($value, '@'), $tree),
+ 'tree' => $tree,
+ 'at' => '@',
+ ]);
+
+ return
+ '<div class="input-group">' .
+ '<div class="input-group-prepend">' .
+ '<button class="btn btn-secondary" type="button" data-toggle="modal" data-backdrop="static" data-target="#wt-ajax-modal" data-href="' . e(route(CreateRepositoryModal::class, ['tree' => $tree->name()])) . '" data-select-id="' . $id . '" title="' . I18N::translate('Create a repository') . '">' .
+ view('icons/add') .
+ '</button>' .
+ '</div>' .
+ $select .
+ '</div>';
+ }
+
+ /**
+ * Display the value of this type of element.
+ *
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function value(string $value, Tree $tree): string
+ {
+ return $this->valueXrefLink($value, $tree, Registry::repositoryFactory());
+ }
+}
diff --git a/app/Elements/XrefSource.php b/app/Elements/XrefSource.php
new file mode 100644
index 0000000000..8cf861eb32
--- /dev/null
+++ b/app/Elements/XrefSource.php
@@ -0,0 +1,110 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Http\RequestHandlers\CreateSourceModal;
+use Fisharebest\Webtrees\I18N;
+use Fisharebest\Webtrees\Registry;
+use Fisharebest\Webtrees\Tree;
+
+use function e;
+use function route;
+use function trim;
+use function view;
+
+/**
+ * XREF:SOUR := {Size=1:22}
+ * A pointer to, or a cross-reference identifier of, a SOURce record.
+ */
+class XrefSource extends AbstractXrefElement
+{
+ protected const SUBTAGS = [
+ 'PAGE' => '0:1',
+ 'EVEN' => '0:1',
+ 'DATA' => '0:1',
+ 'OBJE' => '0:M',
+ 'NOTE' => '0:M',
+ 'QUAY' => '0:1',
+ ];
+
+ protected const ABRIDGED_SUBTAGS = [
+ 'PAGE' => '0:1',
+ 'DATA' => '0:1',
+ 'OBJE' => '0:M',
+ ];
+
+ /**
+ * An edit control for this data.
+ *
+ * @param string $id
+ * @param string $name
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function edit(string $id, string $name, string $value, Tree $tree): string
+ {
+ $select = view('components/select-source', [
+ 'id' => $id,
+ 'name' => $name,
+ 'source' => Registry::sourceFactory()->make(trim($value, '@'), $tree),
+ 'tree' => $tree,
+ 'at' => '@',
+ ]);
+
+ return
+ '<div class="input-group">' .
+ '<div class="input-group-prepend">' .
+ '<button class="btn btn-secondary" type="button" data-toggle="modal" data-backdrop="static" data-target="#wt-ajax-modal" data-href="' . e(route(CreateSourceModal::class, ['tree' => $tree->name()])) . '" data-select-id="' . $id . '" title="' . I18N::translate('Create a source') . '">' .
+ view('icons/add') .
+ '</button>' .
+ '</div>' .
+ $select .
+ '</div>';
+ }
+
+ /**
+ * @param Tree $tree
+ *
+ * @return array<string,string>
+ */
+ public function subtags(Tree $tree): array
+ {
+ if ($tree->getPreference('FULL_SOURCES') === '1') {
+ return static::SUBTAGS;
+ }
+
+ return static::ABRIDGED_SUBTAGS;
+ }
+
+ /**
+ * Display the value of this type of element.
+ *
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function value(string $value, Tree $tree): string
+ {
+ return $this->valueXrefLink($value, $tree, Registry::sourceFactory());
+ }
+}
diff --git a/app/Elements/XrefSubmission.php b/app/Elements/XrefSubmission.php
new file mode 100644
index 0000000000..376911c1f8
--- /dev/null
+++ b/app/Elements/XrefSubmission.php
@@ -0,0 +1,81 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Http\RequestHandlers\CreateSubmissionModal;
+use Fisharebest\Webtrees\I18N;
+use Fisharebest\Webtrees\Registry;
+use Fisharebest\Webtrees\Tree;
+
+use function e;
+use function route;
+use function trim;
+use function view;
+
+/**
+ * XREF:SUBN := {Size=1:22}
+ * A pointer to, or a cross-reference identifier of, a SUBmissioN record.
+ */
+class XrefSubmission extends AbstractXrefElement
+{
+ /**
+ * An edit control for this data.
+ *
+ * @param string $id
+ * @param string $name
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function edit(string $id, string $name, string $value, Tree $tree): string
+ {
+ $select = view('components/select-submission', [
+ 'id' => $id,
+ 'name' => $name,
+ 'submission' => Registry::submissionFactory()->make(trim($value, '@'), $tree),
+ 'tree' => $tree,
+ 'at' => '@',
+ ]);
+
+ return
+ '<div class="input-group">' .
+ '<div class="input-group-prepend">' .
+ '<button class="btn btn-secondary" type="button" data-toggle="modal" data-backdrop="static" data-target="#wt-ajax-modal" data-href="' . e(route(CreateSubmissionModal::class, ['tree' => $tree->name()])) . '" data-select-id="' . $id . '" title="' . I18N::translate('Create a submission') . '">' .
+ view('icons/add') .
+ '</button>' .
+ '</div>' .
+ $select .
+ '</div>';
+ }
+
+ /**
+ * Display the value of this type of element.
+ *
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function value(string $value, Tree $tree): string
+ {
+ return $this->valueXrefLink($value, $tree, Registry::submissionFactory());
+ }
+}
diff --git a/app/Elements/XrefSubmitter.php b/app/Elements/XrefSubmitter.php
new file mode 100644
index 0000000000..a77c8c1afc
--- /dev/null
+++ b/app/Elements/XrefSubmitter.php
@@ -0,0 +1,81 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Elements;
+
+use Fisharebest\Webtrees\Http\RequestHandlers\CreateSubmitterModal;
+use Fisharebest\Webtrees\I18N;
+use Fisharebest\Webtrees\Registry;
+use Fisharebest\Webtrees\Tree;
+
+use function e;
+use function route;
+use function trim;
+use function view;
+
+/**
+ * XREF:SUBM := {Size=1:22}
+ * A pointer to, or a cross-reference identifier of, a SUBMitter record.
+ */
+class XrefSubmitter extends AbstractXrefElement
+{
+ /**
+ * An edit control for this data.
+ *
+ * @param string $id
+ * @param string $name
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function edit(string $id, string $name, string $value, Tree $tree): string
+ {
+ $select = view('components/select-submitter', [
+ 'id' => $id,
+ 'name' => $name,
+ 'submitter' => Registry::submitterFactory()->make(trim($value, '@'), $tree),
+ 'tree' => $tree,
+ 'at' => '@',
+ ]);
+
+ return
+ '<div class="input-group">' .
+ '<div class="input-group-prepend">' .
+ '<button class="btn btn-secondary" type="button" data-toggle="modal" data-backdrop="static" data-target="#wt-ajax-modal" data-href="' . e(route(CreateSubmitterModal::class, ['tree' => $tree->name()])) . '" data-select-id="' . $id . '" title="' . I18N::translate('Create a submitter') . '">' .
+ view('icons/add') .
+ '</button>' .
+ '</div>' .
+ $select .
+ '</div>';
+ }
+
+ /**
+ * Display the value of this type of element.
+ *
+ * @param string $value
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public function value(string $value, Tree $tree): string
+ {
+ return $this->valueXrefLink($value, $tree, Registry::submitterFactory());
+ }
+}
diff --git a/app/Factories/ElementFactory.php b/app/Factories/ElementFactory.php
new file mode 100644
index 0000000000..0f42b0511f
--- /dev/null
+++ b/app/Factories/ElementFactory.php
@@ -0,0 +1,772 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Factories;
+
+use Fisharebest\Webtrees\Contracts\ElementFactoryInterface;
+use Fisharebest\Webtrees\Contracts\ElementInterface;
+use Fisharebest\Webtrees\Elements\AddressCity;
+use Fisharebest\Webtrees\Elements\AddressCountry;
+use Fisharebest\Webtrees\Elements\AddressEmail;
+use Fisharebest\Webtrees\Elements\AddressFax;
+use Fisharebest\Webtrees\Elements\AddressLine;
+use Fisharebest\Webtrees\Elements\AddressLine1;
+use Fisharebest\Webtrees\Elements\AddressLine2;
+use Fisharebest\Webtrees\Elements\AddressLine3;
+use Fisharebest\Webtrees\Elements\AddressPostalCode;
+use Fisharebest\Webtrees\Elements\AddressState;
+use Fisharebest\Webtrees\Elements\AddressWebPage;
+use Fisharebest\Webtrees\Elements\AdoptedByWhichParent;
+use Fisharebest\Webtrees\Elements\Adoption;
+use Fisharebest\Webtrees\Elements\AdultChristening;
+use Fisharebest\Webtrees\Elements\AgeAtEvent;
+use Fisharebest\Webtrees\Elements\AncestralFileNumber;
+use Fisharebest\Webtrees\Elements\Annulment;
+use Fisharebest\Webtrees\Elements\ApprovedSystemId;
+use Fisharebest\Webtrees\Elements\AttributeDescriptor;
+use Fisharebest\Webtrees\Elements\AutomatedRecordId;
+use Fisharebest\Webtrees\Elements\Baptism;
+use Fisharebest\Webtrees\Elements\BasMitzvah;
+use Fisharebest\Webtrees\Elements\Birth;
+use Fisharebest\Webtrees\Elements\Blessing;
+use Fisharebest\Webtrees\Elements\Burial;
+use Fisharebest\Webtrees\Elements\CasteName;
+use Fisharebest\Webtrees\Elements\CauseOfEvent;
+use Fisharebest\Webtrees\Elements\Census;
+use Fisharebest\Webtrees\Elements\CertaintyAssessment;
+use Fisharebest\Webtrees\Elements\Change;
+use Fisharebest\Webtrees\Elements\ChangeDate;
+use Fisharebest\Webtrees\Elements\CharacterSet;
+use Fisharebest\Webtrees\Elements\ChildLinkageStatus;
+use Fisharebest\Webtrees\Elements\Christening;
+use Fisharebest\Webtrees\Elements\Confirmation;
+use Fisharebest\Webtrees\Elements\ContentDescription;
+use Fisharebest\Webtrees\Elements\CopyrightFile;
+use Fisharebest\Webtrees\Elements\CopyrightSourceData;
+use Fisharebest\Webtrees\Elements\CountOfChildren;
+use Fisharebest\Webtrees\Elements\CountOfMarriages;
+use Fisharebest\Webtrees\Elements\Cremation;
+use Fisharebest\Webtrees\Elements\DateLdsOrd;
+use Fisharebest\Webtrees\Elements\DateValue;
+use Fisharebest\Webtrees\Elements\Death;
+use Fisharebest\Webtrees\Elements\DescriptiveTitle;
+use Fisharebest\Webtrees\Elements\Divorce;
+use Fisharebest\Webtrees\Elements\DivorceFiled;
+use Fisharebest\Webtrees\Elements\Emigration;
+use Fisharebest\Webtrees\Elements\EmptyElement;
+use Fisharebest\Webtrees\Elements\Engagement;
+use Fisharebest\Webtrees\Elements\EntryRecordingDate;
+use Fisharebest\Webtrees\Elements\EventAttributeType;
+use Fisharebest\Webtrees\Elements\EventDescriptor;
+use Fisharebest\Webtrees\Elements\EventOrFactClassification;
+use Fisharebest\Webtrees\Elements\EventsRecorded;
+use Fisharebest\Webtrees\Elements\EventTypeCitedFrom;
+use Fisharebest\Webtrees\Elements\FileName;
+use Fisharebest\Webtrees\Elements\FirstCommunion;
+use Fisharebest\Webtrees\Elements\Form;
+use Fisharebest\Webtrees\Elements\Gedcom;
+use Fisharebest\Webtrees\Elements\GenerationsOfAncestors;
+use Fisharebest\Webtrees\Elements\GenerationsOfDescendants;
+use Fisharebest\Webtrees\Elements\Graduation;
+use Fisharebest\Webtrees\Elements\Immigration;
+use Fisharebest\Webtrees\Elements\LanguageId;
+use Fisharebest\Webtrees\Elements\LdsBaptism;
+use Fisharebest\Webtrees\Elements\LdsBaptismDateStatus;
+use Fisharebest\Webtrees\Elements\LdsChildSealing;
+use Fisharebest\Webtrees\Elements\LdsChildSealingDateStatus;
+use Fisharebest\Webtrees\Elements\LdsConfirmation;
+use Fisharebest\Webtrees\Elements\LdsEndowment;
+use Fisharebest\Webtrees\Elements\LdsEndowmentDateStatus;
+use Fisharebest\Webtrees\Elements\LdsSpouseSealing;
+use Fisharebest\Webtrees\Elements\LdsSpouseSealingDateStatus;
+use Fisharebest\Webtrees\Elements\Marriage;
+use Fisharebest\Webtrees\Elements\MarriageBanns;
+use Fisharebest\Webtrees\Elements\MarriageContract;
+use Fisharebest\Webtrees\Elements\MarriageLicence;
+use Fisharebest\Webtrees\Elements\MarriageSettlement;
+use Fisharebest\Webtrees\Elements\MarriageType;
+use Fisharebest\Webtrees\Elements\MediaRecord;
+use Fisharebest\Webtrees\Elements\MultimediaFileReference;
+use Fisharebest\Webtrees\Elements\MultimediaFormat;
+use Fisharebest\Webtrees\Elements\NameOfBusiness;
+use Fisharebest\Webtrees\Elements\NameOfFamilyFile;
+use Fisharebest\Webtrees\Elements\NameOfProduct;
+use Fisharebest\Webtrees\Elements\NameOfRepository;
+use Fisharebest\Webtrees\Elements\NameOfSourceData;
+use Fisharebest\Webtrees\Elements\NamePersonal;
+use Fisharebest\Webtrees\Elements\NamePhoneticVariation;
+use Fisharebest\Webtrees\Elements\NamePieceGiven;
+use Fisharebest\Webtrees\Elements\NamePieceNickname;
+use Fisharebest\Webtrees\Elements\NamePiecePrefix;
+use Fisharebest\Webtrees\Elements\NamePieceSuffix;
+use Fisharebest\Webtrees\Elements\NamePieceSurname;
+use Fisharebest\Webtrees\Elements\NamePieceSurnamePrefix;
+use Fisharebest\Webtrees\Elements\NameRomanizedVariation;
+use Fisharebest\Webtrees\Elements\NameType;
+use Fisharebest\Webtrees\Elements\NationalIdNumber;
+use Fisharebest\Webtrees\Elements\NationOrTribalOrigin;
+use Fisharebest\Webtrees\Elements\Naturalization;
+use Fisharebest\Webtrees\Elements\NobilityTypeTitle;
+use Fisharebest\Webtrees\Elements\NoteRecord;
+use Fisharebest\Webtrees\Elements\NoteStructure;
+use Fisharebest\Webtrees\Elements\Occupation;
+use Fisharebest\Webtrees\Elements\OrdinanceProcessFlag;
+use Fisharebest\Webtrees\Elements\Ordination;
+use Fisharebest\Webtrees\Elements\PafUid;
+use Fisharebest\Webtrees\Elements\PedigreeLinkageType;
+use Fisharebest\Webtrees\Elements\PermanentRecordFileNumber;
+use Fisharebest\Webtrees\Elements\PhoneNumber;
+use Fisharebest\Webtrees\Elements\PhoneticType;
+use Fisharebest\Webtrees\Elements\PhysicalDescription;
+use Fisharebest\Webtrees\Elements\PlaceHierarchy;
+use Fisharebest\Webtrees\Elements\PlaceLatitude;
+use Fisharebest\Webtrees\Elements\PlaceLivingOrdinance;
+use Fisharebest\Webtrees\Elements\PlaceLongtitude;
+use Fisharebest\Webtrees\Elements\PlaceName;
+use Fisharebest\Webtrees\Elements\PlacePhoneticVariation;
+use Fisharebest\Webtrees\Elements\PlaceRomanizedVariation;
+use Fisharebest\Webtrees\Elements\Possessions;
+use Fisharebest\Webtrees\Elements\Probate;
+use Fisharebest\Webtrees\Elements\PublicationDate;
+use Fisharebest\Webtrees\Elements\ReceivingSystemName;
+use Fisharebest\Webtrees\Elements\RelationIsDescriptor;
+use Fisharebest\Webtrees\Elements\ReligiousAffiliation;
+use Fisharebest\Webtrees\Elements\RepositoryRecord;
+use Fisharebest\Webtrees\Elements\Residence;
+use Fisharebest\Webtrees\Elements\ResponsibleAgency;
+use Fisharebest\Webtrees\Elements\RestrictionNotice;
+use Fisharebest\Webtrees\Elements\Retirement;
+use Fisharebest\Webtrees\Elements\RoleInEvent;
+use Fisharebest\Webtrees\Elements\RomanizedType;
+use Fisharebest\Webtrees\Elements\ScholasticAchievement;
+use Fisharebest\Webtrees\Elements\SexValue;
+use Fisharebest\Webtrees\Elements\SocialSecurityNumber;
+use Fisharebest\Webtrees\Elements\SourceCallNumber;
+use Fisharebest\Webtrees\Elements\SourceData;
+use Fisharebest\Webtrees\Elements\SourceFiledByEntry;
+use Fisharebest\Webtrees\Elements\SourceJurisdictionPlace;
+use Fisharebest\Webtrees\Elements\SourceMediaType;
+use Fisharebest\Webtrees\Elements\SourceOriginator;
+use Fisharebest\Webtrees\Elements\SourcePublicationFacts;
+use Fisharebest\Webtrees\Elements\SourceRecord;
+use Fisharebest\Webtrees\Elements\SubmissionRecord;
+use Fisharebest\Webtrees\Elements\SubmitterName;
+use Fisharebest\Webtrees\Elements\SubmitterRecord;
+use Fisharebest\Webtrees\Elements\SubmitterRegisteredRfn;
+use Fisharebest\Webtrees\Elements\SubmitterText;
+use Fisharebest\Webtrees\Elements\TempleCode;
+use Fisharebest\Webtrees\Elements\TextFromSource;
+use Fisharebest\Webtrees\Elements\TimeValue;
+use Fisharebest\Webtrees\Elements\TransmissionDate;
+use Fisharebest\Webtrees\Elements\UnknownElement;
+use Fisharebest\Webtrees\Elements\UserReferenceNumber;
+use Fisharebest\Webtrees\Elements\UserReferenceType;
+use Fisharebest\Webtrees\Elements\VersionNumber;
+use Fisharebest\Webtrees\Elements\WebtreesUser;
+use Fisharebest\Webtrees\Elements\WhereWithinSource;
+use Fisharebest\Webtrees\Elements\Will;
+use Fisharebest\Webtrees\Elements\XrefFamily;
+use Fisharebest\Webtrees\Elements\XrefIndividual;
+use Fisharebest\Webtrees\Elements\XrefMedia;
+use Fisharebest\Webtrees\Elements\XrefRepository;
+use Fisharebest\Webtrees\Elements\XrefSource;
+use Fisharebest\Webtrees\Elements\XrefSubmission;
+use Fisharebest\Webtrees\Elements\XrefSubmitter;
+use Fisharebest\Webtrees\I18N;
+
+use function preg_match;
+use function strpos;
+
+/**
+ * Make a GEDCOM element.
+ */
+class ElementFactory implements ElementFactoryInterface
+{
+ /** @var null|array<string,ElementInterface> */
+ private $elements;
+
+ /**
+ * Create a GEDCOM element that corresponds to a GEDCOM tag.
+ * Finds the correct element for all valid tags.
+ * Finds a likely element for custom tags.
+ *
+ * @param string $tag - Colon delimited hierarchy, e.g. 'INDI:BIRT:PLAC'
+ *
+ * @return ElementInterface
+ */
+ public function make(string $tag): ElementInterface
+ {
+ return $this->elements()[$tag] ?? $this->findElementByWildcard($tag) ?? new UnknownElement($tag);
+ }
+
+ /**
+ * @param string $tag
+ *
+ * @return ElementInterface|null
+ */
+ private function findElementByWildcard(string $tag): ?ElementInterface
+ {
+ foreach ($this->elements() as $tags => $element) {
+ if (strpos($tags, '*') !== false) {
+ $regex = '/^' . strtr($tags, ['*' => '[^:]+']) . '$/';
+
+ if (preg_match($regex, $tag)) {
+ return $element;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Register more elements.
+ *
+ * @param array<string,ElementInterface> $elements
+ */
+ public function register(array $elements): void
+ {
+ $this->elements = array_merge($this->elements(), $elements);
+ }
+
+ /**
+ * Association between GEDCOM tags and GEDCOM elements.
+ * We can't initialise this in the constructor, as the I18N package isn't available then.
+ *
+ * @return array<string,ElementInterface>
+ */
+ private function elements(): array
+ {
+ if ($this->elements === null) {
+ // Custom tags are indicated with ***
+ $this->elements = [
+ 'FAM' => new EmptyElement(I18N::translate('Family')),
+ 'FAM:*:ADDR' => new AddressLine(I18N::translate('Address')),
+ 'FAM:*:ADDR:ADR1' => new AddressLine1(I18N::translate('Address line 1')),
+ 'FAM:*:ADDR:ADR2' => new AddressLine2(I18N::translate('Address line 2')),
+ 'FAM:*:ADDR:ADR3' => new AddressLine3(I18N::translate('Address line 3')),
+ 'FAM:*:ADDR:CITY' => new AddressCity(I18N::translate('City')),
+ 'FAM:*:ADDR:CTRY' => new AddressCountry(I18N::translate('Country')),
+ 'FAM:*:ADDR:POST' => new AddressPostalCode(I18N::translate('Postal code')),
+ 'FAM:*:ADDR:STAE' => new AddressState(I18N::translate('State')),
+ 'FAM:*:AGNC' => new ResponsibleAgency(I18N::translate('Agency')),
+ 'FAM:*:CAUS' => new CauseOfEvent(I18N::translate('Cause')),
+ 'FAM:*:DATE' => new DateValue(I18N::translate('Date')),
+ 'FAM:*:EMAIL' => new AddressEmail(I18N::translate('Email address')),
+ 'FAM:*:FAX' => new AddressFax(I18N::translate('Fax')),
+ 'FAM:*:HUSB' => new EmptyElement(I18N::translate('Husband'), ['AGE' => '0:1']),
+ 'FAM:*:HUSB:AGE' => new AgeAtEvent(I18N::translate('Husband’s age')),
+ 'FAM:*:NOTE' => new NoteStructure(I18N::translate('Note')),
+ 'FAM:*:OBJE' => new XrefMedia(I18N::translate('Media object')),
+ 'FAM:*:PHON' => new PhoneNumber(I18N::translate('Phone')),
+ 'FAM:*:PLAC' => new PlaceName(I18N::translate('Place')),
+ 'FAM:*:PLAC:FONE' => new PlacePhoneticVariation(I18N::translate('Phonetic place')),
+ 'FAM:*:PLAC:FONE:TYPE' => new PhoneticType(I18N::translate('Type')),
+ 'FAM:*:PLAC:FORM' => new PlaceHierarchy(I18N::translate('Format')),
+ 'FAM:*:PLAC:MAP' => new EmptyElement(I18N::translate('Coordinates')),
+ 'FAM:*:PLAC:MAP:LATI' => new PlaceLatitude(I18N::translate('Latitude')),
+ 'FAM:*:PLAC:MAP:LONG' => new PlaceLongtitude(I18N::translate('Longitude')),
+ 'FAM:*:PLAC:NOTE' => new NoteStructure(I18N::translate('Note')),
+ 'FAM:*:PLAC:ROMN' => new PlaceRomanizedVariation(I18N::translate('Romanized place')),
+ 'FAM:*:PLAC:ROMN:TYPE' => new RomanizedType(I18N::translate('Type')),
+ 'FAM:*:RELI' => new ReligiousAffiliation(I18N::translate('Religion')),
+ 'FAM:*:RESN' => new RestrictionNotice(I18N::translate('Restriction')),
+ 'FAM:*:SOUR' => new XrefSource(I18N::translate('Source')),
+ 'FAM:*:SOUR:DATA' => new SourceData(I18N::translate('Data')),
+ 'FAM:*:SOUR:DATA:DATE' => new EntryRecordingDate(I18N::translate('Date of entry in original source')),
+ 'FAM:*:SOUR:DATA:TEXT' => new TextFromSource(I18N::translate('Text')),
+ 'FAM:*:SOUR:EVEN' => new EventTypeCitedFrom(I18N::translate('Event')),
+ 'FAM:*:SOUR:EVEN:ROLE' => new RoleInEvent(I18N::translate('Role')),
+ 'FAM:*:SOUR:NOTE' => new NoteStructure(I18N::translate('Note')),
+ 'FAM:*:SOUR:OBJE' => new XrefMedia(I18N::translate('Media object')),
+ 'FAM:*:SOUR:PAGE' => new WhereWithinSource(I18N::translate('Citation details')),
+ 'FAM:*:SOUR:QUAY' => new CertaintyAssessment(I18N::translate('Quality of data')),
+ 'FAM:*:TYPE' => new EventOrFactClassification(I18N::translate('Type')),
+ 'FAM:*:WIFE' => new EmptyElement(I18N::translate('Wife'), ['AGE' => '0:1']),
+ 'FAM:*:WIFE:AGE' => new AgeAtEvent(I18N::translate('Wife’s age')),
+ 'FAM:*:WWW' => new AddressWebPage(I18N::translate('URL')),
+ 'FAM:*:_ASSO' => new XrefIndividual(I18N::translate('Associate')), // ***
+ 'FAM:*:_ASSO:RELA' => new RelationIsDescriptor(I18N::translate('Relationship')), // ***
+ 'FAM:ANUL' => new Annulment(I18N::translate('Annulment')),
+ 'FAM:CENS' => new Census(I18N::translate('Census')),
+ 'FAM:CHAN' => new Change(I18N::translate('Last change')),
+ 'FAM:CHAN:DATE' => new ChangeDate(I18N::translate('Date of last change')),
+ 'FAM:CHAN:DATE:TIME' => new TimeValue(I18N::translate('Time')),
+ 'FAM:CHAN:_WT_USER' => new WebtreesUser(I18N::translate('Author of last change')), // *** webtrees
+ 'FAM:CHIL' => new XrefIndividual(I18N::translate('Child')),
+ 'FAM:DIV' => new Divorce(I18N::translate('Divorce')),
+ 'FAM:DIVF' => new DivorceFiled(I18N::translate('Divorce filed')),
+ 'FAM:ENGA' => new Engagement(I18N::translate('Engagement')),
+ 'FAM:ENGA:DATE' => new DateValue(I18N::translate('Date of engagement')),
+ 'FAM:ENGA:PLACE' => new PlaceName(I18N::translate('Place of engagement')),
+ 'FAM:EVEN' => new EventDescriptor(I18N::translate('Event')),
+ 'FAM:EVEN:TYPE' => new EventAttributeType(I18N::translate('Type of event')),
+ 'FAM:HUSB' => new XrefIndividual(I18N::translate('Husband')),
+ 'FAM:MARB' => new MarriageBanns(I18N::translate('Marriage banns')),
+ 'FAM:MARB:DATE' => new DateValue(I18N::translate('Date of marriage banns')),
+ 'FAM:MARB:PLAC' => new PlaceName(I18N::translate('Place of marriage banns')),
+ 'FAM:MARC' => new MarriageContract(I18N::translate('Marriage contract')),
+ 'FAM:MARL' => new MarriageLicence(I18N::translate('Marriage license')),
+ 'FAM:MARR' => new Marriage(I18N::translate('Marriage')),
+ 'FAM:MARR:DATE' => new DateValue(I18N::translate('Date of marriage')),
+ 'FAM:MARR:PLAC' => new PlaceName(I18N::translate('Place of marriage')),
+ 'FAM:MARR:TYPE' => new MarriageType(I18N::translate('Type of marriage')),
+ 'FAM:MARS' => new MarriageSettlement(I18N::translate('Marriage settlement')),
+ 'FAM:NCHI' => new CountOfChildren(I18N::translate('Number of children')),
+ 'FAM:NOTE' => new NoteStructure(I18N::translate('Note')),
+ 'FAM:OBJE' => new XrefMedia(I18N::translate('Media object')),
+ 'FAM:REFN' => new UserReferenceNumber(I18N::translate('Reference number')),
+ 'FAM:REFN:TYPE' => new UserReferenceType(I18N::translate('Type')),
+ 'FAM:RESI' => new Residence(I18N::translate('Residence')),
+ 'FAM:RESN' => new RestrictionNotice(I18N::translate('Restriction')),
+ 'FAM:RIN' => new AutomatedRecordId(I18N::translate('Record ID number')),
+ 'FAM:SLGS' => new LdsSpouseSealing(I18N::translate('LDS spouse sealing')),
+ 'FAM:SLGS:DATE' => new DateLdsOrd(I18N::translate('Date')),
+ 'FAM:SLGS:NOTE' => new NoteStructure(I18N::translate('Note')),
+ 'FAM:SLGS:PLAC' => new PlaceLivingOrdinance(I18N::translate('Place')),
+ 'FAM:SLGS:STAT' => new LdsSpouseSealingDateStatus(I18N::translate('Status')),
+ 'FAM:SLGS:STAT:DATE' => new ChangeDate(I18N::translate('Status change date')),
+ 'FAM:SLGS:TEMP' => new TempleCode(/* I18N: https://en.wikipedia.org/wiki/Temple_(LDS_Church)*/ I18N::translate('Temple')),
+ 'FAM:SOUR' => new XrefSource(I18N::translate('Source')),
+ 'FAM:SOUR:DATA' => new SourceData(I18N::translate('Data')),
+ 'FAM:SOUR:DATA:DATE' => new EntryRecordingDate(I18N::translate('Date of entry in original source')),
+ 'FAM:SOUR:DATA:TEXT' => new TextFromSource(I18N::translate('Text')),
+ 'FAM:SOUR:EVEN' => new EventTypeCitedFrom(I18N::translate('Event')),
+ 'FAM:SOUR:EVEN:ROLE' => new RoleInEvent(I18N::translate('Role')),
+ 'FAM:SOUR:NOTE' => new NoteStructure(I18N::translate('Note')),
+ 'FAM:SOUR:OBJE' => new XrefMedia(I18N::translate('Media object')),
+ 'FAM:SOUR:PAGE' => new WhereWithinSource(I18N::translate('Citation details')),
+ 'FAM:SOUR:QUAY' => new CertaintyAssessment(I18N::translate('Quality of data')),
+ 'FAM:SUBM' => new XrefSubmitter(I18N::translate('Submitter')),
+ 'FAM:WIFE' => new XrefIndividual(I18N::translate('Wife')),
+ 'FAM:_UID' => new PafUid(I18N::translate('Unique identifier')), // ***
+ 'HEAD' => new EmptyElement(I18N::translate('Header')),
+ 'HEAD:CHAR' => new CharacterSet(I18N::translate('Character set')),
+ 'HEAD:CHAR:VERS' => new VersionNumber(I18N::translate('Version')),
+ 'HEAD:COPR' => new CopyrightFile(I18N::translate('Copyright')),
+ 'HEAD:DATE' => new TransmissionDate(I18N::translate('Date')),
+ 'HEAD:DATE:TIME' => new TimeValue(I18N::translate('Time')),
+ 'HEAD:DEST' => new ReceivingSystemName(I18N::translate('Destination')),
+ 'HEAD:FILE' => new FileName(I18N::translate('Filename')),
+ 'HEAD:GEDC' => new Gedcom(I18N::translate('GEDCOM file')),
+ 'HEAD:GEDC:FORM' => new Form(I18N::translate('Format')),
+ 'HEAD:GEDC:VERS' => new VersionNumber(I18N::translate('Version')),
+ 'HEAD:NOTE' => new ContentDescription(I18N::translate('Note')),
+ 'HEAD:PLAC' => new EmptyElement(I18N::translate('Place'), ['FORM' => '1:1']),
+ 'HEAD:PLAC:FORM' => new PlaceHierarchy(I18N::translate('Format')),
+ 'HEAD:SOUR' => new ApprovedSystemId('Genealogy software'),
+ 'HEAD:SOUR:CORP' => new NameOfBusiness(I18N::translate('Corporation')),
+ 'HEAD:SOUR:CORP:ADDR' => new AddressLine(I18N::translate('Address')),
+ 'HEAD:SOUR:CORP:ADDR:ADR1' => new AddressLine1(I18N::translate('Address line 1')),
+ 'HEAD:SOUR:CORP:ADDR:ADR2' => new AddressLine2(I18N::translate('Address line 2')),
+ 'HEAD:SOUR:CORP:ADDR:ADR3' => new AddressLine3(I18N::translate('Address line 3')),
+ 'HEAD:SOUR:CORP:ADDR:CITY' => new AddressCity(I18N::translate('City')),
+ 'HEAD:SOUR:CORP:ADDR:CTRY' => new AddressCountry(I18N::translate('Country')),
+ 'HEAD:SOUR:CORP:ADDR:POST' => new AddressPostalCode(I18N::translate('Postal code')),
+ 'HEAD:SOUR:CORP:ADDR:STAE' => new AddressState(I18N::translate('State')),
+ 'HEAD:SOUR:CORP:EMAIL' => new AddressEmail(I18N::translate('Email address')),
+ 'HEAD:SOUR:CORP:FAX' => new AddressFax(I18N::translate('Fax')),
+ 'HEAD:SOUR:CORP:PHON' => new PhoneNumber(I18N::translate('Phone')),
+ 'HEAD:SOUR:CORP:WWW' => new AddressWebPage(I18N::translate('URL')),
+ 'HEAD:SOUR:DATA' => new NameOfSourceData('Data'),
+ 'HEAD:SOUR:DATA:COPR' => new CopyrightSourceData(I18N::translate('Copyright')),
+ 'HEAD:SOUR:DATA:DATE' => new PublicationDate(I18N::translate('Date')),
+ 'HEAD:SOUR:NAME' => new NameOfProduct('Software product'),
+ 'HEAD:SOUR:VERS' => new VersionNumber(I18N::translate('Version')),
+ 'HEAD:SUBM' => new XrefSubmitter(I18N::translate('Submitter')),
+ 'HEAD:SUBN' => new XrefSubmission(I18N::translate('Submission')),
+ 'INDI' => new EmptyElement(I18N::translate('Individual')),
+ 'INDI:*:ADDR' => new AddressLine(I18N::translate('Address')),
+ 'INDI:*:ADDR:ADR1' => new AddressLine1(I18N::translate('Address line 1')),
+ 'INDI:*:ADDR:ADR2' => new AddressLine2(I18N::translate('Address line 2')),
+ 'INDI:*:ADDR:ADR3' => new AddressLine3(I18N::translate('Address line 3')),
+ 'INDI:*:ADDR:CITY' => new AddressCity(I18N::translate('City')),
+ 'INDI:*:ADDR:CTRY' => new AddressCountry(I18N::translate('Country')),
+ 'INDI:*:ADDR:POST' => new AddressPostalCode(I18N::translate('Postal code')),
+ 'INDI:*:ADDR:STAE' => new AddressState(I18N::translate('State')),
+ 'INDI:*:AGE' => new AgeAtEvent(I18N::translate('Age')),
+ 'INDI:*:AGNC' => new ResponsibleAgency(I18N::translate('Agency')),
+ 'INDI:*:ASSO' => new XrefIndividual(I18N::translate('Associate')), // ***
+ 'INDI:*:ASSO:RELA' => new RelationIsDescriptor(I18N::translate('Relationship')),
+ 'INDI:*:CAUS' => new CauseOfEvent(I18N::translate('Cause')),
+ 'INDI:*:DATE' => new DateValue(I18N::translate('Date')),
+ 'INDI:*:EMAIL' => new AddressEmail(I18N::translate('Email address')),
+ 'INDI:*:FAX' => new AddressFax(I18N::translate('Fax')),
+ 'INDI:*:NOTE' => new NoteStructure(I18N::translate('Note')),
+ 'INDI:*:OBJE' => new XrefMedia(I18N::translate('Media object')),
+ 'INDI:*:PHON' => new PhoneNumber(I18N::translate('Phone')),
+ 'INDI:*:PLAC' => new PlaceName(I18N::translate('Place')),
+ 'INDI:*:PLAC:FONE' => new PlacePhoneticVariation(I18N::translate('Phonetic place')),
+ 'INDI:*:PLAC:FONE:TYPE' => new PhoneticType(I18N::translate('Type')),
+ 'INDI:*:PLAC:FORM' => new PlaceHierarchy(I18N::translate('Format')),
+ 'INDI:*:PLAC:MAP' => new EmptyElement(I18N::translate('Coordinates')),
+ 'INDI:*:PLAC:MAP:LATI' => new PlaceLatitude(I18N::translate('Latitude')),
+ 'INDI:*:PLAC:MAP:LONG' => new PlaceLongtitude(I18N::translate('Longitude')),
+ 'INDI:*:PLAC:NOTE' => new NoteStructure(I18N::translate('Note')),
+ 'INDI:*:PLAC:ROMN' => new PlaceRomanizedVariation(I18N::translate('Romanized place')),
+ 'INDI:*:PLAC:ROMN:TYPE' => new RomanizedType(I18N::translate('Type')),
+ 'INDI:*:PLAC:_HEB' => new NoteStructure(I18N::translate('Place in Hebrew')), // ***
+ 'INDI:*:RELI' => new ReligiousAffiliation(I18N::translate('Religion')),
+ 'INDI:*:RESN' => new RestrictionNotice(I18N::translate('Restriction')),
+ 'INDI:*:SOUR' => new XrefSource(I18N::translate('Source')),
+ 'INDI:*:SOUR:DATA' => new SourceData(I18N::translate('Data')),
+ 'INDI:*:SOUR:DATA:DATE' => new EntryRecordingDate(I18N::translate('Date of entry in original source')),
+ 'INDI:*:SOUR:DATA:TEXT' => new TextFromSource(I18N::translate('Text')),
+ 'INDI:*:SOUR:EVEN' => new EventTypeCitedFrom(I18N::translate('Event')),
+ 'INDI:*:SOUR:EVEN:ROLE' => new RoleInEvent(I18N::translate('Role')),
+ 'INDI:*:SOUR:NOTE' => new NoteStructure(I18N::translate('Note')),
+ 'INDI:*:SOUR:OBJE' => new XrefMedia(I18N::translate('Media object')),
+ 'INDI:*:SOUR:PAGE' => new WhereWithinSource(I18N::translate('Citation details')),
+ 'INDI:*:SOUR:QUAY' => new CertaintyAssessment(I18N::translate('Quality of data')),
+ 'INDI:*:TYPE' => new EventOrFactClassification(I18N::translate('Type')),
+ 'INDI:*:WWW' => new AddressWebPage(I18N::translate('URL')),
+ 'INDI:*:_ASSO' => new XrefIndividual(I18N::translate('Associate')), // ***
+ 'INDI:*:_ASSO:RELA' => new RelationIsDescriptor(I18N::translate('Relationship')), // ***
+ 'INDI:ADOP' => new Adoption(I18N::translate('Adoption')),
+ 'INDI:ADOP:DATE' => new DateValue(I18N::translate('Date of adoption')),
+ 'INDI:ADOP:FAMC' => new XrefFamily(I18N::translate('Adoptive parents')),
+ 'INDI:ADOP:FAMC:ADOP' => new AdoptedByWhichParent(I18N::translate('Adoption')),
+ 'INDI:ADOP:PLAC' => new PlaceName(I18N::translate('Place of adoption')),
+ 'INDI:AFN' => new AncestralFileNumber(I18N::translate('Ancestral file number')),
+ 'INDI:ALIA' => new XrefIndividual(I18N::translate('Alias')),
+ 'INDI:ANCI' => new XrefSubmitter(I18N::translate('Ancestors interest')),
+ 'INDI:ASSO' => new XrefIndividual(I18N::translate('Associate')),
+ 'INDI:ASSO:NOTE' => new NoteStructure(I18N::translate('Note')),
+ 'INDI:ASSO:RELA' => new RelationIsDescriptor(I18N::translate('Relationship')),
+ 'INDI:BAPL' => new LdsBaptism(I18N::translate('LDS baptism')),
+ 'INDI:BAPL:DATE' => new DateLdsOrd(I18N::translate('Date of LDS baptism')),
+ 'INDI:BAPL:NOTE' => new NoteStructure(I18N::translate('Note')),
+ 'INDI:BAPL:PLAC' => new PlaceLivingOrdinance(I18N::translate('Place of LDS baptism')),
+ 'INDI:BAPL:STAT' => new LdsBaptismDateStatus(I18N::translate('Status')),
+ 'INDI:BAPL:STAT:DATE' => new ChangeDate(I18N::translate('Status change date')),
+ 'INDI:BAPL:TEMP' => new TempleCode(/* I18N: https://en.wikipedia.org/wiki/Temple_(LDS_Church)*/ I18N::translate('Temple')),
+ 'INDI:BAPM' => new Baptism(I18N::translate('Baptism')),
+ 'INDI:BAPM:DATE' => new DateValue(I18N::translate('Date of baptism')),
+ 'INDI:BAPM:PLAC' => new PlaceName(I18N::translate('Place of baptism')),
+ 'INDI:BARM' => new PlaceName(I18N::translate('Bar mitzvah')),
+ 'INDI:BARM:DATE' => new DateValue(I18N::translate('Date of bar mitzvah')),
+ 'INDI:BARM:PLAC' => new PlaceName(I18N::translate('Place of bar mitzvah')),
+ 'INDI:BASM' => new BasMitzvah(I18N::translate('Bat mitzvah')),
+ 'INDI:BASM:DATE' => new BasMitzvah(I18N::translate('Date of bat mitzvah')),
+ 'INDI:BASM:PLAC' => new DateValue(I18N::translate('Place of bat mitzvah')),
+ 'INDI:BIRT' => new Birth(I18N::translate('Birth')),
+ 'INDI:BIRT:DATE' => new DateValue(I18N::translate('Date of birth')),
+ 'INDI:BIRT:FAMC' => new XrefFamily(I18N::translate('Birth parents')),
+ 'INDI:BIRT:PLAC' => new PlaceName(I18N::translate('Place of birth')),
+ 'INDI:BLES' => new Blessing(I18N::translate('Blessing')),
+ 'INDI:BLES:DATE' => new DateValue(I18N::translate('Date of blessing')),
+ 'INDI:BLES:PLAC' => new PlaceName(I18N::translate('Place of blessing')),
+ 'INDI:BURI' => new Burial(I18N::translate('Burial')),
+ 'INDI:BURI:DATE' => new DateValue(I18N::translate('Date of burial')),
+ 'INDI:BURI:PLAC' => new PlaceName(I18N::translate('Place of burial')),
+ 'INDI:CAST' => new CasteName(I18N::translate('Caste')),
+ 'INDI:CENS' => new Census(I18N::translate('Census')),
+ 'INDI:CENS:DATE' => new DateValue(I18N::translate('Census date')),
+ 'INDI:CENS:PLAC' => new PlaceName(I18N::translate('Census place')),
+ 'INDI:CHAN' => new Change(I18N::translate('Last change')),
+ 'INDI:CHAN:DATE' => new ChangeDate(I18N::translate('Date of last change')),
+ 'INDI:CHAN:DATE:TIME' => new TimeValue(I18N::translate('Time')),
+ 'INDI:CHAN:_WT_USER' => new WebtreesUser(I18N::translate('Author of last change')), // *** webtrees
+ 'INDI:CHR' => new Christening(I18N::translate('Christening')),
+ 'INDI:CHR:DATE' => new DateValue(I18N::translate('Date of christening')),
+ 'INDI:CHR:FAMC' => new XrefFamily(I18N::translate('Godparents')),
+ 'INDI:CHR:PLAC' => new PlaceName(I18N::translate('Place of christening')),
+ 'INDI:CHRA' => new AdultChristening(I18N::translate('Adult christening')),
+ 'INDI:CONF' => new Confirmation(I18N::translate('Confirmation')),
+ 'INDI:CONF:DATE' => new DateValue(I18N::translate('Date of confirmation')),
+ 'INDI:CONF:PLAC' => new PlaceName(I18N::translate('Place of confirmation')),
+ 'INDI:CONL' => new LdsConfirmation(I18N::translate('LDS confirmation')),
+ 'INDI:CONL:DATE' => new DateLdsOrd(I18N::translate('Date of LDS confirmation')),
+ 'INDI:CONL:NOTE' => new NoteStructure(I18N::translate('Note')),
+ 'INDI:CONL:PLAC' => new PlaceLivingOrdinance(I18N::translate('Place of LDS confirmation')),
+ 'INDI:CONL:STAT' => new LdsSpouseSealingDateStatus(I18N::translate('Status')),
+ 'INDI:CONL:STAT:DATE' => new ChangeDate(I18N::translate('Status change date')),
+ 'INDI:CONL:TEMP' => new TempleCode(/* I18N: https://en.wikipedia.org/wiki/Temple_(LDS_Church)*/ I18N::translate('Temple')),
+ 'INDI:CREM' => new Cremation(I18N::translate('Cremation')),
+ 'INDI:CREM:DATE' => new Cremation(I18N::translate('Date of cremation')),
+ 'INDI:CREM:PLAC' => new Cremation(I18N::translate('Place of cremation')),
+ 'INDI:DEAT' => new Death(I18N::translate('Death')),
+ 'INDI:DEAT:CAUS' => new CauseOfEvent(I18N::translate('Cause of death')),
+ 'INDI:DEAT:DATE' => new DateValue(I18N::translate('Date of death')),
+ 'INDI:DEAT:PLAC' => new PlaceName(I18N::translate('Place of death')),
+ 'INDI:DESI' => new XrefSubmitter(I18N::translate('Descendants interest')),
+ 'INDI:DSCR' => new PhysicalDescription(I18N::translate('Description')),
+ 'INDI:EDUC' => new ScholasticAchievement(I18N::translate('Education')),
+ 'INDI:EDUC:AGNC' => new ResponsibleAgency(I18N::translate('School or college')),
+ 'INDI:EMIG' => new Emigration(I18N::translate('Emigration')),
+ 'INDI:EMIG:DATE' => new DateValue(I18N::translate('Date of emigration')),
+ 'INDI:EMIG:PLAC' => new PlaceName(I18N::translate('Place of emigration')),
+ 'INDI:ENDL' => new LdsEndowment(I18N::translate('LDS endowment')),
+ 'INDI:ENDL:DATE' => new DateLdsOrd(I18N::translate('Date of LDS endowment')),
+ 'INDI:ENDL:NOTE' => new NoteStructure(I18N::translate('Note')),
+ 'INDI:ENDL:PLAC' => new PlaceLivingOrdinance(I18N::translate('Place of LDS endowment')),
+ 'INDI:ENDL:STAT' => new LdsEndowmentDateStatus(I18N::translate('Status')),
+ 'INDI:ENDL:STAT:DATE' => new ChangeDate(I18N::translate('Status change date')),
+ 'INDI:ENDL:TEMP' => new TempleCode(/* I18N: https://en.wikipedia.org/wiki/Temple_(LDS_Church)*/ I18N::translate('Temple')),
+ 'INDI:EVEN' => new EventDescriptor(I18N::translate('Event')),
+ 'INDI:EVEN:DATE' => new DateValue(I18N::translate('Date of event')),
+ 'INDI:EVEN:PLAC' => new PlaceName(I18N::translate('Place of event')),
+ 'INDI:EVEN:TYPE' => new EventAttributeType(I18N::translate('Type of event')),
+ 'INDI:FACT' => new AttributeDescriptor(I18N::translate('Fact')),
+ 'INDI:FACT:TYPE' => new EventAttributeType(I18N::translate('Type of fact')),
+ 'INDI:FAMC' => new XrefFamily(I18N::translate('Family as a child')),
+ 'INDI:FAMC:NOTE' => new NoteStructure(I18N::translate('Note')),
+ 'INDI:FAMC:PEDI' => new PedigreeLinkageType(I18N::translate('Relationship to parents')),
+ 'INDI:FAMC:STAT' => new ChildLinkageStatus(I18N::translate('Status')),
+ 'INDI:FAMS' => new XrefFamily(I18N::translate('Family as a spouse')),
+ 'INDI:FCOM' => new FirstCommunion(I18N::translate('First communion')),
+ 'INDI:FCOM:DATE' => new DateValue(I18N::translate('Date of first communion')),
+ 'INDI:FCOM:PLAC' => new PlaceName(I18N::translate('Place of first communion')),
+ 'INDI:GRAD' => new Graduation(I18N::translate('Graduation')),
+ 'INDI:GRAD:AGNC' => new ResponsibleAgency(I18N::translate('School or college')),
+ 'INDI:IDNO' => new NationalIdNumber(I18N::translate('Identification number')),
+ 'INDI:IMMI' => new Immigration(I18N::translate('Immigration')),
+ 'INDI:IMMI:DATE' => new DateValue(I18N::translate('Date of immigration')),
+ 'INDI:IMMI:PLAC' => new PlaceName(I18N::translate('Place of immigration')),
+ 'INDI:NAME' => new NamePersonal(I18N::translate('Name')),
+ 'INDI:NAME:FONE' => new NamePhoneticVariation(I18N::translate('Phonetic name')),
+ 'INDI:NAME:FONE:GIVN' => new NamePieceGiven(I18N::translate('Given names')),
+ 'INDI:NAME:FONE:NICK' => new NamePieceNickname(I18N::translate('Nickname')),
+ 'INDI:NAME:FONE:NPFX' => new NamePiecePrefix(I18N::translate('Name prefix')),
+ 'INDI:NAME:FONE:NSFX' => new NamePieceSuffix(I18N::translate('Name suffix')),
+ 'INDI:NAME:FONE:SPFX' => new NamePieceSurnamePrefix(I18N::translate('Surname prefix')),
+ 'INDI:NAME:FONE:SURN' => new NamePieceSurname(I18N::translate('Surname')),
+ 'INDI:NAME:FONE:TYPE' => new PhoneticType(I18N::translate('Type of name')),
+ 'INDI:NAME:GIVN' => new NamePieceGiven(I18N::translate('Given names')),
+ 'INDI:NAME:NICK' => new NamePieceNickname(I18N::translate('Nickname')),
+ 'INDI:NAME:NOTE' => new NoteStructure(I18N::translate('Note')),
+ 'INDI:NAME:NPFX' => new NamePiecePrefix(I18N::translate('Name prefix')),
+ 'INDI:NAME:NSFX' => new NamePieceSuffix(I18N::translate('Name suffix')),
+ 'INDI:NAME:ROMN' => new NameRomanizedVariation(I18N::translate('Romanized name')),
+ 'INDI:NAME:ROMN:GIVN' => new NamePieceGiven(I18N::translate('Given names')),
+ 'INDI:NAME:ROMN:NICK' => new NamePieceNickname(I18N::translate('Nickname')),
+ 'INDI:NAME:ROMN:NPFX' => new NamePiecePrefix(I18N::translate('Name prefix')),
+ 'INDI:NAME:ROMN:NSFX' => new NamePieceSuffix(I18N::translate('Name suffix')),
+ 'INDI:NAME:ROMN:SPFX' => new NamePieceSurnamePrefix(I18N::translate('Surname prefix')),
+ 'INDI:NAME:ROMN:SURN' => new NamePieceSurname(I18N::translate('Surname')),
+ 'INDI:NAME:ROMN:TYPE' => new RomanizedType(I18N::translate('Type')),
+ 'INDI:NAME:SPFX' => new NamePieceSurnamePrefix(I18N::translate('Surname prefix')),
+ 'INDI:NAME:SURN' => new NamePieceSurname(I18N::translate('Surname')),
+ 'INDI:NAME:TYPE' => new NameType(I18N::translate('Type of name')),
+ 'INDI:NATI' => new NationOrTribalOrigin(I18N::translate('Nationality')),
+ 'INDI:NATU' => new Naturalization(I18N::translate('Naturalization')),
+ 'INDI:NATU:DATE' => new DateValue(I18N::translate('Date of naturalization')),
+ 'INDI:NATU:PLAC' => new PlaceName(I18N::translate('Place of naturalization')),
+ 'INDI:NCHI' => new CountOfChildren(I18N::translate('Number of children')),
+ 'INDI:NMR' => new CountOfMarriages(I18N::translate('Number of marriages')),
+ 'INDI:NOTE' => new NoteStructure(I18N::translate('Note')),
+ 'INDI:OBJE' => new XrefMedia(I18N::translate('Media object')),
+ 'INDI:OCCU' => new Occupation(I18N::translate('Occupation')),
+ 'INDI:OCCU:AGNC' => new ResponsibleAgency(I18N::translate('Employer')),
+ 'INDI:ORDN' => new Ordination(I18N::translate('Ordination')),
+ 'INDI:ORDN:AGNC' => new Ordination(I18N::translate('Religious institution')),
+ 'INDI:ORDN:DATE' => new Ordination(I18N::translate('Date of ordination')),
+ 'INDI:ORDN:PLAC' => new Ordination(I18N::translate('Place of ordination')),
+ 'INDI:PROB' => new Probate(I18N::translate('Probate')),
+ 'INDI:PROP' => new Possessions(I18N::translate('Property')),
+ 'INDI:REFN' => new UserReferenceNumber(I18N::translate('Reference number')),
+ 'INDI:REFN:TYPE' => new UserReferenceType(I18N::translate('Type')),
+ 'INDI:RELI' => new ReligiousAffiliation(I18N::translate('Religion')),
+ 'INDI:RESI' => new Residence(I18N::translate('Residence')),
+ 'INDI:RESI:DATE' => new DateValue(I18N::translate('Date of residence')),
+ 'INDI:RESI:PLAC' => new PlaceName(I18N::translate('Place of residence')),
+ 'INDI:RESN' => new RestrictionNotice(I18N::translate('Restriction')),
+ 'INDI:RETI' => new Retirement(I18N::translate('Retirement')),
+ 'INDI:RETI:AGNC' => new ResponsibleAgency(I18N::translate('Employer')),
+ 'INDI:RFN' => new PermanentRecordFileNumber(I18N::translate('Record file number')),
+ 'INDI:RIN' => new AutomatedRecordId(I18N::translate('Record ID number')),
+ 'INDI:SEX' => new SexValue(I18N::translate('Gender')),
+ 'INDI:SLGC' => new LdsChildSealing(I18N::translate('LDS child sealing')),
+ 'INDI:SLGC:DATE' => new DateLdsOrd(I18N::translate('Date of LDS child sealing')),
+ 'INDI:SLGC:FAMC' => new XrefFamily(I18N::translate('Parents')),
+ 'INDI:SLGC:NOTE' => new NoteStructure(I18N::translate('Note')),
+ 'INDI:SLGC:PLAC' => new PlaceLivingOrdinance(I18N::translate('Place of LDS child sealing')),
+ 'INDI:SLGC:STAT' => new LdsChildSealingDateStatus(I18N::translate('Status')),
+ 'INDI:SLGC:STAT:DATE' => new ChangeDate(I18N::translate('Status change date')),
+ 'INDI:SLGC:TEMP' => new TempleCode(/* I18N: https://en.wikipedia.org/wiki/Temple_(LDS_Church)*/ I18N::translate('Temple')),
+ 'INDI:SOUR' => new XrefSource(I18N::translate('Source')),
+ 'INDI:SOUR:DATA' => new SourceData(I18N::translate('Data')),
+ 'INDI:SOUR:DATA:DATE' => new EntryRecordingDate(I18N::translate('Date of entry in original source')),
+ 'INDI:SOUR:DATA:TEXT' => new TextFromSource(I18N::translate('Text')),
+ 'INDI:SOUR:EVEN' => new EventTypeCitedFrom(I18N::translate('Event')),
+ 'INDI:SOUR:EVEN:ROLE' => new RoleInEvent(I18N::translate('Role')),
+ 'INDI:SOUR:NOTE' => new NoteStructure(I18N::translate('Note')),
+ 'INDI:SOUR:OBJE' => new XrefMedia(I18N::translate('Media object')),
+ 'INDI:SOUR:PAGE' => new WhereWithinSource(I18N::translate('Citation details')),
+ 'INDI:SOUR:QUAY' => new CertaintyAssessment(I18N::translate('Quality of data')),
+ 'INDI:SSN' => new SocialSecurityNumber(I18N::translate('Social security number')),
+ 'INDI:SUBM' => new XrefSubmitter(I18N::translate('Submitter')),
+ 'INDI:TITL' => new NobilityTypeTitle(I18N::translate('Title')),
+ 'INDI:WILL' => new Will(I18N::translate('Will')),
+ 'INDI:_TODO' => new UnknownElement(I18N::translate('Research task')), // *** webtrees
+ 'INDI:_TODO:DATE' => new DateValue(I18N::translate('Date')), // *** webtrees
+ 'INDI:_TODO:_WT_USER' => new WebtreesUser(I18N::translate('User')), // *** webtrees
+ 'INDI:_UID' => new PafUid(I18N::translate('Unique identifier')), // ***
+ 'INDI:_WT_OBJE_SORT' => new XrefMedia(I18N::translate('Re-order media')), // *** webtrees 1.7
+ 'NOTE' => new NoteRecord(I18N::translate('Note')),
+ 'NOTE:CHAN' => new Change(I18N::translate('Last change')),
+ 'NOTE:CHAN:DATE' => new ChangeDate(I18N::translate('Date of last change')),
+ 'NOTE:CHAN:DATE:TIME' => new TimeValue(I18N::translate('Time')),
+ 'NOTE:CHAN:NOTE' => new NoteStructure(I18N::translate('Note')),
+ 'NOTE:CHAN:_WT_USER' => new WebtreesUser(I18N::translate('Author of last change')), // *** webtrees
+ 'NOTE:CONC' => new SubmitterText(I18N::translate('Note')),
+ 'NOTE:CONT' => new SubmitterText(I18N::translate('Continued')),
+ 'NOTE:REFN' => new UserReferenceNumber(I18N::translate('Reference number')),
+ 'NOTE:REFN:TYPE' => new UserReferenceType(I18N::translate('Type')),
+ 'NOTE:RESN' => new RestrictionNotice(I18N::translate('Restriction')), // *** webtrees
+ 'NOTE:RIN' => new AutomatedRecordId(I18N::translate('Record ID number')),
+ 'NOTE:SOUR' => new XrefSource(I18N::translate('Source')),
+ 'NOTE:SOUR:DATA' => new SourceData(I18N::translate('Data')),
+ 'NOTE:SOUR:DATA:DATE' => new EntryRecordingDate(I18N::translate('Date of entry in original source')),
+ 'NOTE:SOUR:DATA:TEXT' => new TextFromSource(I18N::translate('Text')),
+ 'NOTE:SOUR:EVEN' => new EventTypeCitedFrom(I18N::translate('Event')),
+ 'NOTE:SOUR:EVEN:ROLE' => new RoleInEvent(I18N::translate('Role')),
+ 'NOTE:SOUR:NOTE' => new NoteStructure(I18N::translate('Note')),
+ 'NOTE:SOUR:OBJE' => new XrefMedia(I18N::translate('Media object')),
+ 'NOTE:SOUR:PAGE' => new WhereWithinSource(I18N::translate('Citation details')),
+ 'NOTE:SOUR:QUAY' => new CertaintyAssessment(I18N::translate('Quality of data')),
+ 'NOTE:_UID' => new PafUid(I18N::translate('Unique identifier')), // ***
+ 'OBJE' => new MediaRecord(I18N::translate('Media object')),
+ 'OBJE:CHAN' => new Change(I18N::translate('Last change')),
+ 'OBJE:CHAN:DATE' => new ChangeDate(I18N::translate('Date of last change')),
+ 'OBJE:CHAN:DATE:TIME' => new TimeValue(I18N::translate('Time')),
+ 'OBJE:CHAN:NOTE' => new NoteStructure(I18N::translate('Note')),
+ 'OBJE:CHAN:_WT_USER' => new WebtreesUser(I18N::translate('Author of last change')), // *** webtrees
+ 'OBJE:FILE' => new MultimediaFileReference(I18N::translate('Filename')),
+ 'OBJE:FILE:FORM' => new MultimediaFormat(I18N::translate('Format')),
+ 'OBJE:FILE:FORM:TYPE' => new SourceMediaType(I18N::translate('Media type')),
+ 'OBJE:FILE:TITL' => new DescriptiveTitle(I18N::translate('Title')),
+ 'OBJE:NOTE' => new NoteStructure(I18N::translate('Note')),
+ 'OBJE:REFN' => new UserReferenceNumber(I18N::translate('Reference number')),
+ 'OBJE:REFN:TYPE' => new UserReferenceType(I18N::translate('Type')),
+ 'OBJE:RESN' => new RestrictionNotice(I18N::translate('Restriction')), // *** webtrees
+ 'OBJE:RIN' => new AutomatedRecordId(I18N::translate('Record ID number')),
+ 'OBJE:SOUR' => new XrefSource(I18N::translate('Source')),
+ 'OBJE:SOUR:DATA' => new SourceData(I18N::translate('Data')),
+ 'OBJE:SOUR:DATA:DATE' => new EntryRecordingDate(I18N::translate('Date of entry in original source')),
+ 'OBJE:SOUR:DATA:TEXT' => new TextFromSource(I18N::translate('Text')),
+ 'OBJE:SOUR:EVEN' => new EventTypeCitedFrom(I18N::translate('Event')),
+ 'OBJE:SOUR:EVEN:ROLE' => new RoleInEvent(I18N::translate('Role')),
+ 'OBJE:SOUR:NOTE' => new NoteStructure(I18N::translate('Note')),
+ 'OBJE:SOUR:OBJE' => new XrefMedia(I18N::translate('Media object')),
+ 'OBJE:SOUR:PAGE' => new WhereWithinSource(I18N::translate('Citation details')),
+ 'OBJE:SOUR:QUAY' => new CertaintyAssessment(I18N::translate('Quality of data')),
+ 'OBJE:_UID' => new PafUid(I18N::translate('Unique identifier')), // ***
+ 'REPO' => new RepositoryRecord(I18N::translate('Repository')),
+ 'REPO:ADDR' => new AddressLine(I18N::translate('Address')),
+ 'REPO:ADDR:ADR1' => new AddressLine1(I18N::translate('Address line 1')),
+ 'REPO:ADDR:ADR2' => new AddressLine2(I18N::translate('Address line 2')),
+ 'REPO:ADDR:ADR3' => new AddressLine3(I18N::translate('Address line 3')),
+ 'REPO:ADDR:CITY' => new AddressCity(I18N::translate('City')),
+ 'REPO:ADDR:CTRY' => new AddressCountry(I18N::translate('Country')),
+ 'REPO:ADDR:POST' => new AddressPostalCode(I18N::translate('Postal code')),
+ 'REPO:ADDR:STAE' => new AddressState(I18N::translate('State')),
+ 'REPO:CHAN' => new Change(I18N::translate('Last change')),
+ 'REPO:CHAN:DATE' => new ChangeDate(I18N::translate('Date of last change')),
+ 'REPO:CHAN:DATE:TIME' => new TimeValue(I18N::translate('Time')),
+ 'REPO:CHAN:NOTE' => new NoteStructure(I18N::translate('Note')),
+ 'REPO:CHAN:_WT_USER' => new WebtreesUser(I18N::translate('Author of last change')), // *** webtrees
+ 'REPO:EMAIL' => new AddressEmail(I18N::translate('Email address')),
+ 'REPO:FAX' => new AddressFax(I18N::translate('Fax')),
+ 'REPO:NAME' => new NameOfRepository(I18N::translateContext('Repository', 'Name')),
+ 'REPO:NOTE' => new NoteStructure(I18N::translate('Note')),
+ 'REPO:PHON' => new PhoneNumber(I18N::translate('Phone')),
+ 'REPO:REFN' => new UserReferenceNumber(I18N::translate('Reference number')),
+ 'REPO:REFN:TYPE' => new UserReferenceType(I18N::translate('Type')),
+ 'REPO:RESN' => new RestrictionNotice(I18N::translate('Restriction')), // *** webtrees
+ 'REPO:RIN' => new AutomatedRecordId(I18N::translate('Record ID number')),
+ 'REPO:WWW' => new AddressWebPage(I18N::translate('URL')),
+ 'REPO:_UID' => new PafUid(I18N::translate('Unique identifier')), // ***
+ 'SOUR' => new SourceRecord(I18N::translate('Source')),
+ 'SOUR:ABBR' => new SourceFiledByEntry(I18N::translate('Abbreviation')),
+ 'SOUR:AUTH' => new SourceOriginator(I18N::translate('Author')),
+ 'SOUR:CHAN' => new Change(I18N::translate('Last change')),
+ 'SOUR:CHAN:DATE' => new ChangeDate(I18N::translate('Date of last change')),
+ 'SOUR:CHAN:DATE:TIME' => new TimeValue(I18N::translate('Time')),
+ 'SOUR:CHAN:NOTE' => new NoteStructure(I18N::translate('Note')),
+ 'SOUR:CHAN:_WT_USER' => new WebtreesUser(I18N::translate('Author of last change')), // *** webtrees
+ 'SOUR:DATA' => new EmptyElement(I18N::translate('Data'), ['EVEN' => '0:M', 'AGNC' => '0:1', 'NOTE' => '0:M']),
+ 'SOUR:DATA:AGNC' => new ResponsibleAgency(I18N::translate('Agency')),
+ 'SOUR:DATA:EVEN' => new EventsRecorded(I18N::translate('Events')),
+ 'SOUR:DATA:EVEN:DATE' => new DateValue(I18N::translate('Date range')),
+ 'SOUR:DATA:EVEN:PLAC' => new SourceJurisdictionPlace(I18N::translate('Place')),
+ 'SOUR:DATA:NOTE' => new NoteStructure(I18N::translate('Note')),
+ 'SOUR:NOTE' => new NoteStructure(I18N::translate('Note')),
+ 'SOUR:OBJE' => new XrefMedia(I18N::translate('Media object')),
+ 'SOUR:PUBL' => new SourcePublicationFacts(I18N::translate('Publication')),
+ 'SOUR:REFN' => new UserReferenceNumber(I18N::translate('Reference number')),
+ 'SOUR:REFN:TYPE' => new UserReferenceType(I18N::translate('Type')),
+ 'SOUR:REPO' => new XrefRepository(I18N::translate('Repository')),
+ 'SOUR:REPO:CALN' => new SourceCallNumber(I18N::translate('Call number')),
+ 'SOUR:REPO:CALN:MEDI' => new SourceMediaType(I18N::translate('Media type')),
+ 'SOUR:REPO:NOTE' => new NoteStructure(I18N::translate('Note')),
+ 'SOUR:RESN' => new RestrictionNotice(I18N::translate('Restriction')), // *** webtrees
+ 'SOUR:RIN' => new AutomatedRecordId(I18N::translate('Record ID number')),
+ 'SOUR:TEXT' => new TextFromSource(I18N::translate('Text')),
+ 'SOUR:TITL' => new DescriptiveTitle(I18N::translate('Title')),
+ 'SOUR:_UID' => new PafUid(I18N::translate('Unique identifier')), // ***
+ 'SUBM' => new SubmitterRecord(I18N::translate('Submitter')),
+ 'SUBM:ADDR' => new AddressLine(I18N::translate('Address')),
+ 'SUBM:ADDR:ADR1' => new AddressLine1(I18N::translate('Address line 1')),
+ 'SUBM:ADDR:ADR2' => new AddressLine2(I18N::translate('Address line 2')),
+ 'SUBM:ADDR:ADR3' => new AddressLine3(I18N::translate('Address line 3')),
+ 'SUBM:ADDR:CITY' => new AddressCity(I18N::translate('City')),
+ 'SUBM:ADDR:CTRY' => new AddressCountry(I18N::translate('Country')),
+ 'SUBM:ADDR:POST' => new AddressPostalCode(I18N::translate('Postal code')),
+ 'SUBM:ADDR:STAE' => new AddressState(I18N::translate('State')),
+ 'SUBM:CHAN' => new Change(I18N::translate('Last change')),
+ 'SUBM:CHAN:DATE' => new ChangeDate(I18N::translate('Date of last change')),
+ 'SUBM:CHAN:DATE:TIME' => new TimeValue(I18N::translate('Time')),
+ 'SUBM:CHAN:NOTE' => new NoteStructure(I18N::translate('Note')),
+ 'SUBM:CHAN:_WT_USER' => new WebtreesUser(I18N::translate('Author of last change')), // *** webtrees
+ 'SUBM:EMAIL' => new AddressEmail(I18N::translate('Email address')),
+ 'SUBM:FAX' => new AddressFax(I18N::translate('Fax')),
+ 'SUBM:LANG' => new LanguageId(I18N::translate('Language')),
+ 'SUBM:NAME' => new SubmitterName(I18N::translate('Name')),
+ 'SUBM:NOTE' => new NoteStructure(I18N::translate('Note')),
+ 'SUBM:OBJE' => new XrefMedia(I18N::translate('Media object')),
+ 'SUBM:PHON' => new PhoneNumber(I18N::translate('Phone')),
+ 'SUBM:RESN' => new RestrictionNotice(I18N::translate('Restriction')), // *** webtrees
+ 'SUBM:RFN' => new SubmitterRegisteredRfn(I18N::translate('Record file number')),
+ 'SUBM:RIN' => new AutomatedRecordId(I18N::translate('Record ID number')),
+ 'SUBM:WWW' => new AddressWebPage(I18N::translate('URL')),
+ 'SUBM:_UID' => new PafUid(I18N::translate('Unique identifier')), // ***
+ 'SUBN' => new SubmissionRecord(I18N::translate('Submission')),
+ 'SUBN:ANCE' => new GenerationsOfAncestors(I18N::translate('Generations of ancestors')),
+ 'SUBN:CHAN' => new Change(I18N::translate('Last change')),
+ 'SUBN:CHAN:DATE' => new ChangeDate(I18N::translate('Date of last change')),
+ 'SUBN:CHAN:DATE:TIME' => new TimeValue(I18N::translate('Time')),
+ 'SUBN:CHAN:NOTE' => new NoteStructure(I18N::translate('Note')),
+ 'SUBN:CHAN:_WT_USER' => new WebtreesUser(I18N::translate('Author of last change')), // *** webtrees
+ 'SUBN:DESC' => new GenerationsOfDescendants(I18N::translate('Generations of descendants')),
+ 'SUBN:FAMF' => new NameOfFamilyFile(I18N::translate('Family file')),
+ 'SUBN:NOTE' => new NoteStructure(I18N::translate('Note')),
+ 'SUBN:ORDI' => new OrdinanceProcessFlag(I18N::translate('Ordinance')),
+ 'SUBN:RESN' => new RestrictionNotice(I18N::translate('Restriction')), // *** webtrees
+ 'SUBN:RIN' => new AutomatedRecordId(I18N::translate('Record ID number')),
+ 'SUBN:SUBM' => new XrefSubmitter(I18N::translate('Submitter')),
+ 'SUBN:TEMP' => new TempleCode(/* I18N: https://en.wikipedia.org/wiki/Temple_(LDS_Church)*/ I18N::translate('Temple')),
+ 'SUBN:_UID' => new PafUid(I18N::translate('Unique identifier')), // ***
+ 'TRLR' => new EmptyElement(I18N::translate('Trailer')), // Not used in webtrees
+ ];
+ }
+
+ return $this->elements;
+ }
+}
diff --git a/app/Functions/FunctionsPrint.php b/app/Functions/FunctionsPrint.php
index a61a5f592f..84a2b0e915 100644
--- a/app/Functions/FunctionsPrint.php
+++ b/app/Functions/FunctionsPrint.php
@@ -557,24 +557,6 @@ class FunctionsPrint
$uniquefacts = [];
$quickfacts = [];
break;
-
- case Submitter::RECORD_TYPE:
- $addfacts = ['LANG', 'OBJE', 'NOTE', 'SHARED_NOTE'];
- $uniquefacts = ['ADDR', 'EMAIL', 'NAME', 'PHON', 'WWW'];
- $quickfacts = [];
- break;
-
- case Submission::RECORD_TYPE:
- $addfacts = ['NOTE', 'SHARED_NOTE'];
- $uniquefacts = ['FAMF', 'TEMP', 'ANCE', 'DESC', 'ORDI', 'SUBM'];
- $quickfacts = [];
- break;
-
- case Header::RECORD_TYPE:
- $addfacts = [];
- $uniquefacts = ['COPR', 'LANG', 'SUBM'];
- $quickfacts = [];
- break;
default:
return;
}
diff --git a/app/GedcomRecord.php b/app/GedcomRecord.php
index bd65d0f2bd..4c62e78ede 100644
--- a/app/GedcomRecord.php
+++ b/app/GedcomRecord.php
@@ -41,6 +41,7 @@ use function count;
use function date;
use function e;
use function explode;
+use function implode;
use function in_array;
use function md5;
use function preg_match;
@@ -51,10 +52,13 @@ use function preg_split;
use function route;
use function str_contains;
use function str_pad;
+use function str_starts_with;
use function strip_tags;
use function strtoupper;
+use function substr_count;
use function trim;
+use const PHP_INT_MAX;
use const PREG_SET_ORDER;
use const STR_PAD_LEFT;
@@ -1340,4 +1344,71 @@ class GedcomRecord
->lockForUpdate()
->get();
}
+
+ /**
+ * Add blank lines, to allow a user to add/edit new values.
+ *
+ * @return string
+ */
+ public function insertMissingSubtags(): string
+ {
+ $gedcom = $this->insertMissingLevels($this->tag(), $this->gedcom());
+
+ return preg_replace('/^0.*\n/', '', $gedcom);
+ }
+
+ /**
+ * @param string $tag
+ * @param string $gedcom
+ *
+ * @return string
+ */
+ public function insertMissingLevels(string $tag, string $gedcom): string
+ {
+ $next_level = substr_count($tag, ':') + 1;
+ $factory = Registry::elementFactory();
+ $subtags = $factory->make($tag)->subtags($this->tree);
+
+ // The first part is level N (includes CONT records). The remainder are level N+1.
+ $parts = preg_split('/\n(?=' . $next_level . ')/', $gedcom);
+ $return = array_shift($parts);
+
+ foreach ($subtags as $subtag => $occurrences) {
+ [$min, $max] = explode(':', $occurrences);
+ if ($max === 'M') {
+ $max = PHP_INT_MAX;
+ } else {
+ $max = (int) $max;
+ }
+
+ $count = 0;
+
+ // Add expected subtags in our preferred order.
+ foreach ($parts as $n => $part) {
+ if (str_starts_with($part, $next_level . ' ' . $subtag)) {
+ $return .= "\n" . $this->insertMissingLevels($tag . ':' . $subtag, $part);
+ $count++;
+ unset($parts[$n]);
+ }
+ }
+
+ // Allowed to have more of this subtag?
+ if ($count < $max) {
+ // Create a new one.
+ $gedcom = $next_level . ' ' . $subtag;
+ $default = $factory->make($tag . ':' . $subtag)->default($this->tree);
+ if ($default !== '') {
+ $gedcom .= ' ' . $default;
+ }
+ $return .= "\n" . $this->insertMissingLevels($tag . ':' . $subtag, $gedcom);
+ }
+ }
+
+ // Now add any unexpected/existing data.
+ if ($parts !== []) {
+ $return .= "\n" . implode("\n", $parts);
+ }
+
+ return $return;
+ }
}
diff --git a/app/Http/Middleware/RegisterFactories.php b/app/Http/Middleware/RegisterFactories.php
index fb36e5bcaf..2f7432adbd 100644
--- a/app/Http/Middleware/RegisterFactories.php
+++ b/app/Http/Middleware/RegisterFactories.php
@@ -22,6 +22,7 @@ namespace Fisharebest\Webtrees\Http\Middleware;
use Fisharebest\Webtrees\Factories\CacheFactory;
use Fisharebest\Webtrees\Factories\FamilyFactory;
use Fisharebest\Webtrees\Factories\FilesystemFactory;
+use Fisharebest\Webtrees\Factories\ElementFactory;
use Fisharebest\Webtrees\Factories\GedcomRecordFactory;
use Fisharebest\Webtrees\Factories\HeaderFactory;
use Fisharebest\Webtrees\Factories\ImageFactory;
@@ -56,6 +57,7 @@ class RegisterFactories implements MiddlewareInterface
Registry::cache(new CacheFactory());
Registry::familyFactory(new FamilyFactory());
Registry::filesystem(new FilesystemFactory());
+ Registry::elementFactory(new ElementFactory());
Registry::gedcomRecordFactory(new GedcomRecordFactory());
Registry::headerFactory(new HeaderFactory());
Registry::imageFactory(new ImageFactory());
diff --git a/app/Http/RequestHandlers/CreateLocationAction.php b/app/Http/RequestHandlers/CreateLocationAction.php
new file mode 100644
index 0000000000..a459415231
--- /dev/null
+++ b/app/Http/RequestHandlers/CreateLocationAction.php
@@ -0,0 +1,66 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Http\RequestHandlers;
+
+use Fisharebest\Webtrees\I18N;
+use Fisharebest\Webtrees\Registry;
+use Fisharebest\Webtrees\Tree;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+
+use function assert;
+
+/**
+ * Process a form to create a new location.
+ */
+class CreateLocationAction implements RequestHandlerInterface
+{
+ /**
+ * @param ServerRequestInterface $request
+ *
+ * @return ResponseInterface
+ */
+ public function handle(ServerRequestInterface $request): ResponseInterface
+ {
+ $tree = $request->getAttribute('tree');
+ assert($tree instanceof Tree);
+
+ $params = (array) $request->getParsedBody();
+ $name = $params['location_name'];
+
+ $gedcom = "0 @@ _LOC\n1 NAME " . $name;
+
+ $record = $tree->createRecord($gedcom);
+ $record = Registry::locationFactory()->new($record->xref(), $record->gedcom(), null, $tree);
+
+ return response([
+ 'id' => $record->xref(),
+ 'text' => view('selects/location', [
+ 'location' => $record,
+ ]),
+ 'html' => view('modals/record-created', [
+ 'title' => I18N::translate('The location has been created'),
+ 'name' => $record->fullName(),
+ 'url' => $record->url(),
+ ]),
+ ]);
+ }
+}
diff --git a/app/Http/RequestHandlers/CreateLocationModal.php b/app/Http/RequestHandlers/CreateLocationModal.php
new file mode 100644
index 0000000000..8fbf673e43
--- /dev/null
+++ b/app/Http/RequestHandlers/CreateLocationModal.php
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Http\RequestHandlers;
+
+use Fisharebest\Webtrees\Tree;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+
+use function assert;
+use function response;
+use function view;
+
+/**
+ * Show a form to create a new location.
+ */
+class CreateLocationModal implements RequestHandlerInterface
+{
+ /**
+ * @param ServerRequestInterface $request
+ *
+ * @return ResponseInterface
+ */
+ public function handle(ServerRequestInterface $request): ResponseInterface
+ {
+ $tree = $request->getAttribute('tree');
+ assert($tree instanceof Tree);
+
+ return response(view('modals/create-location', [
+ 'tree' => $tree,
+ ]));
+ }
+}
diff --git a/app/Http/RequestHandlers/CreateSubmissionAction.php b/app/Http/RequestHandlers/CreateSubmissionAction.php
new file mode 100644
index 0000000000..5fd51dbd94
--- /dev/null
+++ b/app/Http/RequestHandlers/CreateSubmissionAction.php
@@ -0,0 +1,66 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Http\RequestHandlers;
+
+use Fisharebest\Webtrees\I18N;
+use Fisharebest\Webtrees\Registry;
+use Fisharebest\Webtrees\Tree;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+
+use function assert;
+
+/**
+ * Process a form to create a new submission.
+ */
+class CreateSubmissionAction implements RequestHandlerInterface
+{
+ /**
+ * @param ServerRequestInterface $request
+ *
+ * @return ResponseInterface
+ */
+ public function handle(ServerRequestInterface $request): ResponseInterface
+ {
+ $tree = $request->getAttribute('tree');
+ assert($tree instanceof Tree);
+
+ $params = (array) $request->getParsedBody();
+ $submitter = $params['submitter'];
+
+ $gedcom = "0 @@ SUBN\n1 SUBM @" . $submitter . '@';
+
+ $record = $tree->createRecord($gedcom);
+ $record = Registry::submissionFactory()->new($record->xref(), $record->gedcom(), null, $tree);
+
+ return response([
+ 'id' => $record->xref(),
+ 'text' => view('selects/submission', [
+ 'submission' => $record,
+ ]),
+ 'html' => view('modals/record-created', [
+ 'title' => I18N::translate('The submission has been created'),
+ 'name' => $record->fullName(),
+ 'url' => $record->url(),
+ ]),
+ ]);
+ }
+}
diff --git a/app/Http/RequestHandlers/CreateSubmissionModal.php b/app/Http/RequestHandlers/CreateSubmissionModal.php
new file mode 100644
index 0000000000..65bb713514
--- /dev/null
+++ b/app/Http/RequestHandlers/CreateSubmissionModal.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Http\RequestHandlers;
+
+use Fisharebest\Webtrees\Tree;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+
+use function assert;
+
+/**
+ * Show a form to create a new submission.
+ */
+class CreateSubmissionModal implements RequestHandlerInterface
+{
+ /**
+ * @param ServerRequestInterface $request
+ *
+ * @return ResponseInterface
+ */
+ public function handle(ServerRequestInterface $request): ResponseInterface
+ {
+ $tree = $request->getAttribute('tree');
+ assert($tree instanceof Tree);
+
+ return response(view('modals/create-submission', [
+ 'tree' => $tree,
+ ]));
+ }
+}
diff --git a/app/Http/RequestHandlers/EditRecordAction.php b/app/Http/RequestHandlers/EditRecordAction.php
new file mode 100644
index 0000000000..69658e871a
--- /dev/null
+++ b/app/Http/RequestHandlers/EditRecordAction.php
@@ -0,0 +1,87 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Http\RequestHandlers;
+
+use Fisharebest\Webtrees\Auth;
+use Fisharebest\Webtrees\Registry;
+use Fisharebest\Webtrees\Services\GedcomEditService;
+use Fisharebest\Webtrees\Services\ModuleService;
+use Fisharebest\Webtrees\Tree;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+
+use function assert;
+use function is_string;
+use function redirect;
+
+/**
+ * Save an updated GEDCOM record.
+ */
+class EditRecordAction implements RequestHandlerInterface
+{
+ /** @var GedcomEditService */
+ private $gedcom_edit_service;
+
+ /** @var ModuleService */
+ private $module_service;
+
+ /**
+ * EditFactAction constructor.
+ *
+ * @param GedcomEditService $gedcom_edit_service
+ * @param ModuleService $module_service
+ */
+ public function __construct(GedcomEditService $gedcom_edit_service, ModuleService $module_service)
+ {
+ $this->gedcom_edit_service = $gedcom_edit_service;
+ $this->module_service = $module_service;
+ }
+
+ /**
+ * @param ServerRequestInterface $request
+ *
+ * @return ResponseInterface
+ */
+ public function handle(ServerRequestInterface $request): ResponseInterface
+ {
+ $tree = $request->getAttribute('tree');
+ assert($tree instanceof Tree);
+
+ $xref = $request->getAttribute('xref');
+ assert(is_string($xref));
+
+ $record = Registry::gedcomRecordFactory()->make($xref, $tree);
+ $record = Auth::checkRecordAccess($record, true);
+
+ $params = (array) $request->getParsedBody();
+ $keep_chan = (bool) ($params['keep_chan'] ?? false);
+ $levels = $params['levels'];
+ $tags = $params['tags'];
+ $values = $params['values'];
+
+ $gedcom = '0 @' . $record->xref() . '@ ' . $record->tag() . "\n";
+ $gedcom .= $this->gedcom_edit_service->editLinesToGedcom($record::RECORD_TYPE, $levels, $tags, $values);
+
+ $record->updateRecord($gedcom, !$keep_chan);
+
+ return redirect($record->url());
+ }
+}
diff --git a/app/Http/RequestHandlers/EditRecordPage.php b/app/Http/RequestHandlers/EditRecordPage.php
new file mode 100644
index 0000000000..de00c9d42a
--- /dev/null
+++ b/app/Http/RequestHandlers/EditRecordPage.php
@@ -0,0 +1,65 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Http\RequestHandlers;
+
+use Fisharebest\Webtrees\Auth;
+use Fisharebest\Webtrees\Http\ViewResponseTrait;
+use Fisharebest\Webtrees\Registry;
+use Fisharebest\Webtrees\Tree;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+
+use function assert;
+use function is_string;
+
+/**
+ * Edit a record.
+ */
+class EditRecordPage implements RequestHandlerInterface
+{
+ use ViewResponseTrait;
+
+ /**
+ * @param ServerRequestInterface $request
+ *
+ * @return ResponseInterface
+ */
+ public function handle(ServerRequestInterface $request): ResponseInterface
+ {
+ $tree = $request->getAttribute('tree');
+ assert($tree instanceof Tree);
+
+ $xref = $request->getAttribute('xref');
+ assert(is_string($xref));
+
+ $record = Registry::gedcomRecordFactory()->make($xref, $tree);
+ $record = Auth::checkRecordAccess($record, true);
+
+ $can_edit_raw = Auth::isAdmin() || $tree->getPreference('SHOW_GEDCOM_RECORD');
+
+ return $this->viewResponse('edit/edit-record', [
+ 'can_edit_raw' => $can_edit_raw,
+ 'record' => $record,
+ 'title' => $record->fullName(),
+ 'tree' => $tree,
+ ]);
+ }
+}
diff --git a/app/Http/RequestHandlers/Select2Location.php b/app/Http/RequestHandlers/Select2Location.php
new file mode 100644
index 0000000000..ad2bfd1242
--- /dev/null
+++ b/app/Http/RequestHandlers/Select2Location.php
@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Http\RequestHandlers;
+
+use Fisharebest\Webtrees\Registry;
+use Fisharebest\Webtrees\Services\SearchService;
+use Fisharebest\Webtrees\Location;
+use Fisharebest\Webtrees\Tree;
+use Illuminate\Support\Collection;
+
+use function view;
+
+/**
+ * Autocomplete for locations.
+ */
+class Select2Location extends AbstractSelect2Handler
+{
+ /** @var SearchService */
+ protected $search_service;
+
+ /**
+ * Select2Location constructor.
+ *
+ * @param SearchService $search_service
+ */
+ public function __construct(
+ SearchService $search_service
+ ) {
+ $this->search_service = $search_service;
+ }
+
+ /**
+ * Perform the search
+ *
+ * @param Tree $tree
+ * @param string $query
+ * @param int $offset
+ * @param int $limit
+ * @param string $at
+ *
+ * @return Collection<array<string,string>>
+ */
+ protected function search(Tree $tree, string $query, int $offset, int $limit, string $at): Collection
+ {
+ // Search by XREF
+ $location = Registry::locationFactory()->make($query, $tree);
+
+ if ($location instanceof Location) {
+ $results = new Collection([$location]);
+ } else {
+ $results = $this->search_service->searchLocations([$tree], [$query], $offset, $limit);
+ }
+
+ return $results->map(static function (Location $location) use ($at): array {
+ return [
+ 'id' => $at . $location->xref() . $at,
+ 'text' => view('selects/location', ['location' => $location]),
+ 'title' => ' ',
+ ];
+ });
+ }
+}
diff --git a/app/Http/RequestHandlers/Select2Submission.php b/app/Http/RequestHandlers/Select2Submission.php
new file mode 100644
index 0000000000..94c8a85a41
--- /dev/null
+++ b/app/Http/RequestHandlers/Select2Submission.php
@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Http\RequestHandlers;
+
+use Fisharebest\Webtrees\Registry;
+use Fisharebest\Webtrees\Services\SearchService;
+use Fisharebest\Webtrees\Submission;
+use Fisharebest\Webtrees\Tree;
+use Illuminate\Support\Collection;
+
+use function view;
+
+/**
+ * Autocomplete for submissions.
+ */
+class Select2Submission extends AbstractSelect2Handler
+{
+ /** @var SearchService */
+ protected $search_service;
+
+ /**
+ * Select2Submission constructor.
+ *
+ * @param SearchService $search_service
+ */
+ public function __construct(
+ SearchService $search_service
+ ) {
+ $this->search_service = $search_service;
+ }
+
+ /**
+ * Perform the search
+ *
+ * @param Tree $tree
+ * @param string $query
+ * @param int $offset
+ * @param int $limit
+ * @param string $at
+ *
+ * @return Collection<array<string,string>>
+ */
+ protected function search(Tree $tree, string $query, int $offset, int $limit, string $at): Collection
+ {
+ // Search by XREF
+ $submission = Registry::submissionFactory()->make($query, $tree);
+
+ if ($submission instanceof Submission) {
+ $results = new Collection([$submission]);
+ } else {
+ $results = $this->search_service->searchSubmissions([$tree], [$query], $offset, $limit);
+ }
+
+ return $results->map(static function (Submission $submission) use ($at): array {
+ return [
+ 'id' => $at . $submission->xref() . $at,
+ 'text' => view('selects/submission', ['submission' => $submission]),
+ 'title' => ' ',
+ ];
+ });
+ }
+}
diff --git a/app/Http/Routes/WebRoutes.php b/app/Http/Routes/WebRoutes.php
index df98e76bcf..2c85693b6e 100644
--- a/app/Http/Routes/WebRoutes.php
+++ b/app/Http/Routes/WebRoutes.php
@@ -65,6 +65,8 @@ use Fisharebest\Webtrees\Http\RequestHandlers\ContactAction;
use Fisharebest\Webtrees\Http\RequestHandlers\ContactPage;
use Fisharebest\Webtrees\Http\RequestHandlers\ControlPanel;
use Fisharebest\Webtrees\Http\RequestHandlers\CopyFact;
+use Fisharebest\Webtrees\Http\RequestHandlers\CreateLocationAction;
+use Fisharebest\Webtrees\Http\RequestHandlers\CreateLocationModal;
use Fisharebest\Webtrees\Http\RequestHandlers\CreateMediaObjectAction;
use Fisharebest\Webtrees\Http\RequestHandlers\CreateMediaObjectFromFile;
use Fisharebest\Webtrees\Http\RequestHandlers\CreateMediaObjectModal;
@@ -74,6 +76,8 @@ use Fisharebest\Webtrees\Http\RequestHandlers\CreateRepositoryAction;
use Fisharebest\Webtrees\Http\RequestHandlers\CreateRepositoryModal;
use Fisharebest\Webtrees\Http\RequestHandlers\CreateSourceAction;
use Fisharebest\Webtrees\Http\RequestHandlers\CreateSourceModal;
+use Fisharebest\Webtrees\Http\RequestHandlers\CreateSubmissionAction;
+use Fisharebest\Webtrees\Http\RequestHandlers\CreateSubmissionModal;
use Fisharebest\Webtrees\Http\RequestHandlers\CreateSubmitterAction;
use Fisharebest\Webtrees\Http\RequestHandlers\CreateSubmitterModal;
use Fisharebest\Webtrees\Http\RequestHandlers\CreateTreeAction;
@@ -101,6 +105,8 @@ use Fisharebest\Webtrees\Http\RequestHandlers\EditRawFactAction;
use Fisharebest\Webtrees\Http\RequestHandlers\EditRawFactPage;
use Fisharebest\Webtrees\Http\RequestHandlers\EditRawRecordAction;
use Fisharebest\Webtrees\Http\RequestHandlers\EditRawRecordPage;
+use Fisharebest\Webtrees\Http\RequestHandlers\EditRecordAction;
+use Fisharebest\Webtrees\Http\RequestHandlers\EditRecordPage;
use Fisharebest\Webtrees\Http\RequestHandlers\EmailPreferencesAction;
use Fisharebest\Webtrees\Http\RequestHandlers\EmailPreferencesPage;
use Fisharebest\Webtrees\Http\RequestHandlers\ExportGedcomClient;
@@ -242,11 +248,13 @@ use Fisharebest\Webtrees\Http\RequestHandlers\SearchReplaceAction;
use Fisharebest\Webtrees\Http\RequestHandlers\SearchReplacePage;
use Fisharebest\Webtrees\Http\RequestHandlers\Select2Family;
use Fisharebest\Webtrees\Http\RequestHandlers\Select2Individual;
+use Fisharebest\Webtrees\Http\RequestHandlers\Select2Location;
use Fisharebest\Webtrees\Http\RequestHandlers\Select2MediaObject;
use Fisharebest\Webtrees\Http\RequestHandlers\Select2Note;
use Fisharebest\Webtrees\Http\RequestHandlers\Select2Place;
use Fisharebest\Webtrees\Http\RequestHandlers\Select2Repository;
use Fisharebest\Webtrees\Http\RequestHandlers\Select2Source;
+use Fisharebest\Webtrees\Http\RequestHandlers\Select2Submission;
use Fisharebest\Webtrees\Http\RequestHandlers\Select2Submitter;
use Fisharebest\Webtrees\Http\RequestHandlers\SelectDefaultTree;
use Fisharebest\Webtrees\Http\RequestHandlers\SelectLanguage;
@@ -513,6 +521,8 @@ class WebRoutes
$router->post(AddSpouseToFamilyAction::class, '/add-spouse-to-family');
$router->get(ChangeFamilyMembersPage::class, '/change-family-members');
$router->post(ChangeFamilyMembersAction::class, '/change-family-members');
+ $router->get(CreateLocationModal::class, '/create-location');
+ $router->post(CreateLocationAction::class, '/create-location');
$router->get(CreateMediaObjectModal::class, '/create-media-object');
$router->post(CreateMediaObjectAction::class, '/create-media-object');
$router->post(CreateMediaObjectFromFile::class, '/create-media-from-file');
@@ -525,6 +535,8 @@ class WebRoutes
$router->post(CreateSourceAction::class, '/create-source');
$router->get(CreateSubmitterModal::class, '/create-submitter');
$router->post(CreateSubmitterAction::class, '/create-submitter');
+ $router->get(CreateSubmissionModal::class, '/create-submission');
+ $router->post(CreateSubmissionAction::class, '/create-submission');
$router->post(DeleteRecord::class, '/delete/{xref}');
$router->post(DeleteFact::class, '/delete/{xref}/{fact_id}');
$router->get(EditFactPage::class, '/edit-fact/{xref}/{fact_id}');
@@ -541,6 +553,8 @@ class WebRoutes
$router->get(LinkMediaToIndividualModal::class, '/link-media-to-individual/{xref}');
$router->get(LinkMediaToSourceModal::class, '/link-media-to-source/{xref}');
$router->post(LinkMediaToRecordAction::class, '/link-media-to-record/{xref}');
+ $router->get(EditRecordPage::class, '/edit-record/{xref}');
+ $router->post(EditRecordAction::class, '/update-record/{xref}');
$router->post(PasteFact::class, '/paste-fact/{xref}');
$router->get(ReorderChildrenPage::class, '/reorder-children/{xref}');
$router->post(ReorderChildrenAction::class, '/reorder-children/{xref}');
@@ -643,10 +657,12 @@ class WebRoutes
$router->post(SearchQuickAction::class, '/search-quick');
$router->post(Select2Family::class, '/select2-family');
$router->post(Select2Individual::class, '/select2-individual');
+ $router->post(Select2Location::class, '/select2-location');
$router->post(Select2MediaObject::class, '/select2-media');
$router->post(Select2Note::class, '/select2-note');
$router->post(Select2Place::class, '/select2-place');
$router->post(Select2Source::class, '/select2-source');
+ $router->post(Select2Submission::class, '/select2-submission');
$router->post(Select2Submitter::class, '/select2-submitter');
$router->post(Select2Repository::class, '/select2-repository');
$router->get(SourcePage::class, '/source/{xref}{/slug}');
diff --git a/app/Module/BranchesListModule.php b/app/Module/BranchesListModule.php
index d59ccee303..478d4a25f4 100644
--- a/app/Module/BranchesListModule.php
+++ b/app/Module/BranchesListModule.php
@@ -25,7 +25,6 @@ use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Contracts\UserInterface;
use Fisharebest\Webtrees\Registry;
use Fisharebest\Webtrees\Family;
-use Fisharebest\Webtrees\GedcomCode\GedcomCodePedi;
use Fisharebest\Webtrees\GedcomRecord;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Individual;
@@ -413,7 +412,7 @@ class BranchesListModule extends AbstractModule implements ModuleListInterface,
$pedi = $fact->attribute('PEDI');
if ($pedi !== '' && $pedi !== 'birth') {
- $pedigree = GedcomCodePedi::getValue($pedi, $individual);
+ $pedigree = Registry::elementFactory()->make('INDI:FAMC:PEDI')->value($pedi, $tree);
$indi_html = '<span class="red">' . $pedigree . '</span> ' . $indi_html;
}
break;
diff --git a/app/Registry.php b/app/Registry.php
index b209b8ec68..160766e19f 100644
--- a/app/Registry.php
+++ b/app/Registry.php
@@ -22,6 +22,7 @@ namespace Fisharebest\Webtrees;
use Fisharebest\Webtrees\Contracts\CacheFactoryInterface;
use Fisharebest\Webtrees\Contracts\FamilyFactoryInterface;
use Fisharebest\Webtrees\Contracts\FilesystemFactoryInterface;
+use Fisharebest\Webtrees\Contracts\ElementFactoryInterface;
use Fisharebest\Webtrees\Contracts\GedcomRecordFactoryInterface;
use Fisharebest\Webtrees\Contracts\HeaderFactoryInterface;
use Fisharebest\Webtrees\Contracts\ImageFactoryInterface;
@@ -43,6 +44,9 @@ class Registry
/** @var CacheFactoryInterface */
private static $cache_factory;
+ /** @var ElementFactoryInterface */
+ private static $element_factory;
+
/** @var FamilyFactoryInterface */
private static $family_factory;
@@ -104,6 +108,22 @@ class Registry
/**
* Store or retrieve a factory object.
*
+ * @param ElementFactoryInterface|null $factory
+ *
+ * @return ElementFactoryInterface
+ */
+ public static function elementFactory(ElementFactoryInterface $factory = null): ElementFactoryInterface
+ {
+ if ($factory instanceof ElementFactoryInterface) {
+ self::$element_factory = $factory;
+ }
+
+ return self::$element_factory;
+ }
+
+ /**
+ * Store or retrieve a factory object.
+ *
* @param FamilyFactoryInterface|null $factory
*
* @return FamilyFactoryInterface
diff --git a/app/Services/GedcomEditService.php b/app/Services/GedcomEditService.php
index 9b5a610b52..0d302ea9cb 100644
--- a/app/Services/GedcomEditService.php
+++ b/app/Services/GedcomEditService.php
@@ -20,6 +20,7 @@ declare(strict_types=1);
namespace Fisharebest\Webtrees\Services;
use Fisharebest\Webtrees\Gedcom;
+use Fisharebest\Webtrees\Registry;
use Fisharebest\Webtrees\Tree;
use Psr\Http\Message\ServerRequestInterface;
@@ -244,7 +245,7 @@ class GedcomEditService
$pass = false;
while ($k < $count && $this->glevels[$k] > $this->glevels[$j]) {
if ($this->text[$k] !== '') {
- if (($this->tag[$j] !== 'OBJE') || ($this->tag[$k] === 'FILE')) {
+ if ($this->tag[$j] !== 'OBJE' || $this->tag[$k] === 'FILE') {
$pass = true;
break;
}
@@ -436,4 +437,63 @@ class GedcomEditService
return $gedrec;
}
+
+ /**
+ * Reassemble edited GEDCOM fields into a GEDCOM fact/event string.
+ *
+ * @param string $record_type
+ * @param array<string> $levels
+ * @param array<string> $tags
+ * @param array<string> $values
+ *
+ * @return string
+ */
+ public function editLinesToGedcom(string $record_type, array $levels, array $tags, array $values): string
+ {
+ // Assert all arrays are the same size.
+ $count = count($levels);
+ assert($count > 0);
+ assert(count($tags) === $count);
+ assert(count($values) === $count);
+
+ $gedcom_lines = [];
+ $hierarchy = [$record_type];
+
+ for ($i = 0; $i < $count; $i++) {
+ $hierarchy[$levels[$i]] = $tags[$i];
+
+ $full_tag = implode(':', array_slice($hierarchy, 0, 1 + (int) $levels[$i]));
+ $element = Registry::elementFactory()->make($full_tag);
+ $values[$i] = $element->canonical($values[$i]);
+
+ // If "1 FACT Y" has a DATE or PLAC, then delete the value of Y
+ if ($levels[$i] === '1' && $values[$i] === 'Y') {
+ for ($j = $i + 1; $j < $count && $levels[$j] > $levels[$i]; ++$j) {
+ if ($levels[$j] === '2' && ($tags[$j] === 'DATE' || $tags[$j] === 'PLAC') && $values[$j] !== '') {
+ $values[$i] = '';
+ break;
+ }
+ }
+ }
+
+ // Include this line if there is a value - or if there is a child record with a value.
+ $include = $values[$i] !== '';
+
+ for ($j = $i + 1; !$include && $j < $count && $levels[$j] > $levels[$i]; $j++) {
+ $include = $values[$j] !== '';
+ }
+
+ if ($include) {
+ if ($values[$i] === '') {
+ $gedcom_lines[] = $levels[$i] . ' ' . $tags[$i];
+ } else {
+ $next_level = 1 + (int) $levels[$i];
+
+ $gedcom_lines[] = $levels[$i] . ' ' . $tags[$i] . ' ' . str_replace("\n", "\n" . $next_level . ' CONT ', $values[$i]);
+ }
+ }
+ }
+
+ return implode("\n", $gedcom_lines);
+ }
}
diff --git a/app/Statistics/Repository/EventRepository.php b/app/Statistics/Repository/EventRepository.php
index d0ba3c8cd5..02df31b342 100644
--- a/app/Statistics/Repository/EventRepository.php
+++ b/app/Statistics/Repository/EventRepository.php
@@ -310,7 +310,7 @@ class EventRepository implements EventRepositoryInterface
}
/**
- * Returns the formatted type of the first/last occuring event.
+ * Returns the formatted type of the first/last occurring event.
*
* @param string $direction The sorting direction
*