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; 2569c05a6eSGreg Roachuse Fisharebest\Webtrees\Services\GedcomExportService; 2622e73debSGreg Roachuse Fisharebest\Webtrees\Services\PendingChangesService; 275cd281f4SGreg Roachuse Fisharebest\Webtrees\Services\TreeService; 2801461f86SGreg Roachuse Illuminate\Database\Capsule\Manager as DB; 29afb591d7SGreg Roachuse InvalidArgumentException; 301df7ae39SGreg Roachuse League\Flysystem\Filesystem; 31*f7cf8a15SGreg Roachuse League\Flysystem\FilesystemOperator; 326ccdf4f0SGreg Roachuse Psr\Http\Message\StreamInterface; 338b67c11aSGreg Roachuse stdClass; 34a25f0a04SGreg Roach 351e653452SGreg Roachuse function app; 36dec352c1SGreg Roachuse function array_key_exists; 3753432476SGreg Roachuse function date; 38dec352c1SGreg Roachuse function str_starts_with; 39dec352c1SGreg Roachuse function strlen; 4053432476SGreg Roachuse function strtoupper; 41dec352c1SGreg Roachuse function substr; 42dec352c1SGreg Roachuse function substr_replace; 431e653452SGreg Roach 44a25f0a04SGreg Roach/** 4576692c8bSGreg Roach * Provide an interface to the wt_gedcom table. 46a25f0a04SGreg Roach */ 47c1010edaSGreg Roachclass Tree 48c1010edaSGreg Roach{ 49061b43d7SGreg Roach private const RESN_PRIVACY = [ 50061b43d7SGreg Roach 'none' => Auth::PRIV_PRIVATE, 51061b43d7SGreg Roach 'privacy' => Auth::PRIV_USER, 52061b43d7SGreg Roach 'confidential' => Auth::PRIV_NONE, 53061b43d7SGreg Roach 'hidden' => Auth::PRIV_HIDE, 54061b43d7SGreg Roach ]; 553df1e584SGreg Roach 566ccdf4f0SGreg Roach /** @var int The tree's ID number */ 576ccdf4f0SGreg Roach private $id; 583df1e584SGreg Roach 596ccdf4f0SGreg Roach /** @var string The tree's name */ 606ccdf4f0SGreg Roach private $name; 613df1e584SGreg Roach 626ccdf4f0SGreg Roach /** @var string The tree's title */ 636ccdf4f0SGreg Roach private $title; 643df1e584SGreg Roach 656ccdf4f0SGreg Roach /** @var int[] Default access rules for facts in this tree */ 666ccdf4f0SGreg Roach private $fact_privacy; 673df1e584SGreg Roach 686ccdf4f0SGreg Roach /** @var int[] Default access rules for individuals in this tree */ 696ccdf4f0SGreg Roach private $individual_privacy; 703df1e584SGreg Roach 716ccdf4f0SGreg Roach /** @var integer[][] Default access rules for individual facts in this tree */ 726ccdf4f0SGreg Roach private $individual_fact_privacy; 733df1e584SGreg Roach 746ccdf4f0SGreg Roach /** @var string[] Cached copy of the wt_gedcom_setting table. */ 756ccdf4f0SGreg Roach private $preferences = []; 763df1e584SGreg Roach 776ccdf4f0SGreg Roach /** @var string[][] Cached copy of the wt_user_gedcom_setting table. */ 786ccdf4f0SGreg Roach private $user_preferences = []; 79061b43d7SGreg Roach 80a25f0a04SGreg Roach /** 813df1e584SGreg Roach * Create a tree object. 82a25f0a04SGreg Roach * 8372cf66d4SGreg Roach * @param int $id 84aa6f03bbSGreg Roach * @param string $name 85cc13d6d8SGreg Roach * @param string $title 86a25f0a04SGreg Roach */ 875afbc57aSGreg Roach public function __construct(int $id, string $name, string $title) 88c1010edaSGreg Roach { 8972cf66d4SGreg Roach $this->id = $id; 90aa6f03bbSGreg Roach $this->name = $name; 91cc13d6d8SGreg Roach $this->title = $title; 9213abd6f3SGreg Roach $this->fact_privacy = []; 9313abd6f3SGreg Roach $this->individual_privacy = []; 9413abd6f3SGreg Roach $this->individual_fact_privacy = []; 95518bbdc1SGreg Roach 96518bbdc1SGreg Roach // Load the privacy settings for this tree 97061b43d7SGreg Roach $rows = DB::table('default_resn') 98061b43d7SGreg Roach ->where('gedcom_id', '=', $this->id) 99061b43d7SGreg Roach ->get(); 100518bbdc1SGreg Roach 101518bbdc1SGreg Roach foreach ($rows as $row) { 102061b43d7SGreg Roach // Convert GEDCOM privacy restriction to a webtrees access level. 103061b43d7SGreg Roach $row->resn = self::RESN_PRIVACY[$row->resn]; 104061b43d7SGreg Roach 105518bbdc1SGreg Roach if ($row->xref !== null) { 106518bbdc1SGreg Roach if ($row->tag_type !== null) { 107b262b3d3SGreg Roach $this->individual_fact_privacy[$row->xref][$row->tag_type] = $row->resn; 108518bbdc1SGreg Roach } else { 109b262b3d3SGreg Roach $this->individual_privacy[$row->xref] = $row->resn; 110518bbdc1SGreg Roach } 111518bbdc1SGreg Roach } else { 112b262b3d3SGreg Roach $this->fact_privacy[$row->tag_type] = $row->resn; 113518bbdc1SGreg Roach } 114518bbdc1SGreg Roach } 115a25f0a04SGreg Roach } 116a25f0a04SGreg Roach 117a25f0a04SGreg Roach /** 1185afbc57aSGreg Roach * A closure which will create a record from a database row. 1195afbc57aSGreg Roach * 1205afbc57aSGreg Roach * @return Closure 1215afbc57aSGreg Roach */ 1225afbc57aSGreg Roach public static function rowMapper(): Closure 1235afbc57aSGreg Roach { 1245afbc57aSGreg Roach return static function (stdClass $row): Tree { 1255afbc57aSGreg Roach return new Tree((int) $row->tree_id, $row->tree_name, $row->tree_title); 1265afbc57aSGreg Roach }; 1275afbc57aSGreg Roach } 1285afbc57aSGreg Roach 1295afbc57aSGreg Roach /** 1306ccdf4f0SGreg Roach * Set the tree’s configuration settings. 1316ccdf4f0SGreg Roach * 1326ccdf4f0SGreg Roach * @param string $setting_name 1336ccdf4f0SGreg Roach * @param string $setting_value 1346ccdf4f0SGreg Roach * 1356ccdf4f0SGreg Roach * @return $this 1366ccdf4f0SGreg Roach */ 1376ccdf4f0SGreg Roach public function setPreference(string $setting_name, string $setting_value): Tree 1386ccdf4f0SGreg Roach { 1396ccdf4f0SGreg Roach if ($setting_value !== $this->getPreference($setting_name)) { 1406ccdf4f0SGreg Roach DB::table('gedcom_setting')->updateOrInsert([ 1416ccdf4f0SGreg Roach 'gedcom_id' => $this->id, 1426ccdf4f0SGreg Roach 'setting_name' => $setting_name, 1436ccdf4f0SGreg Roach ], [ 1446ccdf4f0SGreg Roach 'setting_value' => $setting_value, 1456ccdf4f0SGreg Roach ]); 1466ccdf4f0SGreg Roach 1476ccdf4f0SGreg Roach $this->preferences[$setting_name] = $setting_value; 1486ccdf4f0SGreg Roach 1496ccdf4f0SGreg Roach Log::addConfigurationLog('Tree preference "' . $setting_name . '" set to "' . $setting_value . '"', $this); 1506ccdf4f0SGreg Roach } 1516ccdf4f0SGreg Roach 1526ccdf4f0SGreg Roach return $this; 1536ccdf4f0SGreg Roach } 1546ccdf4f0SGreg Roach 1556ccdf4f0SGreg Roach /** 1566ccdf4f0SGreg Roach * Get the tree’s configuration settings. 1576ccdf4f0SGreg Roach * 1586ccdf4f0SGreg Roach * @param string $setting_name 1596ccdf4f0SGreg Roach * @param string $default 1606ccdf4f0SGreg Roach * 1616ccdf4f0SGreg Roach * @return string 1626ccdf4f0SGreg Roach */ 1636ccdf4f0SGreg Roach public function getPreference(string $setting_name, string $default = ''): string 1646ccdf4f0SGreg Roach { 16554c1ab5eSGreg Roach if ($this->preferences === []) { 1666ccdf4f0SGreg Roach $this->preferences = DB::table('gedcom_setting') 1676ccdf4f0SGreg Roach ->where('gedcom_id', '=', $this->id) 1686ccdf4f0SGreg Roach ->pluck('setting_value', 'setting_name') 1696ccdf4f0SGreg Roach ->all(); 1706ccdf4f0SGreg Roach } 1716ccdf4f0SGreg Roach 1726ccdf4f0SGreg Roach return $this->preferences[$setting_name] ?? $default; 1736ccdf4f0SGreg Roach } 1746ccdf4f0SGreg Roach 1756ccdf4f0SGreg Roach /** 1766ccdf4f0SGreg Roach * The name of this tree 1776ccdf4f0SGreg Roach * 1786ccdf4f0SGreg Roach * @return string 1796ccdf4f0SGreg Roach */ 1806ccdf4f0SGreg Roach public function name(): string 1816ccdf4f0SGreg Roach { 1826ccdf4f0SGreg Roach return $this->name; 1836ccdf4f0SGreg Roach } 1846ccdf4f0SGreg Roach 1856ccdf4f0SGreg Roach /** 1866ccdf4f0SGreg Roach * The title of this tree 1876ccdf4f0SGreg Roach * 1886ccdf4f0SGreg Roach * @return string 1896ccdf4f0SGreg Roach */ 1906ccdf4f0SGreg Roach public function title(): string 1916ccdf4f0SGreg Roach { 1926ccdf4f0SGreg Roach return $this->title; 1936ccdf4f0SGreg Roach } 1946ccdf4f0SGreg Roach 1956ccdf4f0SGreg Roach /** 1966ccdf4f0SGreg Roach * The fact-level privacy for this tree. 1976ccdf4f0SGreg Roach * 1986ccdf4f0SGreg Roach * @return int[] 1996ccdf4f0SGreg Roach */ 2006ccdf4f0SGreg Roach public function getFactPrivacy(): array 2016ccdf4f0SGreg Roach { 2026ccdf4f0SGreg Roach return $this->fact_privacy; 2036ccdf4f0SGreg Roach } 2046ccdf4f0SGreg Roach 2056ccdf4f0SGreg Roach /** 2066ccdf4f0SGreg Roach * The individual-level privacy for this tree. 2076ccdf4f0SGreg Roach * 2086ccdf4f0SGreg Roach * @return int[] 2096ccdf4f0SGreg Roach */ 2106ccdf4f0SGreg Roach public function getIndividualPrivacy(): array 2116ccdf4f0SGreg Roach { 2126ccdf4f0SGreg Roach return $this->individual_privacy; 2136ccdf4f0SGreg Roach } 2146ccdf4f0SGreg Roach 2156ccdf4f0SGreg Roach /** 2166ccdf4f0SGreg Roach * The individual-fact-level privacy for this tree. 2176ccdf4f0SGreg Roach * 2186ccdf4f0SGreg Roach * @return int[][] 2196ccdf4f0SGreg Roach */ 2206ccdf4f0SGreg Roach public function getIndividualFactPrivacy(): array 2216ccdf4f0SGreg Roach { 2226ccdf4f0SGreg Roach return $this->individual_fact_privacy; 2236ccdf4f0SGreg Roach } 2246ccdf4f0SGreg Roach 2256ccdf4f0SGreg Roach /** 2266ccdf4f0SGreg Roach * Set the tree’s user-configuration settings. 2276ccdf4f0SGreg Roach * 2286ccdf4f0SGreg Roach * @param UserInterface $user 2296ccdf4f0SGreg Roach * @param string $setting_name 2306ccdf4f0SGreg Roach * @param string $setting_value 2316ccdf4f0SGreg Roach * 2326ccdf4f0SGreg Roach * @return $this 2336ccdf4f0SGreg Roach */ 2346ccdf4f0SGreg Roach public function setUserPreference(UserInterface $user, string $setting_name, string $setting_value): Tree 2356ccdf4f0SGreg Roach { 2366ccdf4f0SGreg Roach if ($this->getUserPreference($user, $setting_name) !== $setting_value) { 2376ccdf4f0SGreg Roach // Update the database 2386ccdf4f0SGreg Roach DB::table('user_gedcom_setting')->updateOrInsert([ 2396ccdf4f0SGreg Roach 'gedcom_id' => $this->id(), 2406ccdf4f0SGreg Roach 'user_id' => $user->id(), 2416ccdf4f0SGreg Roach 'setting_name' => $setting_name, 2426ccdf4f0SGreg Roach ], [ 2436ccdf4f0SGreg Roach 'setting_value' => $setting_value, 2446ccdf4f0SGreg Roach ]); 2456ccdf4f0SGreg Roach 2466ccdf4f0SGreg Roach // Update the cache 2476ccdf4f0SGreg Roach $this->user_preferences[$user->id()][$setting_name] = $setting_value; 2486ccdf4f0SGreg Roach // Audit log of changes 2496ccdf4f0SGreg Roach Log::addConfigurationLog('Tree preference "' . $setting_name . '" set to "' . $setting_value . '" for user "' . $user->userName() . '"', $this); 2506ccdf4f0SGreg Roach } 2516ccdf4f0SGreg Roach 2526ccdf4f0SGreg Roach return $this; 2536ccdf4f0SGreg Roach } 2546ccdf4f0SGreg Roach 2556ccdf4f0SGreg Roach /** 2566ccdf4f0SGreg Roach * Get the tree’s user-configuration settings. 2576ccdf4f0SGreg Roach * 2586ccdf4f0SGreg Roach * @param UserInterface $user 2596ccdf4f0SGreg Roach * @param string $setting_name 2606ccdf4f0SGreg Roach * @param string $default 2616ccdf4f0SGreg Roach * 2626ccdf4f0SGreg Roach * @return string 2636ccdf4f0SGreg Roach */ 2646ccdf4f0SGreg Roach public function getUserPreference(UserInterface $user, string $setting_name, string $default = ''): string 2656ccdf4f0SGreg Roach { 2666ccdf4f0SGreg Roach // There are lots of settings, and we need to fetch lots of them on every page 2676ccdf4f0SGreg Roach // so it is quicker to fetch them all in one go. 2686ccdf4f0SGreg Roach if (!array_key_exists($user->id(), $this->user_preferences)) { 2696ccdf4f0SGreg Roach $this->user_preferences[$user->id()] = DB::table('user_gedcom_setting') 2706ccdf4f0SGreg Roach ->where('user_id', '=', $user->id()) 2716ccdf4f0SGreg Roach ->where('gedcom_id', '=', $this->id) 2726ccdf4f0SGreg Roach ->pluck('setting_value', 'setting_name') 2736ccdf4f0SGreg Roach ->all(); 2746ccdf4f0SGreg Roach } 2756ccdf4f0SGreg Roach 2766ccdf4f0SGreg Roach return $this->user_preferences[$user->id()][$setting_name] ?? $default; 2776ccdf4f0SGreg Roach } 2786ccdf4f0SGreg Roach 2796ccdf4f0SGreg Roach /** 2806ccdf4f0SGreg Roach * The ID of this tree 2816ccdf4f0SGreg Roach * 2826ccdf4f0SGreg Roach * @return int 2836ccdf4f0SGreg Roach */ 2846ccdf4f0SGreg Roach public function id(): int 2856ccdf4f0SGreg Roach { 2866ccdf4f0SGreg Roach return $this->id; 2876ccdf4f0SGreg Roach } 2886ccdf4f0SGreg Roach 2896ccdf4f0SGreg Roach /** 2906ccdf4f0SGreg Roach * Can a user accept changes for this tree? 2916ccdf4f0SGreg Roach * 2926ccdf4f0SGreg Roach * @param UserInterface $user 2936ccdf4f0SGreg Roach * 2946ccdf4f0SGreg Roach * @return bool 2956ccdf4f0SGreg Roach */ 2966ccdf4f0SGreg Roach public function canAcceptChanges(UserInterface $user): bool 2976ccdf4f0SGreg Roach { 2986ccdf4f0SGreg Roach return Auth::isModerator($this, $user); 2996ccdf4f0SGreg Roach } 3006ccdf4f0SGreg Roach 3016ccdf4f0SGreg Roach /** 302b78374c5SGreg Roach * Are there any pending edits for this tree, than need reviewing by a moderator. 303b78374c5SGreg Roach * 304b78374c5SGreg Roach * @return bool 305b78374c5SGreg Roach */ 306771ae10aSGreg Roach public function hasPendingEdit(): bool 307c1010edaSGreg Roach { 30815a3f100SGreg Roach return DB::table('change') 30915a3f100SGreg Roach ->where('gedcom_id', '=', $this->id) 31015a3f100SGreg Roach ->where('status', '=', 'pending') 31115a3f100SGreg Roach ->exists(); 312b78374c5SGreg Roach } 313b78374c5SGreg Roach 314b78374c5SGreg Roach /** 3156ccdf4f0SGreg Roach * Delete everything relating to a tree 3166ccdf4f0SGreg Roach * 3176ccdf4f0SGreg Roach * @return void 3185cd281f4SGreg Roach * 3195cd281f4SGreg Roach * @deprecated - since 2.0.12 - will be removed in 2.1.0 3206ccdf4f0SGreg Roach */ 3216ccdf4f0SGreg Roach public function delete(): void 3226ccdf4f0SGreg Roach { 3235cd281f4SGreg Roach $tree_service = new TreeService(); 3246ccdf4f0SGreg Roach 3255cd281f4SGreg Roach $tree_service->delete($this); 3266ccdf4f0SGreg Roach } 3276ccdf4f0SGreg Roach 3286ccdf4f0SGreg Roach /** 329a25f0a04SGreg Roach * Delete all the genealogy data from a tree - in preparation for importing 330a25f0a04SGreg Roach * new data. Optionally retain the media data, for when the user has been 331a25f0a04SGreg Roach * editing their data offline using an application which deletes (or does not 332a25f0a04SGreg Roach * support) media data. 333a25f0a04SGreg Roach * 334a25f0a04SGreg Roach * @param bool $keep_media 335b7e60af1SGreg Roach * 336b7e60af1SGreg Roach * @return void 3375cd281f4SGreg Roach * 3385cd281f4SGreg Roach * @deprecated - since 2.0.12 - will be removed in 2.1.0 339a25f0a04SGreg Roach */ 340e364afe4SGreg Roach public function deleteGenealogyData(bool $keep_media): void 341c1010edaSGreg Roach { 3425cd281f4SGreg Roach $tree_service = new TreeService(); 343a25f0a04SGreg Roach 3445cd281f4SGreg Roach $tree_service->deleteGenealogyData($this, $keep_media); 345a25f0a04SGreg Roach } 346a25f0a04SGreg Roach 347a25f0a04SGreg Roach /** 348a25f0a04SGreg Roach * Export the tree to a GEDCOM file 349a25f0a04SGreg Roach * 3505792757eSGreg Roach * @param resource $stream 351b7e60af1SGreg Roach * 352b7e60af1SGreg Roach * @return void 35369c05a6eSGreg Roach * 35469c05a6eSGreg Roach * @deprecated since 2.0.5. Will be removed in 2.1.0 355a25f0a04SGreg Roach */ 356425af8b9SGreg Roach public function exportGedcom($stream): void 357c1010edaSGreg Roach { 35869c05a6eSGreg Roach $gedcom_export_service = new GedcomExportService(); 35994026f20SGreg Roach 36069c05a6eSGreg Roach $gedcom_export_service->export($this, $stream); 361a25f0a04SGreg Roach } 362a25f0a04SGreg Roach 363a25f0a04SGreg Roach /** 364a25f0a04SGreg Roach * Import data from a gedcom file into this tree. 365a25f0a04SGreg Roach * 3666ccdf4f0SGreg Roach * @param StreamInterface $stream The GEDCOM file. 367a25f0a04SGreg Roach * @param string $filename The preferred filename, for export/download. 368a25f0a04SGreg Roach * 369b7e60af1SGreg Roach * @return void 3705cd281f4SGreg Roach * 3715cd281f4SGreg Roach * @deprecated since 2.0.12. Will be removed in 2.1.0 372a25f0a04SGreg Roach */ 3736ccdf4f0SGreg Roach public function importGedcomFile(StreamInterface $stream, string $filename): void 374c1010edaSGreg Roach { 3755cd281f4SGreg Roach $tree_service = new TreeService(); 376a25f0a04SGreg Roach 3775cd281f4SGreg Roach $tree_service->importGedcomFile($this, $stream, $filename); 3786ccdf4f0SGreg Roach } 3796ccdf4f0SGreg Roach 3806ccdf4f0SGreg Roach /** 3816ccdf4f0SGreg Roach * Create a new record from GEDCOM data. 3826ccdf4f0SGreg Roach * 3836ccdf4f0SGreg Roach * @param string $gedcom 3846ccdf4f0SGreg Roach * 3850d15532eSGreg Roach * @return GedcomRecord|Individual|Family|Location|Note|Source|Repository|Media|Submitter|Submission 3866ccdf4f0SGreg Roach * @throws InvalidArgumentException 3876ccdf4f0SGreg Roach */ 3886ccdf4f0SGreg Roach public function createRecord(string $gedcom): GedcomRecord 3896ccdf4f0SGreg Roach { 390b4a2f885SGreg Roach if (!preg_match('/^0 @@ ([_A-Z]+)/', $gedcom, $match)) { 3916ccdf4f0SGreg Roach throw new InvalidArgumentException('GedcomRecord::createRecord(' . $gedcom . ') does not begin 0 @@'); 3926ccdf4f0SGreg Roach } 3936ccdf4f0SGreg Roach 3946b9cb339SGreg Roach $xref = Registry::xrefFactory()->make($match[1]); 395dec352c1SGreg Roach $gedcom = substr_replace($gedcom, $xref, 3, 0); 3966ccdf4f0SGreg Roach 3976ccdf4f0SGreg Roach // Create a change record 39853432476SGreg Roach $today = strtoupper(date('d M Y')); 39953432476SGreg Roach $now = date('H:i:s'); 40053432476SGreg Roach $gedcom .= "\n1 CHAN\n2 DATE " . $today . "\n3 TIME " . $now . "\n2 _WT_USER " . Auth::user()->userName(); 4016ccdf4f0SGreg Roach 4026ccdf4f0SGreg Roach // Create a pending change 4036ccdf4f0SGreg Roach DB::table('change')->insert([ 4046ccdf4f0SGreg Roach 'gedcom_id' => $this->id, 4056ccdf4f0SGreg Roach 'xref' => $xref, 4066ccdf4f0SGreg Roach 'old_gedcom' => '', 4076ccdf4f0SGreg Roach 'new_gedcom' => $gedcom, 4086ccdf4f0SGreg Roach 'user_id' => Auth::id(), 4096ccdf4f0SGreg Roach ]); 4106ccdf4f0SGreg Roach 4116ccdf4f0SGreg Roach // Accept this pending change 4121fe542e9SGreg Roach if (Auth::user()->getPreference(UserInterface::PREF_AUTO_ACCEPT_EDITS) === '1') { 4136b9cb339SGreg Roach $record = Registry::gedcomRecordFactory()->new($xref, $gedcom, null, $this); 4146ccdf4f0SGreg Roach 41522e73debSGreg Roach app(PendingChangesService::class)->acceptRecord($record); 41622e73debSGreg Roach 41722e73debSGreg Roach return $record; 4186ccdf4f0SGreg Roach } 4196ccdf4f0SGreg Roach 4206b9cb339SGreg Roach return Registry::gedcomRecordFactory()->new($xref, '', $gedcom, $this); 421a25f0a04SGreg Roach } 422304f20d5SGreg Roach 423304f20d5SGreg Roach /** 424b90d8accSGreg Roach * Generate a new XREF, unique across all family trees 425b90d8accSGreg Roach * 426b90d8accSGreg Roach * @return string 427b4a2f885SGreg Roach * @deprecated - use the factory directly. 428b90d8accSGreg Roach */ 429771ae10aSGreg Roach public function getNewXref(): string 430c1010edaSGreg Roach { 4316b9cb339SGreg Roach return Registry::xrefFactory()->make(GedcomRecord::RECORD_TYPE); 432b90d8accSGreg Roach } 433b90d8accSGreg Roach 434b90d8accSGreg Roach /** 435afb591d7SGreg Roach * Create a new family from GEDCOM data. 436afb591d7SGreg Roach * 437afb591d7SGreg Roach * @param string $gedcom 438afb591d7SGreg Roach * 439afb591d7SGreg Roach * @return Family 440afb591d7SGreg Roach * @throws InvalidArgumentException 441afb591d7SGreg Roach */ 442afb591d7SGreg Roach public function createFamily(string $gedcom): GedcomRecord 443afb591d7SGreg Roach { 444dec352c1SGreg Roach if (!str_starts_with($gedcom, '0 @@ FAM')) { 445afb591d7SGreg Roach throw new InvalidArgumentException('GedcomRecord::createFamily(' . $gedcom . ') does not begin 0 @@ FAM'); 446afb591d7SGreg Roach } 447afb591d7SGreg Roach 4486b9cb339SGreg Roach $xref = Registry::xrefFactory()->make(Family::RECORD_TYPE); 449dec352c1SGreg Roach $gedcom = substr_replace($gedcom, $xref, 3, 0); 450afb591d7SGreg Roach 451afb591d7SGreg Roach // Create a change record 45253432476SGreg Roach $today = strtoupper(date('d M Y')); 45353432476SGreg Roach $now = date('H:i:s'); 45453432476SGreg Roach $gedcom .= "\n1 CHAN\n2 DATE " . $today . "\n3 TIME " . $now . "\n2 _WT_USER " . Auth::user()->userName(); 455afb591d7SGreg Roach 456afb591d7SGreg Roach // Create a pending change 457963fbaeeSGreg Roach DB::table('change')->insert([ 458963fbaeeSGreg Roach 'gedcom_id' => $this->id, 459963fbaeeSGreg Roach 'xref' => $xref, 460963fbaeeSGreg Roach 'old_gedcom' => '', 461963fbaeeSGreg Roach 'new_gedcom' => $gedcom, 462963fbaeeSGreg Roach 'user_id' => Auth::id(), 463afb591d7SGreg Roach ]); 464304f20d5SGreg Roach 465304f20d5SGreg Roach // Accept this pending change 4661fe542e9SGreg Roach if (Auth::user()->getPreference(UserInterface::PREF_AUTO_ACCEPT_EDITS) === '1') { 4676b9cb339SGreg Roach $record = Registry::familyFactory()->new($xref, $gedcom, null, $this); 468afb591d7SGreg Roach 46922e73debSGreg Roach app(PendingChangesService::class)->acceptRecord($record); 47022e73debSGreg Roach 47122e73debSGreg Roach return $record; 472304f20d5SGreg Roach } 473afb591d7SGreg Roach 4746b9cb339SGreg Roach return Registry::familyFactory()->new($xref, '', $gedcom, $this); 475afb591d7SGreg Roach } 476afb591d7SGreg Roach 477afb591d7SGreg Roach /** 478afb591d7SGreg Roach * Create a new individual from GEDCOM data. 479afb591d7SGreg Roach * 480afb591d7SGreg Roach * @param string $gedcom 481afb591d7SGreg Roach * 482afb591d7SGreg Roach * @return Individual 483afb591d7SGreg Roach * @throws InvalidArgumentException 484afb591d7SGreg Roach */ 485afb591d7SGreg Roach public function createIndividual(string $gedcom): GedcomRecord 486afb591d7SGreg Roach { 487dec352c1SGreg Roach if (!str_starts_with($gedcom, '0 @@ INDI')) { 488afb591d7SGreg Roach throw new InvalidArgumentException('GedcomRecord::createIndividual(' . $gedcom . ') does not begin 0 @@ INDI'); 489afb591d7SGreg Roach } 490afb591d7SGreg Roach 4916b9cb339SGreg Roach $xref = Registry::xrefFactory()->make(Individual::RECORD_TYPE); 492dec352c1SGreg Roach $gedcom = substr_replace($gedcom, $xref, 3, 0); 493afb591d7SGreg Roach 494afb591d7SGreg Roach // Create a change record 49553432476SGreg Roach $today = strtoupper(date('d M Y')); 49653432476SGreg Roach $now = date('H:i:s'); 49753432476SGreg Roach $gedcom .= "\n1 CHAN\n2 DATE " . $today . "\n3 TIME " . $now . "\n2 _WT_USER " . Auth::user()->userName(); 498afb591d7SGreg Roach 499afb591d7SGreg Roach // Create a pending change 500963fbaeeSGreg Roach DB::table('change')->insert([ 501963fbaeeSGreg Roach 'gedcom_id' => $this->id, 502963fbaeeSGreg Roach 'xref' => $xref, 503963fbaeeSGreg Roach 'old_gedcom' => '', 504963fbaeeSGreg Roach 'new_gedcom' => $gedcom, 505963fbaeeSGreg Roach 'user_id' => Auth::id(), 506afb591d7SGreg Roach ]); 507afb591d7SGreg Roach 508afb591d7SGreg Roach // Accept this pending change 5091fe542e9SGreg Roach if (Auth::user()->getPreference(UserInterface::PREF_AUTO_ACCEPT_EDITS) === '1') { 5106b9cb339SGreg Roach $record = Registry::individualFactory()->new($xref, $gedcom, null, $this); 511afb591d7SGreg Roach 51222e73debSGreg Roach app(PendingChangesService::class)->acceptRecord($record); 51322e73debSGreg Roach 51422e73debSGreg Roach return $record; 515afb591d7SGreg Roach } 516afb591d7SGreg Roach 5176b9cb339SGreg Roach return Registry::individualFactory()->new($xref, '', $gedcom, $this); 518304f20d5SGreg Roach } 5198586983fSGreg Roach 5208586983fSGreg Roach /** 52120b58d20SGreg Roach * Create a new media object from GEDCOM data. 52220b58d20SGreg Roach * 52320b58d20SGreg Roach * @param string $gedcom 52420b58d20SGreg Roach * 52520b58d20SGreg Roach * @return Media 52620b58d20SGreg Roach * @throws InvalidArgumentException 52720b58d20SGreg Roach */ 52820b58d20SGreg Roach public function createMediaObject(string $gedcom): Media 52920b58d20SGreg Roach { 530dec352c1SGreg Roach if (!str_starts_with($gedcom, '0 @@ OBJE')) { 53120b58d20SGreg Roach throw new InvalidArgumentException('GedcomRecord::createIndividual(' . $gedcom . ') does not begin 0 @@ OBJE'); 53220b58d20SGreg Roach } 53320b58d20SGreg Roach 5346b9cb339SGreg Roach $xref = Registry::xrefFactory()->make(Media::RECORD_TYPE); 535dec352c1SGreg Roach $gedcom = substr_replace($gedcom, $xref, 3, 0); 53620b58d20SGreg Roach 53720b58d20SGreg Roach // Create a change record 53853432476SGreg Roach $today = strtoupper(date('d M Y')); 53953432476SGreg Roach $now = date('H:i:s'); 54053432476SGreg Roach $gedcom .= "\n1 CHAN\n2 DATE " . $today . "\n3 TIME " . $now . "\n2 _WT_USER " . Auth::user()->userName(); 54120b58d20SGreg Roach 54220b58d20SGreg Roach // Create a pending change 543963fbaeeSGreg Roach DB::table('change')->insert([ 544963fbaeeSGreg Roach 'gedcom_id' => $this->id, 545963fbaeeSGreg Roach 'xref' => $xref, 546963fbaeeSGreg Roach 'old_gedcom' => '', 547963fbaeeSGreg Roach 'new_gedcom' => $gedcom, 548963fbaeeSGreg Roach 'user_id' => Auth::id(), 54920b58d20SGreg Roach ]); 55020b58d20SGreg Roach 55120b58d20SGreg Roach // Accept this pending change 5521fe542e9SGreg Roach if (Auth::user()->getPreference(UserInterface::PREF_AUTO_ACCEPT_EDITS) === '1') { 5536b9cb339SGreg Roach $record = Registry::mediaFactory()->new($xref, $gedcom, null, $this); 55420b58d20SGreg Roach 55522e73debSGreg Roach app(PendingChangesService::class)->acceptRecord($record); 55622e73debSGreg Roach 55722e73debSGreg Roach return $record; 55820b58d20SGreg Roach } 55920b58d20SGreg Roach 5606b9cb339SGreg Roach return Registry::mediaFactory()->new($xref, '', $gedcom, $this); 56120b58d20SGreg Roach } 56220b58d20SGreg Roach 56320b58d20SGreg Roach /** 5648586983fSGreg Roach * What is the most significant individual in this tree. 5658586983fSGreg Roach * 566e5a6b4d4SGreg Roach * @param UserInterface $user 5673370567dSGreg Roach * @param string $xref 5688586983fSGreg Roach * 5698586983fSGreg Roach * @return Individual 5708586983fSGreg Roach */ 5713370567dSGreg Roach public function significantIndividual(UserInterface $user, $xref = ''): Individual 572c1010edaSGreg Roach { 5733370567dSGreg Roach if ($xref === '') { 5748f9b0fb2SGreg Roach $individual = null; 5753370567dSGreg Roach } else { 5766b9cb339SGreg Roach $individual = Registry::individualFactory()->make($xref, $this); 5773370567dSGreg Roach 5783370567dSGreg Roach if ($individual === null) { 5796b9cb339SGreg Roach $family = Registry::familyFactory()->make($xref, $this); 5803370567dSGreg Roach 5813370567dSGreg Roach if ($family instanceof Family) { 5823370567dSGreg Roach $individual = $family->spouses()->first() ?? $family->children()->first(); 5833370567dSGreg Roach } 5843370567dSGreg Roach } 5853370567dSGreg Roach } 5868586983fSGreg Roach 5871fe542e9SGreg Roach if ($individual === null && $this->getUserPreference($user, UserInterface::PREF_TREE_DEFAULT_XREF) !== '') { 5881fe542e9SGreg Roach $individual = Registry::individualFactory()->make($this->getUserPreference($user, UserInterface::PREF_TREE_DEFAULT_XREF), $this); 5898586983fSGreg Roach } 5908f9b0fb2SGreg Roach 5911fe542e9SGreg Roach if ($individual === null && $this->getUserPreference($user, UserInterface::PREF_TREE_ACCOUNT_XREF) !== '') { 5921fe542e9SGreg Roach $individual = Registry::individualFactory()->make($this->getUserPreference($user, UserInterface::PREF_TREE_ACCOUNT_XREF), $this); 5938586983fSGreg Roach } 5948f9b0fb2SGreg Roach 595bec87e94SGreg Roach if ($individual === null && $this->getPreference('PEDIGREE_ROOT_ID') !== '') { 5966b9cb339SGreg Roach $individual = Registry::individualFactory()->make($this->getPreference('PEDIGREE_ROOT_ID'), $this); 5978586983fSGreg Roach } 5988f9b0fb2SGreg Roach if ($individual === null) { 5998f9b0fb2SGreg Roach $xref = (string) DB::table('individuals') 6008f9b0fb2SGreg Roach ->where('i_file', '=', $this->id()) 6018f9b0fb2SGreg Roach ->min('i_id'); 602769d7d6eSGreg Roach 6036b9cb339SGreg Roach $individual = Registry::individualFactory()->make($xref, $this); 6045fe1add5SGreg Roach } 6058f9b0fb2SGreg Roach if ($individual === null) { 6065fe1add5SGreg Roach // always return a record 6076b9cb339SGreg Roach $individual = Registry::individualFactory()->new('I', '0 @I@ INDI', null, $this); 6085fe1add5SGreg Roach } 6095fe1add5SGreg Roach 6105fe1add5SGreg Roach return $individual; 6115fe1add5SGreg Roach } 6121df7ae39SGreg Roach 61385a166d8SGreg Roach /** 61485a166d8SGreg Roach * Where do we store our media files. 61585a166d8SGreg Roach * 616*f7cf8a15SGreg Roach * @param FilesystemOperator $data_filesystem 617a04bb9a2SGreg Roach * 618*f7cf8a15SGreg Roach * @return FilesystemOperator 61985a166d8SGreg Roach */ 620*f7cf8a15SGreg Roach public function mediaFilesystem(FilesystemOperator $data_filesystem): FilesystemOperator 6211df7ae39SGreg Roach { 622456d0d35SGreg Roach $media_dir = $this->getPreference('MEDIA_DIRECTORY', 'media/'); 623a04bb9a2SGreg Roach $adapter = new ChrootAdapter($data_filesystem, $media_dir); 624456d0d35SGreg Roach 625456d0d35SGreg Roach return new Filesystem($adapter); 6261df7ae39SGreg Roach } 627a25f0a04SGreg Roach} 628