1a25f0a04SGreg Roach<?php 23976b470SGreg Roach 3a25f0a04SGreg Roach/** 4a25f0a04SGreg Roach * webtrees: online genealogy 5d11be702SGreg Roach * Copyright (C) 2023 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; 23e5a6b4d4SGreg Roachuse Fisharebest\Webtrees\Contracts\UserInterface; 2422e73debSGreg Roachuse Fisharebest\Webtrees\Services\PendingChangesService; 25afb591d7SGreg Roachuse InvalidArgumentException; 26f7cf8a15SGreg Roachuse League\Flysystem\FilesystemOperator; 27a25f0a04SGreg Roach 28dec352c1SGreg Roachuse function array_key_exists; 2953432476SGreg Roachuse function date; 30b55cbc6bSGreg Roachuse function is_string; 31dec352c1SGreg Roachuse function str_starts_with; 3253432476SGreg Roachuse function strtoupper; 33dec352c1SGreg Roachuse function substr_replace; 341e653452SGreg Roach 35a25f0a04SGreg Roach/** 3676692c8bSGreg Roach * Provide an interface to the wt_gedcom table. 37a25f0a04SGreg Roach */ 38c1010edaSGreg Roachclass Tree 39c1010edaSGreg Roach{ 40061b43d7SGreg Roach private const RESN_PRIVACY = [ 41061b43d7SGreg Roach 'none' => Auth::PRIV_PRIVATE, 42061b43d7SGreg Roach 'privacy' => Auth::PRIV_USER, 43061b43d7SGreg Roach 'confidential' => Auth::PRIV_NONE, 44061b43d7SGreg Roach 'hidden' => Auth::PRIV_HIDE, 45061b43d7SGreg Roach ]; 463df1e584SGreg Roach 478a07c98eSGreg Roach 488a07c98eSGreg Roach // Default values for some tree preferences. 498a07c98eSGreg Roach protected const DEFAULT_PREFERENCES = [ 508a07c98eSGreg Roach 'CALENDAR_FORMAT' => 'gregorian', 518a07c98eSGreg Roach 'CHART_BOX_TAGS' => '', 528a07c98eSGreg Roach 'EXPAND_SOURCES' => '0', 539c7bc1e3SGreg Roach 'FAM_FACTS_QUICK' => 'ENGA,MARR,DIV', 548a07c98eSGreg Roach 'FORMAT_TEXT' => 'markdown', 558a07c98eSGreg Roach 'GEDCOM_MEDIA_PATH' => '', 568a07c98eSGreg Roach 'GENERATE_UIDS' => '0', 578a07c98eSGreg Roach 'HIDE_GEDCOM_ERRORS' => '1', 588a07c98eSGreg Roach 'HIDE_LIVE_PEOPLE' => '1', 598a07c98eSGreg Roach 'INDI_FACTS_QUICK' => 'BIRT,BURI,BAPM,CENS,DEAT,OCCU,RESI', 608a07c98eSGreg Roach 'KEEP_ALIVE_YEARS_BIRTH' => '', 618a07c98eSGreg Roach 'KEEP_ALIVE_YEARS_DEATH' => '', 628a07c98eSGreg Roach 'LANGUAGE' => 'en-US', 638a07c98eSGreg Roach 'MAX_ALIVE_AGE' => '120', 648a07c98eSGreg Roach 'MEDIA_DIRECTORY' => 'media/', 659062008fSGreg Roach 'MEDIA_UPLOAD' => '1', // Auth::PRIV_USER 668a07c98eSGreg Roach 'META_DESCRIPTION' => '', 678a07c98eSGreg Roach 'META_TITLE' => Webtrees::NAME, 688a07c98eSGreg Roach 'NO_UPDATE_CHAN' => '0', 698a07c98eSGreg Roach 'PEDIGREE_ROOT_ID' => '', 708a07c98eSGreg Roach 'QUICK_REQUIRED_FACTS' => 'BIRT,DEAT', 718a07c98eSGreg Roach 'QUICK_REQUIRED_FAMFACTS' => 'MARR', 728a07c98eSGreg Roach 'REQUIRE_AUTHENTICATION' => '0', 738a07c98eSGreg Roach 'SAVE_WATERMARK_IMAGE' => '0', 748a07c98eSGreg Roach 'SHOW_AGE_DIFF' => '0', 758a07c98eSGreg Roach 'SHOW_COUNTER' => '1', 769062008fSGreg Roach 'SHOW_DEAD_PEOPLE' => '2', // Auth::PRIV_PRIVATE 778a07c98eSGreg Roach 'SHOW_EST_LIST_DATES' => '0', 788a07c98eSGreg Roach 'SHOW_FACT_ICONS' => '1', 798a07c98eSGreg Roach 'SHOW_GEDCOM_RECORD' => '0', 808a07c98eSGreg Roach 'SHOW_HIGHLIGHT_IMAGES' => '1', 818a07c98eSGreg Roach 'SHOW_LEVEL2_NOTES' => '1', 829062008fSGreg Roach 'SHOW_LIVING_NAMES' => '1', // Auth::PRIV_USER 838a07c98eSGreg Roach 'SHOW_MEDIA_DOWNLOAD' => '0', 849062008fSGreg Roach 'SHOW_NO_WATERMARK' => '1', // Auth::PRIV_USER 858a07c98eSGreg Roach 'SHOW_PARENTS_AGE' => '1', 868a07c98eSGreg Roach 'SHOW_PEDIGREE_PLACES' => '9', 878a07c98eSGreg Roach 'SHOW_PEDIGREE_PLACES_SUFFIX' => '0', 888a07c98eSGreg Roach 'SHOW_PRIVATE_RELATIONSHIPS' => '1', 898a07c98eSGreg Roach 'SHOW_RELATIVES_EVENTS' => '_BIRT_CHIL,_BIRT_SIBL,_MARR_CHIL,_MARR_PARE,_DEAT_CHIL,_DEAT_PARE,_DEAT_GPAR,_DEAT_SIBL,_DEAT_SPOU', 908a07c98eSGreg Roach 'SUBLIST_TRIGGER_I' => '200', 918a07c98eSGreg Roach 'SURNAME_LIST_STYLE' => 'style2', 928a07c98eSGreg Roach 'SURNAME_TRADITION' => 'paternal', 938a07c98eSGreg Roach 'USE_SILHOUETTE' => '1', 948a07c98eSGreg Roach 'WORD_WRAPPED_NOTES' => '0', 958a07c98eSGreg Roach ]; 968a07c98eSGreg Roach 974c78e066SGreg Roach private int $id; 983df1e584SGreg Roach 994c78e066SGreg Roach private string $name; 1003df1e584SGreg Roach 1014c78e066SGreg Roach private string $title; 1023df1e584SGreg Roach 1034c78e066SGreg Roach /** @var array<int> Default access rules for facts in this tree */ 1044c78e066SGreg Roach private array $fact_privacy; 1053df1e584SGreg Roach 1064c78e066SGreg Roach /** @var array<int> Default access rules for individuals in this tree */ 1074c78e066SGreg Roach private array $individual_privacy; 1083df1e584SGreg Roach 1094c78e066SGreg Roach /** @var array<array<int>> Default access rules for individual facts in this tree */ 1104c78e066SGreg Roach private array $individual_fact_privacy; 1113df1e584SGreg Roach 11209482a55SGreg Roach /** @var array<string> Cached copy of the wt_gedcom_setting table. */ 1135f2657cbSGreg Roach private array $preferences = []; 1143df1e584SGreg Roach 11509482a55SGreg Roach /** @var array<array<string>> Cached copy of the wt_user_gedcom_setting table. */ 1164c78e066SGreg Roach private array $user_preferences = []; 117061b43d7SGreg Roach 118a25f0a04SGreg Roach /** 1193df1e584SGreg Roach * Create a tree object. 120a25f0a04SGreg Roach * 12172cf66d4SGreg Roach * @param int $id 122aa6f03bbSGreg Roach * @param string $name 123cc13d6d8SGreg Roach * @param string $title 124a25f0a04SGreg Roach */ 1255afbc57aSGreg Roach public function __construct(int $id, string $name, string $title) 126c1010edaSGreg Roach { 12772cf66d4SGreg Roach $this->id = $id; 128aa6f03bbSGreg Roach $this->name = $name; 129cc13d6d8SGreg Roach $this->title = $title; 13013abd6f3SGreg Roach $this->fact_privacy = []; 13113abd6f3SGreg Roach $this->individual_privacy = []; 13213abd6f3SGreg Roach $this->individual_fact_privacy = []; 133518bbdc1SGreg Roach 134518bbdc1SGreg Roach // Load the privacy settings for this tree 135061b43d7SGreg Roach $rows = DB::table('default_resn') 136061b43d7SGreg Roach ->where('gedcom_id', '=', $this->id) 137061b43d7SGreg Roach ->get(); 138518bbdc1SGreg Roach 139518bbdc1SGreg Roach foreach ($rows as $row) { 140061b43d7SGreg Roach // Convert GEDCOM privacy restriction to a webtrees access level. 141061b43d7SGreg Roach $row->resn = self::RESN_PRIVACY[$row->resn]; 142061b43d7SGreg Roach 143518bbdc1SGreg Roach if ($row->xref !== null) { 144518bbdc1SGreg Roach if ($row->tag_type !== null) { 145b262b3d3SGreg Roach $this->individual_fact_privacy[$row->xref][$row->tag_type] = $row->resn; 146518bbdc1SGreg Roach } else { 147b262b3d3SGreg Roach $this->individual_privacy[$row->xref] = $row->resn; 148518bbdc1SGreg Roach } 149518bbdc1SGreg Roach } else { 150b262b3d3SGreg Roach $this->fact_privacy[$row->tag_type] = $row->resn; 151518bbdc1SGreg Roach } 152518bbdc1SGreg Roach } 153a25f0a04SGreg Roach } 154a25f0a04SGreg Roach 155a25f0a04SGreg Roach /** 1565afbc57aSGreg Roach * A closure which will create a record from a database row. 1575afbc57aSGreg Roach * 158c6921a17SGreg Roach * @return Closure(object):Tree 1595afbc57aSGreg Roach */ 1605afbc57aSGreg Roach public static function rowMapper(): Closure 1615afbc57aSGreg Roach { 162743491b8SGreg Roach return static fn (object $row): Tree => new Tree((int) $row->tree_id, $row->tree_name, $row->tree_title); 1635afbc57aSGreg Roach } 1645afbc57aSGreg Roach 1655afbc57aSGreg Roach /** 1666ccdf4f0SGreg Roach * Set the tree’s configuration settings. 1676ccdf4f0SGreg Roach * 1686ccdf4f0SGreg Roach * @param string $setting_name 1696ccdf4f0SGreg Roach * @param string $setting_value 1706ccdf4f0SGreg Roach * 1716612c384SGreg Roach * @return self 1726ccdf4f0SGreg Roach */ 1736ccdf4f0SGreg Roach public function setPreference(string $setting_name, string $setting_value): Tree 1746ccdf4f0SGreg Roach { 1756ccdf4f0SGreg Roach if ($setting_value !== $this->getPreference($setting_name)) { 1766ccdf4f0SGreg Roach DB::table('gedcom_setting')->updateOrInsert([ 1776ccdf4f0SGreg Roach 'gedcom_id' => $this->id, 1786ccdf4f0SGreg Roach 'setting_name' => $setting_name, 1796ccdf4f0SGreg Roach ], [ 1806ccdf4f0SGreg Roach 'setting_value' => $setting_value, 1816ccdf4f0SGreg Roach ]); 1826ccdf4f0SGreg Roach 1836ccdf4f0SGreg Roach $this->preferences[$setting_name] = $setting_value; 1846ccdf4f0SGreg Roach 1856ccdf4f0SGreg Roach Log::addConfigurationLog('Tree preference "' . $setting_name . '" set to "' . $setting_value . '"', $this); 1866ccdf4f0SGreg Roach } 1876ccdf4f0SGreg Roach 1886ccdf4f0SGreg Roach return $this; 1896ccdf4f0SGreg Roach } 1906ccdf4f0SGreg Roach 1916ccdf4f0SGreg Roach /** 1926ccdf4f0SGreg Roach * Get the tree’s configuration settings. 1936ccdf4f0SGreg Roach * 1946ccdf4f0SGreg Roach * @param string $setting_name 1959062008fSGreg Roach * @param string|null $default 1966ccdf4f0SGreg Roach * 1976ccdf4f0SGreg Roach * @return string 1986ccdf4f0SGreg Roach */ 199*2c6f1bd5SGreg Roach public function getPreference(string $setting_name, string|null $default = null): string 2006ccdf4f0SGreg Roach { 20154c1ab5eSGreg Roach if ($this->preferences === []) { 2026ccdf4f0SGreg Roach $this->preferences = DB::table('gedcom_setting') 2036ccdf4f0SGreg Roach ->where('gedcom_id', '=', $this->id) 2046ccdf4f0SGreg Roach ->pluck('setting_value', 'setting_name') 2056ccdf4f0SGreg Roach ->all(); 2066ccdf4f0SGreg Roach } 2076ccdf4f0SGreg Roach 2089062008fSGreg Roach return $this->preferences[$setting_name] ?? $default ?? self::DEFAULT_PREFERENCES[$setting_name] ?? ''; 2096ccdf4f0SGreg Roach } 2106ccdf4f0SGreg Roach 2116ccdf4f0SGreg Roach /** 2126ccdf4f0SGreg Roach * The name of this tree 2136ccdf4f0SGreg Roach * 2146ccdf4f0SGreg Roach * @return string 2156ccdf4f0SGreg Roach */ 2166ccdf4f0SGreg Roach public function name(): string 2176ccdf4f0SGreg Roach { 2186ccdf4f0SGreg Roach return $this->name; 2196ccdf4f0SGreg Roach } 2206ccdf4f0SGreg Roach 2216ccdf4f0SGreg Roach /** 2226ccdf4f0SGreg Roach * The title of this tree 2236ccdf4f0SGreg Roach * 2246ccdf4f0SGreg Roach * @return string 2256ccdf4f0SGreg Roach */ 2266ccdf4f0SGreg Roach public function title(): string 2276ccdf4f0SGreg Roach { 2286ccdf4f0SGreg Roach return $this->title; 2296ccdf4f0SGreg Roach } 2306ccdf4f0SGreg Roach 2316ccdf4f0SGreg Roach /** 2326ccdf4f0SGreg Roach * The fact-level privacy for this tree. 2336ccdf4f0SGreg Roach * 23409482a55SGreg Roach * @return array<int> 2356ccdf4f0SGreg Roach */ 2366ccdf4f0SGreg Roach public function getFactPrivacy(): array 2376ccdf4f0SGreg Roach { 2386ccdf4f0SGreg Roach return $this->fact_privacy; 2396ccdf4f0SGreg Roach } 2406ccdf4f0SGreg Roach 2416ccdf4f0SGreg Roach /** 2426ccdf4f0SGreg Roach * The individual-level privacy for this tree. 2436ccdf4f0SGreg Roach * 24409482a55SGreg Roach * @return array<int> 2456ccdf4f0SGreg Roach */ 2466ccdf4f0SGreg Roach public function getIndividualPrivacy(): array 2476ccdf4f0SGreg Roach { 2486ccdf4f0SGreg Roach return $this->individual_privacy; 2496ccdf4f0SGreg Roach } 2506ccdf4f0SGreg Roach 2516ccdf4f0SGreg Roach /** 2526ccdf4f0SGreg Roach * The individual-fact-level privacy for this tree. 2536ccdf4f0SGreg Roach * 25409482a55SGreg Roach * @return array<array<int>> 2556ccdf4f0SGreg Roach */ 2566ccdf4f0SGreg Roach public function getIndividualFactPrivacy(): array 2576ccdf4f0SGreg Roach { 2586ccdf4f0SGreg Roach return $this->individual_fact_privacy; 2596ccdf4f0SGreg Roach } 2606ccdf4f0SGreg Roach 2616ccdf4f0SGreg Roach /** 2626ccdf4f0SGreg Roach * Set the tree’s user-configuration settings. 2636ccdf4f0SGreg Roach * 2646ccdf4f0SGreg Roach * @param UserInterface $user 2656ccdf4f0SGreg Roach * @param string $setting_name 2666ccdf4f0SGreg Roach * @param string $setting_value 2676ccdf4f0SGreg Roach * 2686612c384SGreg Roach * @return self 2696ccdf4f0SGreg Roach */ 2706ccdf4f0SGreg Roach public function setUserPreference(UserInterface $user, string $setting_name, string $setting_value): Tree 2716ccdf4f0SGreg Roach { 2726ccdf4f0SGreg Roach if ($this->getUserPreference($user, $setting_name) !== $setting_value) { 2736ccdf4f0SGreg Roach // Update the database 2746ccdf4f0SGreg Roach DB::table('user_gedcom_setting')->updateOrInsert([ 2756ccdf4f0SGreg Roach 'gedcom_id' => $this->id(), 2766ccdf4f0SGreg Roach 'user_id' => $user->id(), 2776ccdf4f0SGreg Roach 'setting_name' => $setting_name, 2786ccdf4f0SGreg Roach ], [ 2796ccdf4f0SGreg Roach 'setting_value' => $setting_value, 2806ccdf4f0SGreg Roach ]); 2816ccdf4f0SGreg Roach 2826ccdf4f0SGreg Roach // Update the cache 2836ccdf4f0SGreg Roach $this->user_preferences[$user->id()][$setting_name] = $setting_value; 2846ccdf4f0SGreg Roach // Audit log of changes 2856ccdf4f0SGreg Roach Log::addConfigurationLog('Tree preference "' . $setting_name . '" set to "' . $setting_value . '" for user "' . $user->userName() . '"', $this); 2866ccdf4f0SGreg Roach } 2876ccdf4f0SGreg Roach 2886ccdf4f0SGreg Roach return $this; 2896ccdf4f0SGreg Roach } 2906ccdf4f0SGreg Roach 2916ccdf4f0SGreg Roach /** 2926ccdf4f0SGreg Roach * Get the tree’s user-configuration settings. 2936ccdf4f0SGreg Roach * 2946ccdf4f0SGreg Roach * @param UserInterface $user 2956ccdf4f0SGreg Roach * @param string $setting_name 2966ccdf4f0SGreg Roach * @param string $default 2976ccdf4f0SGreg Roach * 2986ccdf4f0SGreg Roach * @return string 2996ccdf4f0SGreg Roach */ 3006ccdf4f0SGreg Roach public function getUserPreference(UserInterface $user, string $setting_name, string $default = ''): string 3016ccdf4f0SGreg Roach { 3026ccdf4f0SGreg Roach // There are lots of settings, and we need to fetch lots of them on every page 3036ccdf4f0SGreg Roach // so it is quicker to fetch them all in one go. 3046ccdf4f0SGreg Roach if (!array_key_exists($user->id(), $this->user_preferences)) { 3056ccdf4f0SGreg Roach $this->user_preferences[$user->id()] = DB::table('user_gedcom_setting') 3066ccdf4f0SGreg Roach ->where('user_id', '=', $user->id()) 3076ccdf4f0SGreg Roach ->where('gedcom_id', '=', $this->id) 3086ccdf4f0SGreg Roach ->pluck('setting_value', 'setting_name') 3096ccdf4f0SGreg Roach ->all(); 3106ccdf4f0SGreg Roach } 3116ccdf4f0SGreg Roach 3126ccdf4f0SGreg Roach return $this->user_preferences[$user->id()][$setting_name] ?? $default; 3136ccdf4f0SGreg Roach } 3146ccdf4f0SGreg Roach 3156ccdf4f0SGreg Roach /** 3166ccdf4f0SGreg Roach * The ID of this tree 3176ccdf4f0SGreg Roach * 3186ccdf4f0SGreg Roach * @return int 3196ccdf4f0SGreg Roach */ 3206ccdf4f0SGreg Roach public function id(): int 3216ccdf4f0SGreg Roach { 3226ccdf4f0SGreg Roach return $this->id; 3236ccdf4f0SGreg Roach } 3246ccdf4f0SGreg Roach 3256ccdf4f0SGreg Roach /** 3266ccdf4f0SGreg Roach * Can a user accept changes for this tree? 3276ccdf4f0SGreg Roach * 3286ccdf4f0SGreg Roach * @param UserInterface $user 3296ccdf4f0SGreg Roach * 3306ccdf4f0SGreg Roach * @return bool 3316ccdf4f0SGreg Roach */ 3326ccdf4f0SGreg Roach public function canAcceptChanges(UserInterface $user): bool 3336ccdf4f0SGreg Roach { 3346ccdf4f0SGreg Roach return Auth::isModerator($this, $user); 3356ccdf4f0SGreg Roach } 3366ccdf4f0SGreg Roach 3376ccdf4f0SGreg Roach /** 3380474d79bSGreg Roach * Are there any pending edits for this tree, that need reviewing by a moderator. 339b78374c5SGreg Roach * 340b78374c5SGreg Roach * @return bool 341b78374c5SGreg Roach */ 342771ae10aSGreg Roach public function hasPendingEdit(): bool 343c1010edaSGreg Roach { 34415a3f100SGreg Roach return DB::table('change') 34515a3f100SGreg Roach ->where('gedcom_id', '=', $this->id) 34615a3f100SGreg Roach ->where('status', '=', 'pending') 34715a3f100SGreg Roach ->exists(); 348b78374c5SGreg Roach } 349b78374c5SGreg Roach 350b78374c5SGreg Roach /** 3516ccdf4f0SGreg Roach * Create a new record from GEDCOM data. 3526ccdf4f0SGreg Roach * 3536ccdf4f0SGreg Roach * @param string $gedcom 3546ccdf4f0SGreg Roach * 3550d15532eSGreg Roach * @return GedcomRecord|Individual|Family|Location|Note|Source|Repository|Media|Submitter|Submission 3566ccdf4f0SGreg Roach * @throws InvalidArgumentException 3576ccdf4f0SGreg Roach */ 3586ccdf4f0SGreg Roach public function createRecord(string $gedcom): GedcomRecord 3596ccdf4f0SGreg Roach { 360ef475b14SGreg Roach if (preg_match('/^0 @@ ([_A-Z]+)/', $gedcom, $match) !== 1) { 3616ccdf4f0SGreg Roach throw new InvalidArgumentException('GedcomRecord::createRecord(' . $gedcom . ') does not begin 0 @@'); 3626ccdf4f0SGreg Roach } 3636ccdf4f0SGreg Roach 3646b9cb339SGreg Roach $xref = Registry::xrefFactory()->make($match[1]); 365dec352c1SGreg Roach $gedcom = substr_replace($gedcom, $xref, 3, 0); 3666ccdf4f0SGreg Roach 3676ccdf4f0SGreg Roach // Create a change record 36853432476SGreg Roach $today = strtoupper(date('d M Y')); 36953432476SGreg Roach $now = date('H:i:s'); 37053432476SGreg Roach $gedcom .= "\n1 CHAN\n2 DATE " . $today . "\n3 TIME " . $now . "\n2 _WT_USER " . Auth::user()->userName(); 3716ccdf4f0SGreg Roach 3726ccdf4f0SGreg Roach // Create a pending change 3736ccdf4f0SGreg Roach DB::table('change')->insert([ 3746ccdf4f0SGreg Roach 'gedcom_id' => $this->id, 3756ccdf4f0SGreg Roach 'xref' => $xref, 3766ccdf4f0SGreg Roach 'old_gedcom' => '', 3776ccdf4f0SGreg Roach 'new_gedcom' => $gedcom, 378c3600e89SGreg Roach 'status' => 'pending', 3796ccdf4f0SGreg Roach 'user_id' => Auth::id(), 3806ccdf4f0SGreg Roach ]); 3816ccdf4f0SGreg Roach 3826ccdf4f0SGreg Roach // Accept this pending change 3831fe542e9SGreg Roach if (Auth::user()->getPreference(UserInterface::PREF_AUTO_ACCEPT_EDITS) === '1') { 3846b9cb339SGreg Roach $record = Registry::gedcomRecordFactory()->new($xref, $gedcom, null, $this); 3856ccdf4f0SGreg Roach 386d35568b4SGreg Roach $pending_changes_service = Registry::container()->get(PendingChangesService::class); 387b55cbc6bSGreg Roach $pending_changes_service->acceptRecord($record); 38822e73debSGreg Roach 38922e73debSGreg Roach return $record; 3906ccdf4f0SGreg Roach } 3916ccdf4f0SGreg Roach 3926b9cb339SGreg Roach return Registry::gedcomRecordFactory()->new($xref, '', $gedcom, $this); 393a25f0a04SGreg Roach } 394304f20d5SGreg Roach 395304f20d5SGreg Roach /** 396afb591d7SGreg Roach * Create a new family from GEDCOM data. 397afb591d7SGreg Roach * 398afb591d7SGreg Roach * @param string $gedcom 399afb591d7SGreg Roach * 400afb591d7SGreg Roach * @return Family 401afb591d7SGreg Roach * @throws InvalidArgumentException 402afb591d7SGreg Roach */ 403afb591d7SGreg Roach public function createFamily(string $gedcom): GedcomRecord 404afb591d7SGreg Roach { 405dec352c1SGreg Roach if (!str_starts_with($gedcom, '0 @@ FAM')) { 406afb591d7SGreg Roach throw new InvalidArgumentException('GedcomRecord::createFamily(' . $gedcom . ') does not begin 0 @@ FAM'); 407afb591d7SGreg Roach } 408afb591d7SGreg Roach 4096b9cb339SGreg Roach $xref = Registry::xrefFactory()->make(Family::RECORD_TYPE); 410dec352c1SGreg Roach $gedcom = substr_replace($gedcom, $xref, 3, 0); 411afb591d7SGreg Roach 412afb591d7SGreg Roach // Create a change record 41353432476SGreg Roach $today = strtoupper(date('d M Y')); 41453432476SGreg Roach $now = date('H:i:s'); 41553432476SGreg Roach $gedcom .= "\n1 CHAN\n2 DATE " . $today . "\n3 TIME " . $now . "\n2 _WT_USER " . Auth::user()->userName(); 416afb591d7SGreg Roach 417afb591d7SGreg Roach // Create a pending change 418963fbaeeSGreg Roach DB::table('change')->insert([ 419963fbaeeSGreg Roach 'gedcom_id' => $this->id, 420963fbaeeSGreg Roach 'xref' => $xref, 421963fbaeeSGreg Roach 'old_gedcom' => '', 422963fbaeeSGreg Roach 'new_gedcom' => $gedcom, 423c3600e89SGreg Roach 'status' => 'pending', 424963fbaeeSGreg Roach 'user_id' => Auth::id(), 425afb591d7SGreg Roach ]); 426304f20d5SGreg Roach 427304f20d5SGreg Roach // Accept this pending change 4281fe542e9SGreg Roach if (Auth::user()->getPreference(UserInterface::PREF_AUTO_ACCEPT_EDITS) === '1') { 4296b9cb339SGreg Roach $record = Registry::familyFactory()->new($xref, $gedcom, null, $this); 430afb591d7SGreg Roach 431d35568b4SGreg Roach $pending_changes_service = Registry::container()->get(PendingChangesService::class); 432b55cbc6bSGreg Roach $pending_changes_service->acceptRecord($record); 43322e73debSGreg Roach 43422e73debSGreg Roach return $record; 435304f20d5SGreg Roach } 436afb591d7SGreg Roach 4376b9cb339SGreg Roach return Registry::familyFactory()->new($xref, '', $gedcom, $this); 438afb591d7SGreg Roach } 439afb591d7SGreg Roach 440afb591d7SGreg Roach /** 441afb591d7SGreg Roach * Create a new individual from GEDCOM data. 442afb591d7SGreg Roach * 443afb591d7SGreg Roach * @param string $gedcom 444afb591d7SGreg Roach * 445afb591d7SGreg Roach * @return Individual 446afb591d7SGreg Roach * @throws InvalidArgumentException 447afb591d7SGreg Roach */ 448afb591d7SGreg Roach public function createIndividual(string $gedcom): GedcomRecord 449afb591d7SGreg Roach { 450dec352c1SGreg Roach if (!str_starts_with($gedcom, '0 @@ INDI')) { 451afb591d7SGreg Roach throw new InvalidArgumentException('GedcomRecord::createIndividual(' . $gedcom . ') does not begin 0 @@ INDI'); 452afb591d7SGreg Roach } 453afb591d7SGreg Roach 4546b9cb339SGreg Roach $xref = Registry::xrefFactory()->make(Individual::RECORD_TYPE); 455dec352c1SGreg Roach $gedcom = substr_replace($gedcom, $xref, 3, 0); 456afb591d7SGreg Roach 457afb591d7SGreg Roach // Create a change record 45853432476SGreg Roach $today = strtoupper(date('d M Y')); 45953432476SGreg Roach $now = date('H:i:s'); 46053432476SGreg Roach $gedcom .= "\n1 CHAN\n2 DATE " . $today . "\n3 TIME " . $now . "\n2 _WT_USER " . Auth::user()->userName(); 461afb591d7SGreg Roach 462afb591d7SGreg Roach // Create a pending change 463963fbaeeSGreg Roach DB::table('change')->insert([ 464963fbaeeSGreg Roach 'gedcom_id' => $this->id, 465963fbaeeSGreg Roach 'xref' => $xref, 466963fbaeeSGreg Roach 'old_gedcom' => '', 467963fbaeeSGreg Roach 'new_gedcom' => $gedcom, 468c3600e89SGreg Roach 'status' => 'pending', 469963fbaeeSGreg Roach 'user_id' => Auth::id(), 470afb591d7SGreg Roach ]); 471afb591d7SGreg Roach 472afb591d7SGreg Roach // Accept this pending change 4731fe542e9SGreg Roach if (Auth::user()->getPreference(UserInterface::PREF_AUTO_ACCEPT_EDITS) === '1') { 4746b9cb339SGreg Roach $record = Registry::individualFactory()->new($xref, $gedcom, null, $this); 475afb591d7SGreg Roach 476d35568b4SGreg Roach $pending_changes_service = Registry::container()->get(PendingChangesService::class); 477b55cbc6bSGreg Roach $pending_changes_service->acceptRecord($record); 47822e73debSGreg Roach 47922e73debSGreg Roach return $record; 480afb591d7SGreg Roach } 481afb591d7SGreg Roach 4826b9cb339SGreg Roach return Registry::individualFactory()->new($xref, '', $gedcom, $this); 483304f20d5SGreg Roach } 4848586983fSGreg Roach 4858586983fSGreg Roach /** 48620b58d20SGreg Roach * Create a new media object from GEDCOM data. 48720b58d20SGreg Roach * 48820b58d20SGreg Roach * @param string $gedcom 48920b58d20SGreg Roach * 49020b58d20SGreg Roach * @return Media 49120b58d20SGreg Roach * @throws InvalidArgumentException 49220b58d20SGreg Roach */ 49320b58d20SGreg Roach public function createMediaObject(string $gedcom): Media 49420b58d20SGreg Roach { 495dec352c1SGreg Roach if (!str_starts_with($gedcom, '0 @@ OBJE')) { 49620b58d20SGreg Roach throw new InvalidArgumentException('GedcomRecord::createIndividual(' . $gedcom . ') does not begin 0 @@ OBJE'); 49720b58d20SGreg Roach } 49820b58d20SGreg Roach 4996b9cb339SGreg Roach $xref = Registry::xrefFactory()->make(Media::RECORD_TYPE); 500dec352c1SGreg Roach $gedcom = substr_replace($gedcom, $xref, 3, 0); 50120b58d20SGreg Roach 50220b58d20SGreg Roach // Create a change record 50353432476SGreg Roach $today = strtoupper(date('d M Y')); 50453432476SGreg Roach $now = date('H:i:s'); 50553432476SGreg Roach $gedcom .= "\n1 CHAN\n2 DATE " . $today . "\n3 TIME " . $now . "\n2 _WT_USER " . Auth::user()->userName(); 50620b58d20SGreg Roach 50720b58d20SGreg Roach // Create a pending change 508963fbaeeSGreg Roach DB::table('change')->insert([ 509963fbaeeSGreg Roach 'gedcom_id' => $this->id, 510963fbaeeSGreg Roach 'xref' => $xref, 511963fbaeeSGreg Roach 'old_gedcom' => '', 512963fbaeeSGreg Roach 'new_gedcom' => $gedcom, 513c3600e89SGreg Roach 'status' => 'pending', 514963fbaeeSGreg Roach 'user_id' => Auth::id(), 51520b58d20SGreg Roach ]); 51620b58d20SGreg Roach 51720b58d20SGreg Roach // Accept this pending change 5181fe542e9SGreg Roach if (Auth::user()->getPreference(UserInterface::PREF_AUTO_ACCEPT_EDITS) === '1') { 5196b9cb339SGreg Roach $record = Registry::mediaFactory()->new($xref, $gedcom, null, $this); 52020b58d20SGreg Roach 521d35568b4SGreg Roach $pending_changes_service = Registry::container()->get(PendingChangesService::class); 522b55cbc6bSGreg Roach $pending_changes_service->acceptRecord($record); 52322e73debSGreg Roach 52422e73debSGreg Roach return $record; 52520b58d20SGreg Roach } 52620b58d20SGreg Roach 5276b9cb339SGreg Roach return Registry::mediaFactory()->new($xref, '', $gedcom, $this); 52820b58d20SGreg Roach } 52920b58d20SGreg Roach 53020b58d20SGreg Roach /** 5318586983fSGreg Roach * What is the most significant individual in this tree. 5328586983fSGreg Roach * 533e5a6b4d4SGreg Roach * @param UserInterface $user 5343370567dSGreg Roach * @param string $xref 5358586983fSGreg Roach * 5368586983fSGreg Roach * @return Individual 5378586983fSGreg Roach */ 53873d58381SGreg Roach public function significantIndividual(UserInterface $user, string $xref = ''): Individual 539c1010edaSGreg Roach { 5403370567dSGreg Roach if ($xref === '') { 5418f9b0fb2SGreg Roach $individual = null; 5423370567dSGreg Roach } else { 5436b9cb339SGreg Roach $individual = Registry::individualFactory()->make($xref, $this); 5443370567dSGreg Roach 5453370567dSGreg Roach if ($individual === null) { 5466b9cb339SGreg Roach $family = Registry::familyFactory()->make($xref, $this); 5473370567dSGreg Roach 5483370567dSGreg Roach if ($family instanceof Family) { 5493370567dSGreg Roach $individual = $family->spouses()->first() ?? $family->children()->first(); 5503370567dSGreg Roach } 5513370567dSGreg Roach } 5523370567dSGreg Roach } 5538586983fSGreg Roach 5541fe542e9SGreg Roach if ($individual === null && $this->getUserPreference($user, UserInterface::PREF_TREE_DEFAULT_XREF) !== '') { 5551fe542e9SGreg Roach $individual = Registry::individualFactory()->make($this->getUserPreference($user, UserInterface::PREF_TREE_DEFAULT_XREF), $this); 5568586983fSGreg Roach } 5578f9b0fb2SGreg Roach 5581fe542e9SGreg Roach if ($individual === null && $this->getUserPreference($user, UserInterface::PREF_TREE_ACCOUNT_XREF) !== '') { 5591fe542e9SGreg Roach $individual = Registry::individualFactory()->make($this->getUserPreference($user, UserInterface::PREF_TREE_ACCOUNT_XREF), $this); 5608586983fSGreg Roach } 5618f9b0fb2SGreg Roach 562bec87e94SGreg Roach if ($individual === null && $this->getPreference('PEDIGREE_ROOT_ID') !== '') { 5636b9cb339SGreg Roach $individual = Registry::individualFactory()->make($this->getPreference('PEDIGREE_ROOT_ID'), $this); 5648586983fSGreg Roach } 5658f9b0fb2SGreg Roach if ($individual === null) { 566b55cbc6bSGreg Roach $xref = DB::table('individuals') 5678f9b0fb2SGreg Roach ->where('i_file', '=', $this->id()) 5688f9b0fb2SGreg Roach ->min('i_id'); 569769d7d6eSGreg Roach 570b55cbc6bSGreg Roach if (is_string($xref)) { 5716b9cb339SGreg Roach $individual = Registry::individualFactory()->make($xref, $this); 5725fe1add5SGreg Roach } 573b55cbc6bSGreg Roach } 5748f9b0fb2SGreg Roach if ($individual === null) { 5755fe1add5SGreg Roach // always return a record 5766b9cb339SGreg Roach $individual = Registry::individualFactory()->new('I', '0 @I@ INDI', null, $this); 5775fe1add5SGreg Roach } 5785fe1add5SGreg Roach 5795fe1add5SGreg Roach return $individual; 5805fe1add5SGreg Roach } 5811df7ae39SGreg Roach 58285a166d8SGreg Roach /** 58385a166d8SGreg Roach * Where do we store our media files. 58485a166d8SGreg Roach * 585f7cf8a15SGreg Roach * @return FilesystemOperator 58685a166d8SGreg Roach */ 5879458f20aSGreg Roach public function mediaFilesystem(): FilesystemOperator 5881df7ae39SGreg Roach { 5899458f20aSGreg Roach return Registry::filesystem()->data($this->getPreference('MEDIA_DIRECTORY')); 5901df7ae39SGreg Roach } 591a25f0a04SGreg Roach} 592