1a25f0a04SGreg Roach<?php 23976b470SGreg Roach 3a25f0a04SGreg Roach/** 4a25f0a04SGreg Roach * webtrees: online genealogy 51fe542e9SGreg Roach * Copyright (C) 2021 webtrees development team 6a25f0a04SGreg Roach * This program is free software: you can redistribute it and/or modify 7a25f0a04SGreg Roach * it under the terms of the GNU General Public License as published by 8a25f0a04SGreg Roach * the Free Software Foundation, either version 3 of the License, or 9a25f0a04SGreg Roach * (at your option) any later version. 10a25f0a04SGreg Roach * This program is distributed in the hope that it will be useful, 11a25f0a04SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of 12a25f0a04SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13a25f0a04SGreg Roach * GNU General Public License for more details. 14a25f0a04SGreg Roach * You should have received a copy of the GNU General Public License 1589f7189bSGreg Roach * along with this program. If not, see <https://www.gnu.org/licenses/>. 16a25f0a04SGreg Roach */ 17fcfa147eSGreg Roach 18e7f56f2aSGreg Roachdeclare(strict_types=1); 19e7f56f2aSGreg Roach 2076692c8bSGreg Roachnamespace Fisharebest\Webtrees; 21a25f0a04SGreg Roach 225afbc57aSGreg Roachuse Closure; 23456d0d35SGreg Roachuse Fisharebest\Flysystem\Adapter\ChrootAdapter; 24e5a6b4d4SGreg Roachuse Fisharebest\Webtrees\Contracts\UserInterface; 2522e73debSGreg Roachuse Fisharebest\Webtrees\Services\PendingChangesService; 2601461f86SGreg Roachuse Illuminate\Database\Capsule\Manager as DB; 27afb591d7SGreg Roachuse InvalidArgumentException; 281df7ae39SGreg Roachuse League\Flysystem\Filesystem; 29f7cf8a15SGreg Roachuse League\Flysystem\FilesystemOperator; 308b67c11aSGreg Roachuse stdClass; 31a25f0a04SGreg Roach 321e653452SGreg Roachuse function app; 33dec352c1SGreg Roachuse function array_key_exists; 3453432476SGreg Roachuse function date; 35dec352c1SGreg Roachuse function str_starts_with; 3653432476SGreg Roachuse function strtoupper; 37dec352c1SGreg Roachuse function substr_replace; 381e653452SGreg Roach 39a25f0a04SGreg Roach/** 4076692c8bSGreg Roach * Provide an interface to the wt_gedcom table. 41a25f0a04SGreg Roach */ 42c1010edaSGreg Roachclass Tree 43c1010edaSGreg Roach{ 44061b43d7SGreg Roach private const RESN_PRIVACY = [ 45061b43d7SGreg Roach 'none' => Auth::PRIV_PRIVATE, 46061b43d7SGreg Roach 'privacy' => Auth::PRIV_USER, 47061b43d7SGreg Roach 'confidential' => Auth::PRIV_NONE, 48061b43d7SGreg Roach 'hidden' => Auth::PRIV_HIDE, 49061b43d7SGreg Roach ]; 503df1e584SGreg Roach 51*8a07c98eSGreg Roach 52*8a07c98eSGreg Roach // Default values for some tree preferences. 53*8a07c98eSGreg Roach protected const DEFAULT_PREFERENCES = [ 54*8a07c98eSGreg Roach 'ADVANCED_NAME_FACTS' => 'NICK', 55*8a07c98eSGreg Roach 'ADVANCED_PLAC_FACTS' => '', 56*8a07c98eSGreg Roach 'CALENDAR_FORMAT' => 'gregorian', 57*8a07c98eSGreg Roach 'CHART_BOX_TAGS' => '', 58*8a07c98eSGreg Roach 'EXPAND_SOURCES' => '0', 59*8a07c98eSGreg Roach 'FORMAT_TEXT' => 'markdown', 60*8a07c98eSGreg Roach 'FULL_SOURCES' => '0', 61*8a07c98eSGreg Roach 'GEDCOM_MEDIA_PATH' => '', 62*8a07c98eSGreg Roach 'GENERATE_UIDS' => '0', 63*8a07c98eSGreg Roach 'HIDE_GEDCOM_ERRORS' => '1', 64*8a07c98eSGreg Roach 'HIDE_LIVE_PEOPLE' => '1', 65*8a07c98eSGreg Roach 'INDI_FACTS_ADD' => 'AFN,BIRT,DEAT,BURI,CREM,ADOP,BAPM,BARM,BASM,BLES,CHRA,CONF,FCOM,ORDN,NATU,EMIG,IMMI,CENS,PROB,WILL,GRAD,RETI,DSCR,EDUC,IDNO,NATI,NCHI,NMR,OCCU,PROP,RELI,RESI,SSN,TITL,BAPL,CONL,ENDL,SLGC,ASSO,RESN', 66*8a07c98eSGreg Roach 'INDI_FACTS_QUICK' => 'BIRT,BURI,BAPM,CENS,DEAT,OCCU,RESI', 67*8a07c98eSGreg Roach 'INDI_FACTS_UNIQUE' => '', 68*8a07c98eSGreg Roach 'KEEP_ALIVE_YEARS_BIRTH' => '', 69*8a07c98eSGreg Roach 'KEEP_ALIVE_YEARS_DEATH' => '', 70*8a07c98eSGreg Roach 'LANGUAGE' => 'en-US', 71*8a07c98eSGreg Roach 'MAX_ALIVE_AGE' => '120', 72*8a07c98eSGreg Roach 'MEDIA_DIRECTORY' => 'media/', 73*8a07c98eSGreg Roach 'MEDIA_UPLOAD' => Auth::PRIV_USER, 74*8a07c98eSGreg Roach 'META_DESCRIPTION' => '', 75*8a07c98eSGreg Roach 'META_TITLE' => Webtrees::NAME, 76*8a07c98eSGreg Roach 'NO_UPDATE_CHAN' => '0', 77*8a07c98eSGreg Roach 'PEDIGREE_ROOT_ID' => '', 78*8a07c98eSGreg Roach 'PREFER_LEVEL2_SOURCES' => '1', 79*8a07c98eSGreg Roach 'QUICK_REQUIRED_FACTS' => 'BIRT,DEAT', 80*8a07c98eSGreg Roach 'QUICK_REQUIRED_FAMFACTS' => 'MARR', 81*8a07c98eSGreg Roach 'REQUIRE_AUTHENTICATION' => '0', 82*8a07c98eSGreg Roach 'SAVE_WATERMARK_IMAGE' => '0', 83*8a07c98eSGreg Roach 'SHOW_AGE_DIFF' => '0', 84*8a07c98eSGreg Roach 'SHOW_COUNTER' => '1', 85*8a07c98eSGreg Roach 'SHOW_DEAD_PEOPLE' => Auth::PRIV_PRIVATE, 86*8a07c98eSGreg Roach 'SHOW_EST_LIST_DATES' => '0', 87*8a07c98eSGreg Roach 'SHOW_FACT_ICONS' => '1', 88*8a07c98eSGreg Roach 'SHOW_GEDCOM_RECORD' => '0', 89*8a07c98eSGreg Roach 'SHOW_HIGHLIGHT_IMAGES' => '1', 90*8a07c98eSGreg Roach 'SHOW_LEVEL2_NOTES' => '1', 91*8a07c98eSGreg Roach 'SHOW_LIVING_NAMES' => Auth::PRIV_USER, 92*8a07c98eSGreg Roach 'SHOW_MEDIA_DOWNLOAD' => '0', 93*8a07c98eSGreg Roach 'SHOW_NO_WATERMARK' => Auth::PRIV_USER, 94*8a07c98eSGreg Roach 'SHOW_PARENTS_AGE' => '1', 95*8a07c98eSGreg Roach 'SHOW_PEDIGREE_PLACES' => '9', 96*8a07c98eSGreg Roach 'SHOW_PEDIGREE_PLACES_SUFFIX' => '0', 97*8a07c98eSGreg Roach 'SHOW_PRIVATE_RELATIONSHIPS' => '1', 98*8a07c98eSGreg Roach 'SHOW_RELATIVES_EVENTS' => '_BIRT_CHIL,_BIRT_SIBL,_MARR_CHIL,_MARR_PARE,_DEAT_CHIL,_DEAT_PARE,_DEAT_GPAR,_DEAT_SIBL,_DEAT_SPOU', 99*8a07c98eSGreg Roach 'SUBLIST_TRIGGER_I' => '200', 100*8a07c98eSGreg Roach 'SURNAME_LIST_STYLE' => 'style2', 101*8a07c98eSGreg Roach 'SURNAME_TRADITION' => 'paternal', 102*8a07c98eSGreg Roach 'THUMBNAIL_WIDTH' => '100', 103*8a07c98eSGreg Roach 'USE_SILHOUETTE' => '1', 104*8a07c98eSGreg Roach 'WORD_WRAPPED_NOTES' => '0', 105*8a07c98eSGreg Roach ]; 106*8a07c98eSGreg Roach 1076ccdf4f0SGreg Roach /** @var int The tree's ID number */ 1086ccdf4f0SGreg Roach private $id; 1093df1e584SGreg Roach 1106ccdf4f0SGreg Roach /** @var string The tree's name */ 1116ccdf4f0SGreg Roach private $name; 1123df1e584SGreg Roach 1136ccdf4f0SGreg Roach /** @var string The tree's title */ 1146ccdf4f0SGreg Roach private $title; 1153df1e584SGreg Roach 1166ccdf4f0SGreg Roach /** @var int[] Default access rules for facts in this tree */ 1176ccdf4f0SGreg Roach private $fact_privacy; 1183df1e584SGreg Roach 1196ccdf4f0SGreg Roach /** @var int[] Default access rules for individuals in this tree */ 1206ccdf4f0SGreg Roach private $individual_privacy; 1213df1e584SGreg Roach 1226ccdf4f0SGreg Roach /** @var integer[][] Default access rules for individual facts in this tree */ 1236ccdf4f0SGreg Roach private $individual_fact_privacy; 1243df1e584SGreg Roach 1256ccdf4f0SGreg Roach /** @var string[] Cached copy of the wt_gedcom_setting table. */ 1266ccdf4f0SGreg Roach private $preferences = []; 1273df1e584SGreg Roach 1286ccdf4f0SGreg Roach /** @var string[][] Cached copy of the wt_user_gedcom_setting table. */ 1296ccdf4f0SGreg Roach private $user_preferences = []; 130061b43d7SGreg Roach 131a25f0a04SGreg Roach /** 1323df1e584SGreg Roach * Create a tree object. 133a25f0a04SGreg Roach * 13472cf66d4SGreg Roach * @param int $id 135aa6f03bbSGreg Roach * @param string $name 136cc13d6d8SGreg Roach * @param string $title 137a25f0a04SGreg Roach */ 1385afbc57aSGreg Roach public function __construct(int $id, string $name, string $title) 139c1010edaSGreg Roach { 14072cf66d4SGreg Roach $this->id = $id; 141aa6f03bbSGreg Roach $this->name = $name; 142cc13d6d8SGreg Roach $this->title = $title; 14313abd6f3SGreg Roach $this->fact_privacy = []; 14413abd6f3SGreg Roach $this->individual_privacy = []; 14513abd6f3SGreg Roach $this->individual_fact_privacy = []; 146518bbdc1SGreg Roach 147518bbdc1SGreg Roach // Load the privacy settings for this tree 148061b43d7SGreg Roach $rows = DB::table('default_resn') 149061b43d7SGreg Roach ->where('gedcom_id', '=', $this->id) 150061b43d7SGreg Roach ->get(); 151518bbdc1SGreg Roach 152518bbdc1SGreg Roach foreach ($rows as $row) { 153061b43d7SGreg Roach // Convert GEDCOM privacy restriction to a webtrees access level. 154061b43d7SGreg Roach $row->resn = self::RESN_PRIVACY[$row->resn]; 155061b43d7SGreg Roach 156518bbdc1SGreg Roach if ($row->xref !== null) { 157518bbdc1SGreg Roach if ($row->tag_type !== null) { 158b262b3d3SGreg Roach $this->individual_fact_privacy[$row->xref][$row->tag_type] = $row->resn; 159518bbdc1SGreg Roach } else { 160b262b3d3SGreg Roach $this->individual_privacy[$row->xref] = $row->resn; 161518bbdc1SGreg Roach } 162518bbdc1SGreg Roach } else { 163b262b3d3SGreg Roach $this->fact_privacy[$row->tag_type] = $row->resn; 164518bbdc1SGreg Roach } 165518bbdc1SGreg Roach } 166a25f0a04SGreg Roach } 167a25f0a04SGreg Roach 168a25f0a04SGreg Roach /** 1695afbc57aSGreg Roach * A closure which will create a record from a database row. 1705afbc57aSGreg Roach * 1715afbc57aSGreg Roach * @return Closure 1725afbc57aSGreg Roach */ 1735afbc57aSGreg Roach public static function rowMapper(): Closure 1745afbc57aSGreg Roach { 1755afbc57aSGreg Roach return static function (stdClass $row): Tree { 1765afbc57aSGreg Roach return new Tree((int) $row->tree_id, $row->tree_name, $row->tree_title); 1775afbc57aSGreg Roach }; 1785afbc57aSGreg Roach } 1795afbc57aSGreg Roach 1805afbc57aSGreg Roach /** 1816ccdf4f0SGreg Roach * Set the tree’s configuration settings. 1826ccdf4f0SGreg Roach * 1836ccdf4f0SGreg Roach * @param string $setting_name 1846ccdf4f0SGreg Roach * @param string $setting_value 1856ccdf4f0SGreg Roach * 1866ccdf4f0SGreg Roach * @return $this 1876ccdf4f0SGreg Roach */ 1886ccdf4f0SGreg Roach public function setPreference(string $setting_name, string $setting_value): Tree 1896ccdf4f0SGreg Roach { 1906ccdf4f0SGreg Roach if ($setting_value !== $this->getPreference($setting_name)) { 1916ccdf4f0SGreg Roach DB::table('gedcom_setting')->updateOrInsert([ 1926ccdf4f0SGreg Roach 'gedcom_id' => $this->id, 1936ccdf4f0SGreg Roach 'setting_name' => $setting_name, 1946ccdf4f0SGreg Roach ], [ 1956ccdf4f0SGreg Roach 'setting_value' => $setting_value, 1966ccdf4f0SGreg Roach ]); 1976ccdf4f0SGreg Roach 1986ccdf4f0SGreg Roach $this->preferences[$setting_name] = $setting_value; 1996ccdf4f0SGreg Roach 2006ccdf4f0SGreg Roach Log::addConfigurationLog('Tree preference "' . $setting_name . '" set to "' . $setting_value . '"', $this); 2016ccdf4f0SGreg Roach } 2026ccdf4f0SGreg Roach 2036ccdf4f0SGreg Roach return $this; 2046ccdf4f0SGreg Roach } 2056ccdf4f0SGreg Roach 2066ccdf4f0SGreg Roach /** 2076ccdf4f0SGreg Roach * Get the tree’s configuration settings. 2086ccdf4f0SGreg Roach * 2096ccdf4f0SGreg Roach * @param string $setting_name 2106ccdf4f0SGreg Roach * 2116ccdf4f0SGreg Roach * @return string 2126ccdf4f0SGreg Roach */ 213*8a07c98eSGreg Roach public function getPreference(string $setting_name): string 2146ccdf4f0SGreg Roach { 21554c1ab5eSGreg Roach if ($this->preferences === []) { 2166ccdf4f0SGreg Roach $this->preferences = DB::table('gedcom_setting') 2176ccdf4f0SGreg Roach ->where('gedcom_id', '=', $this->id) 2186ccdf4f0SGreg Roach ->pluck('setting_value', 'setting_name') 2196ccdf4f0SGreg Roach ->all(); 2206ccdf4f0SGreg Roach } 2216ccdf4f0SGreg Roach 222*8a07c98eSGreg Roach return $this->preferences[$setting_name] ?? self::DEFAULT_PREFERENCES[$setting_name] ?? ''; 2236ccdf4f0SGreg Roach } 2246ccdf4f0SGreg Roach 2256ccdf4f0SGreg Roach /** 2266ccdf4f0SGreg Roach * The name of this tree 2276ccdf4f0SGreg Roach * 2286ccdf4f0SGreg Roach * @return string 2296ccdf4f0SGreg Roach */ 2306ccdf4f0SGreg Roach public function name(): string 2316ccdf4f0SGreg Roach { 2326ccdf4f0SGreg Roach return $this->name; 2336ccdf4f0SGreg Roach } 2346ccdf4f0SGreg Roach 2356ccdf4f0SGreg Roach /** 2366ccdf4f0SGreg Roach * The title of this tree 2376ccdf4f0SGreg Roach * 2386ccdf4f0SGreg Roach * @return string 2396ccdf4f0SGreg Roach */ 2406ccdf4f0SGreg Roach public function title(): string 2416ccdf4f0SGreg Roach { 2426ccdf4f0SGreg Roach return $this->title; 2436ccdf4f0SGreg Roach } 2446ccdf4f0SGreg Roach 2456ccdf4f0SGreg Roach /** 2466ccdf4f0SGreg Roach * The fact-level privacy for this tree. 2476ccdf4f0SGreg Roach * 2486ccdf4f0SGreg Roach * @return int[] 2496ccdf4f0SGreg Roach */ 2506ccdf4f0SGreg Roach public function getFactPrivacy(): array 2516ccdf4f0SGreg Roach { 2526ccdf4f0SGreg Roach return $this->fact_privacy; 2536ccdf4f0SGreg Roach } 2546ccdf4f0SGreg Roach 2556ccdf4f0SGreg Roach /** 2566ccdf4f0SGreg Roach * The individual-level privacy for this tree. 2576ccdf4f0SGreg Roach * 2586ccdf4f0SGreg Roach * @return int[] 2596ccdf4f0SGreg Roach */ 2606ccdf4f0SGreg Roach public function getIndividualPrivacy(): array 2616ccdf4f0SGreg Roach { 2626ccdf4f0SGreg Roach return $this->individual_privacy; 2636ccdf4f0SGreg Roach } 2646ccdf4f0SGreg Roach 2656ccdf4f0SGreg Roach /** 2666ccdf4f0SGreg Roach * The individual-fact-level privacy for this tree. 2676ccdf4f0SGreg Roach * 2686ccdf4f0SGreg Roach * @return int[][] 2696ccdf4f0SGreg Roach */ 2706ccdf4f0SGreg Roach public function getIndividualFactPrivacy(): array 2716ccdf4f0SGreg Roach { 2726ccdf4f0SGreg Roach return $this->individual_fact_privacy; 2736ccdf4f0SGreg Roach } 2746ccdf4f0SGreg Roach 2756ccdf4f0SGreg Roach /** 2766ccdf4f0SGreg Roach * Set the tree’s user-configuration settings. 2776ccdf4f0SGreg Roach * 2786ccdf4f0SGreg Roach * @param UserInterface $user 2796ccdf4f0SGreg Roach * @param string $setting_name 2806ccdf4f0SGreg Roach * @param string $setting_value 2816ccdf4f0SGreg Roach * 2826ccdf4f0SGreg Roach * @return $this 2836ccdf4f0SGreg Roach */ 2846ccdf4f0SGreg Roach public function setUserPreference(UserInterface $user, string $setting_name, string $setting_value): Tree 2856ccdf4f0SGreg Roach { 2866ccdf4f0SGreg Roach if ($this->getUserPreference($user, $setting_name) !== $setting_value) { 2876ccdf4f0SGreg Roach // Update the database 2886ccdf4f0SGreg Roach DB::table('user_gedcom_setting')->updateOrInsert([ 2896ccdf4f0SGreg Roach 'gedcom_id' => $this->id(), 2906ccdf4f0SGreg Roach 'user_id' => $user->id(), 2916ccdf4f0SGreg Roach 'setting_name' => $setting_name, 2926ccdf4f0SGreg Roach ], [ 2936ccdf4f0SGreg Roach 'setting_value' => $setting_value, 2946ccdf4f0SGreg Roach ]); 2956ccdf4f0SGreg Roach 2966ccdf4f0SGreg Roach // Update the cache 2976ccdf4f0SGreg Roach $this->user_preferences[$user->id()][$setting_name] = $setting_value; 2986ccdf4f0SGreg Roach // Audit log of changes 2996ccdf4f0SGreg Roach Log::addConfigurationLog('Tree preference "' . $setting_name . '" set to "' . $setting_value . '" for user "' . $user->userName() . '"', $this); 3006ccdf4f0SGreg Roach } 3016ccdf4f0SGreg Roach 3026ccdf4f0SGreg Roach return $this; 3036ccdf4f0SGreg Roach } 3046ccdf4f0SGreg Roach 3056ccdf4f0SGreg Roach /** 3066ccdf4f0SGreg Roach * Get the tree’s user-configuration settings. 3076ccdf4f0SGreg Roach * 3086ccdf4f0SGreg Roach * @param UserInterface $user 3096ccdf4f0SGreg Roach * @param string $setting_name 3106ccdf4f0SGreg Roach * @param string $default 3116ccdf4f0SGreg Roach * 3126ccdf4f0SGreg Roach * @return string 3136ccdf4f0SGreg Roach */ 3146ccdf4f0SGreg Roach public function getUserPreference(UserInterface $user, string $setting_name, string $default = ''): string 3156ccdf4f0SGreg Roach { 3166ccdf4f0SGreg Roach // There are lots of settings, and we need to fetch lots of them on every page 3176ccdf4f0SGreg Roach // so it is quicker to fetch them all in one go. 3186ccdf4f0SGreg Roach if (!array_key_exists($user->id(), $this->user_preferences)) { 3196ccdf4f0SGreg Roach $this->user_preferences[$user->id()] = DB::table('user_gedcom_setting') 3206ccdf4f0SGreg Roach ->where('user_id', '=', $user->id()) 3216ccdf4f0SGreg Roach ->where('gedcom_id', '=', $this->id) 3226ccdf4f0SGreg Roach ->pluck('setting_value', 'setting_name') 3236ccdf4f0SGreg Roach ->all(); 3246ccdf4f0SGreg Roach } 3256ccdf4f0SGreg Roach 3266ccdf4f0SGreg Roach return $this->user_preferences[$user->id()][$setting_name] ?? $default; 3276ccdf4f0SGreg Roach } 3286ccdf4f0SGreg Roach 3296ccdf4f0SGreg Roach /** 3306ccdf4f0SGreg Roach * The ID of this tree 3316ccdf4f0SGreg Roach * 3326ccdf4f0SGreg Roach * @return int 3336ccdf4f0SGreg Roach */ 3346ccdf4f0SGreg Roach public function id(): int 3356ccdf4f0SGreg Roach { 3366ccdf4f0SGreg Roach return $this->id; 3376ccdf4f0SGreg Roach } 3386ccdf4f0SGreg Roach 3396ccdf4f0SGreg Roach /** 3406ccdf4f0SGreg Roach * Can a user accept changes for this tree? 3416ccdf4f0SGreg Roach * 3426ccdf4f0SGreg Roach * @param UserInterface $user 3436ccdf4f0SGreg Roach * 3446ccdf4f0SGreg Roach * @return bool 3456ccdf4f0SGreg Roach */ 3466ccdf4f0SGreg Roach public function canAcceptChanges(UserInterface $user): bool 3476ccdf4f0SGreg Roach { 3486ccdf4f0SGreg Roach return Auth::isModerator($this, $user); 3496ccdf4f0SGreg Roach } 3506ccdf4f0SGreg Roach 3516ccdf4f0SGreg Roach /** 352b78374c5SGreg Roach * Are there any pending edits for this tree, than need reviewing by a moderator. 353b78374c5SGreg Roach * 354b78374c5SGreg Roach * @return bool 355b78374c5SGreg Roach */ 356771ae10aSGreg Roach public function hasPendingEdit(): bool 357c1010edaSGreg Roach { 35815a3f100SGreg Roach return DB::table('change') 35915a3f100SGreg Roach ->where('gedcom_id', '=', $this->id) 36015a3f100SGreg Roach ->where('status', '=', 'pending') 36115a3f100SGreg Roach ->exists(); 362b78374c5SGreg Roach } 363b78374c5SGreg Roach 364b78374c5SGreg Roach /** 3656ccdf4f0SGreg Roach * Create a new record from GEDCOM data. 3666ccdf4f0SGreg Roach * 3676ccdf4f0SGreg Roach * @param string $gedcom 3686ccdf4f0SGreg Roach * 3690d15532eSGreg Roach * @return GedcomRecord|Individual|Family|Location|Note|Source|Repository|Media|Submitter|Submission 3706ccdf4f0SGreg Roach * @throws InvalidArgumentException 3716ccdf4f0SGreg Roach */ 3726ccdf4f0SGreg Roach public function createRecord(string $gedcom): GedcomRecord 3736ccdf4f0SGreg Roach { 374b4a2f885SGreg Roach if (!preg_match('/^0 @@ ([_A-Z]+)/', $gedcom, $match)) { 3756ccdf4f0SGreg Roach throw new InvalidArgumentException('GedcomRecord::createRecord(' . $gedcom . ') does not begin 0 @@'); 3766ccdf4f0SGreg Roach } 3776ccdf4f0SGreg Roach 3786b9cb339SGreg Roach $xref = Registry::xrefFactory()->make($match[1]); 379dec352c1SGreg Roach $gedcom = substr_replace($gedcom, $xref, 3, 0); 3806ccdf4f0SGreg Roach 3816ccdf4f0SGreg Roach // Create a change record 38253432476SGreg Roach $today = strtoupper(date('d M Y')); 38353432476SGreg Roach $now = date('H:i:s'); 38453432476SGreg Roach $gedcom .= "\n1 CHAN\n2 DATE " . $today . "\n3 TIME " . $now . "\n2 _WT_USER " . Auth::user()->userName(); 3856ccdf4f0SGreg Roach 3866ccdf4f0SGreg Roach // Create a pending change 3876ccdf4f0SGreg Roach DB::table('change')->insert([ 3886ccdf4f0SGreg Roach 'gedcom_id' => $this->id, 3896ccdf4f0SGreg Roach 'xref' => $xref, 3906ccdf4f0SGreg Roach 'old_gedcom' => '', 3916ccdf4f0SGreg Roach 'new_gedcom' => $gedcom, 3926ccdf4f0SGreg Roach 'user_id' => Auth::id(), 3936ccdf4f0SGreg Roach ]); 3946ccdf4f0SGreg Roach 3956ccdf4f0SGreg Roach // Accept this pending change 3961fe542e9SGreg Roach if (Auth::user()->getPreference(UserInterface::PREF_AUTO_ACCEPT_EDITS) === '1') { 3976b9cb339SGreg Roach $record = Registry::gedcomRecordFactory()->new($xref, $gedcom, null, $this); 3986ccdf4f0SGreg Roach 39922e73debSGreg Roach app(PendingChangesService::class)->acceptRecord($record); 40022e73debSGreg Roach 40122e73debSGreg Roach return $record; 4026ccdf4f0SGreg Roach } 4036ccdf4f0SGreg Roach 4046b9cb339SGreg Roach return Registry::gedcomRecordFactory()->new($xref, '', $gedcom, $this); 405a25f0a04SGreg Roach } 406304f20d5SGreg Roach 407304f20d5SGreg Roach /** 408afb591d7SGreg Roach * Create a new family from GEDCOM data. 409afb591d7SGreg Roach * 410afb591d7SGreg Roach * @param string $gedcom 411afb591d7SGreg Roach * 412afb591d7SGreg Roach * @return Family 413afb591d7SGreg Roach * @throws InvalidArgumentException 414afb591d7SGreg Roach */ 415afb591d7SGreg Roach public function createFamily(string $gedcom): GedcomRecord 416afb591d7SGreg Roach { 417dec352c1SGreg Roach if (!str_starts_with($gedcom, '0 @@ FAM')) { 418afb591d7SGreg Roach throw new InvalidArgumentException('GedcomRecord::createFamily(' . $gedcom . ') does not begin 0 @@ FAM'); 419afb591d7SGreg Roach } 420afb591d7SGreg Roach 4216b9cb339SGreg Roach $xref = Registry::xrefFactory()->make(Family::RECORD_TYPE); 422dec352c1SGreg Roach $gedcom = substr_replace($gedcom, $xref, 3, 0); 423afb591d7SGreg Roach 424afb591d7SGreg Roach // Create a change record 42553432476SGreg Roach $today = strtoupper(date('d M Y')); 42653432476SGreg Roach $now = date('H:i:s'); 42753432476SGreg Roach $gedcom .= "\n1 CHAN\n2 DATE " . $today . "\n3 TIME " . $now . "\n2 _WT_USER " . Auth::user()->userName(); 428afb591d7SGreg Roach 429afb591d7SGreg Roach // Create a pending change 430963fbaeeSGreg Roach DB::table('change')->insert([ 431963fbaeeSGreg Roach 'gedcom_id' => $this->id, 432963fbaeeSGreg Roach 'xref' => $xref, 433963fbaeeSGreg Roach 'old_gedcom' => '', 434963fbaeeSGreg Roach 'new_gedcom' => $gedcom, 435963fbaeeSGreg Roach 'user_id' => Auth::id(), 436afb591d7SGreg Roach ]); 437304f20d5SGreg Roach 438304f20d5SGreg Roach // Accept this pending change 4391fe542e9SGreg Roach if (Auth::user()->getPreference(UserInterface::PREF_AUTO_ACCEPT_EDITS) === '1') { 4406b9cb339SGreg Roach $record = Registry::familyFactory()->new($xref, $gedcom, null, $this); 441afb591d7SGreg Roach 44222e73debSGreg Roach app(PendingChangesService::class)->acceptRecord($record); 44322e73debSGreg Roach 44422e73debSGreg Roach return $record; 445304f20d5SGreg Roach } 446afb591d7SGreg Roach 4476b9cb339SGreg Roach return Registry::familyFactory()->new($xref, '', $gedcom, $this); 448afb591d7SGreg Roach } 449afb591d7SGreg Roach 450afb591d7SGreg Roach /** 451afb591d7SGreg Roach * Create a new individual from GEDCOM data. 452afb591d7SGreg Roach * 453afb591d7SGreg Roach * @param string $gedcom 454afb591d7SGreg Roach * 455afb591d7SGreg Roach * @return Individual 456afb591d7SGreg Roach * @throws InvalidArgumentException 457afb591d7SGreg Roach */ 458afb591d7SGreg Roach public function createIndividual(string $gedcom): GedcomRecord 459afb591d7SGreg Roach { 460dec352c1SGreg Roach if (!str_starts_with($gedcom, '0 @@ INDI')) { 461afb591d7SGreg Roach throw new InvalidArgumentException('GedcomRecord::createIndividual(' . $gedcom . ') does not begin 0 @@ INDI'); 462afb591d7SGreg Roach } 463afb591d7SGreg Roach 4646b9cb339SGreg Roach $xref = Registry::xrefFactory()->make(Individual::RECORD_TYPE); 465dec352c1SGreg Roach $gedcom = substr_replace($gedcom, $xref, 3, 0); 466afb591d7SGreg Roach 467afb591d7SGreg Roach // Create a change record 46853432476SGreg Roach $today = strtoupper(date('d M Y')); 46953432476SGreg Roach $now = date('H:i:s'); 47053432476SGreg Roach $gedcom .= "\n1 CHAN\n2 DATE " . $today . "\n3 TIME " . $now . "\n2 _WT_USER " . Auth::user()->userName(); 471afb591d7SGreg Roach 472afb591d7SGreg Roach // Create a pending change 473963fbaeeSGreg Roach DB::table('change')->insert([ 474963fbaeeSGreg Roach 'gedcom_id' => $this->id, 475963fbaeeSGreg Roach 'xref' => $xref, 476963fbaeeSGreg Roach 'old_gedcom' => '', 477963fbaeeSGreg Roach 'new_gedcom' => $gedcom, 478963fbaeeSGreg Roach 'user_id' => Auth::id(), 479afb591d7SGreg Roach ]); 480afb591d7SGreg Roach 481afb591d7SGreg Roach // Accept this pending change 4821fe542e9SGreg Roach if (Auth::user()->getPreference(UserInterface::PREF_AUTO_ACCEPT_EDITS) === '1') { 4836b9cb339SGreg Roach $record = Registry::individualFactory()->new($xref, $gedcom, null, $this); 484afb591d7SGreg Roach 48522e73debSGreg Roach app(PendingChangesService::class)->acceptRecord($record); 48622e73debSGreg Roach 48722e73debSGreg Roach return $record; 488afb591d7SGreg Roach } 489afb591d7SGreg Roach 4906b9cb339SGreg Roach return Registry::individualFactory()->new($xref, '', $gedcom, $this); 491304f20d5SGreg Roach } 4928586983fSGreg Roach 4938586983fSGreg Roach /** 49420b58d20SGreg Roach * Create a new media object from GEDCOM data. 49520b58d20SGreg Roach * 49620b58d20SGreg Roach * @param string $gedcom 49720b58d20SGreg Roach * 49820b58d20SGreg Roach * @return Media 49920b58d20SGreg Roach * @throws InvalidArgumentException 50020b58d20SGreg Roach */ 50120b58d20SGreg Roach public function createMediaObject(string $gedcom): Media 50220b58d20SGreg Roach { 503dec352c1SGreg Roach if (!str_starts_with($gedcom, '0 @@ OBJE')) { 50420b58d20SGreg Roach throw new InvalidArgumentException('GedcomRecord::createIndividual(' . $gedcom . ') does not begin 0 @@ OBJE'); 50520b58d20SGreg Roach } 50620b58d20SGreg Roach 5076b9cb339SGreg Roach $xref = Registry::xrefFactory()->make(Media::RECORD_TYPE); 508dec352c1SGreg Roach $gedcom = substr_replace($gedcom, $xref, 3, 0); 50920b58d20SGreg Roach 51020b58d20SGreg Roach // Create a change record 51153432476SGreg Roach $today = strtoupper(date('d M Y')); 51253432476SGreg Roach $now = date('H:i:s'); 51353432476SGreg Roach $gedcom .= "\n1 CHAN\n2 DATE " . $today . "\n3 TIME " . $now . "\n2 _WT_USER " . Auth::user()->userName(); 51420b58d20SGreg Roach 51520b58d20SGreg Roach // Create a pending change 516963fbaeeSGreg Roach DB::table('change')->insert([ 517963fbaeeSGreg Roach 'gedcom_id' => $this->id, 518963fbaeeSGreg Roach 'xref' => $xref, 519963fbaeeSGreg Roach 'old_gedcom' => '', 520963fbaeeSGreg Roach 'new_gedcom' => $gedcom, 521963fbaeeSGreg Roach 'user_id' => Auth::id(), 52220b58d20SGreg Roach ]); 52320b58d20SGreg Roach 52420b58d20SGreg Roach // Accept this pending change 5251fe542e9SGreg Roach if (Auth::user()->getPreference(UserInterface::PREF_AUTO_ACCEPT_EDITS) === '1') { 5266b9cb339SGreg Roach $record = Registry::mediaFactory()->new($xref, $gedcom, null, $this); 52720b58d20SGreg Roach 52822e73debSGreg Roach app(PendingChangesService::class)->acceptRecord($record); 52922e73debSGreg Roach 53022e73debSGreg Roach return $record; 53120b58d20SGreg Roach } 53220b58d20SGreg Roach 5336b9cb339SGreg Roach return Registry::mediaFactory()->new($xref, '', $gedcom, $this); 53420b58d20SGreg Roach } 53520b58d20SGreg Roach 53620b58d20SGreg Roach /** 5378586983fSGreg Roach * What is the most significant individual in this tree. 5388586983fSGreg Roach * 539e5a6b4d4SGreg Roach * @param UserInterface $user 5403370567dSGreg Roach * @param string $xref 5418586983fSGreg Roach * 5428586983fSGreg Roach * @return Individual 5438586983fSGreg Roach */ 5443370567dSGreg Roach public function significantIndividual(UserInterface $user, $xref = ''): Individual 545c1010edaSGreg Roach { 5463370567dSGreg Roach if ($xref === '') { 5478f9b0fb2SGreg Roach $individual = null; 5483370567dSGreg Roach } else { 5496b9cb339SGreg Roach $individual = Registry::individualFactory()->make($xref, $this); 5503370567dSGreg Roach 5513370567dSGreg Roach if ($individual === null) { 5526b9cb339SGreg Roach $family = Registry::familyFactory()->make($xref, $this); 5533370567dSGreg Roach 5543370567dSGreg Roach if ($family instanceof Family) { 5553370567dSGreg Roach $individual = $family->spouses()->first() ?? $family->children()->first(); 5563370567dSGreg Roach } 5573370567dSGreg Roach } 5583370567dSGreg Roach } 5598586983fSGreg Roach 5601fe542e9SGreg Roach if ($individual === null && $this->getUserPreference($user, UserInterface::PREF_TREE_DEFAULT_XREF) !== '') { 5611fe542e9SGreg Roach $individual = Registry::individualFactory()->make($this->getUserPreference($user, UserInterface::PREF_TREE_DEFAULT_XREF), $this); 5628586983fSGreg Roach } 5638f9b0fb2SGreg Roach 5641fe542e9SGreg Roach if ($individual === null && $this->getUserPreference($user, UserInterface::PREF_TREE_ACCOUNT_XREF) !== '') { 5651fe542e9SGreg Roach $individual = Registry::individualFactory()->make($this->getUserPreference($user, UserInterface::PREF_TREE_ACCOUNT_XREF), $this); 5668586983fSGreg Roach } 5678f9b0fb2SGreg Roach 568bec87e94SGreg Roach if ($individual === null && $this->getPreference('PEDIGREE_ROOT_ID') !== '') { 5696b9cb339SGreg Roach $individual = Registry::individualFactory()->make($this->getPreference('PEDIGREE_ROOT_ID'), $this); 5708586983fSGreg Roach } 5718f9b0fb2SGreg Roach if ($individual === null) { 5728f9b0fb2SGreg Roach $xref = (string) DB::table('individuals') 5738f9b0fb2SGreg Roach ->where('i_file', '=', $this->id()) 5748f9b0fb2SGreg Roach ->min('i_id'); 575769d7d6eSGreg Roach 5766b9cb339SGreg Roach $individual = Registry::individualFactory()->make($xref, $this); 5775fe1add5SGreg Roach } 5788f9b0fb2SGreg Roach if ($individual === null) { 5795fe1add5SGreg Roach // always return a record 5806b9cb339SGreg Roach $individual = Registry::individualFactory()->new('I', '0 @I@ INDI', null, $this); 5815fe1add5SGreg Roach } 5825fe1add5SGreg Roach 5835fe1add5SGreg Roach return $individual; 5845fe1add5SGreg Roach } 5851df7ae39SGreg Roach 58685a166d8SGreg Roach /** 58785a166d8SGreg Roach * Where do we store our media files. 58885a166d8SGreg Roach * 589f7cf8a15SGreg Roach * @param FilesystemOperator $data_filesystem 590a04bb9a2SGreg Roach * 591f7cf8a15SGreg Roach * @return FilesystemOperator 59285a166d8SGreg Roach */ 593f7cf8a15SGreg Roach public function mediaFilesystem(FilesystemOperator $data_filesystem): FilesystemOperator 5941df7ae39SGreg Roach { 595*8a07c98eSGreg Roach $media_dir = $this->getPreference('MEDIA_DIRECTORY'); 596a04bb9a2SGreg Roach $adapter = new ChrootAdapter($data_filesystem, $media_dir); 597456d0d35SGreg Roach 598456d0d35SGreg Roach return new Filesystem($adapter); 5991df7ae39SGreg Roach } 600a25f0a04SGreg Roach} 601