1ce42304aSGreg Roach<?php 2ce42304aSGreg Roach 3ce42304aSGreg Roach/** 4ce42304aSGreg Roach * webtrees: online genealogy 5d11be702SGreg Roach * Copyright (C) 2023 webtrees development team 6ce42304aSGreg Roach * This program is free software: you can redistribute it and/or modify 7ce42304aSGreg Roach * it under the terms of the GNU General Public License as published by 8ce42304aSGreg Roach * the Free Software Foundation, either version 3 of the License, or 9ce42304aSGreg Roach * (at your option) any later version. 10ce42304aSGreg Roach * This program is distributed in the hope that it will be useful, 11ce42304aSGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of 12ce42304aSGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13ce42304aSGreg Roach * GNU General Public License for more details. 14ce42304aSGreg 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/>. 16ce42304aSGreg Roach */ 17ce42304aSGreg Roach 18ce42304aSGreg Roachdeclare(strict_types=1); 19ce42304aSGreg Roach 20ce42304aSGreg Roachnamespace Fisharebest\Webtrees\Module; 21ce42304aSGreg Roach 226f4ec3caSGreg Roachuse Fisharebest\Webtrees\DB; 23ce42304aSGreg Roachuse Fisharebest\Webtrees\Family; 24ce42304aSGreg Roachuse Fisharebest\Webtrees\GedcomRecord; 25ce42304aSGreg Roachuse Fisharebest\Webtrees\Individual; 267684867eSGreg Roachuse Fisharebest\Webtrees\Location; 27ce42304aSGreg Roachuse Fisharebest\Webtrees\Media; 28ce42304aSGreg Roachuse Fisharebest\Webtrees\Note; 29ce42304aSGreg Roachuse Fisharebest\Webtrees\Repository; 30ce42304aSGreg Roachuse Fisharebest\Webtrees\Source; 31ce42304aSGreg Roachuse Fisharebest\Webtrees\Submitter; 32ce42304aSGreg Roachuse Fisharebest\Webtrees\Tree; 33ce42304aSGreg Roachuse Illuminate\Database\Query\Builder; 34ce42304aSGreg Roachuse Illuminate\Support\Collection; 35ce42304aSGreg Roach 36ce42304aSGreg Roach/** 37ce42304aSGreg Roach * Trait ModuleDataFixTrait - default implementation of ModuleDataFixTrait 38ce42304aSGreg Roach */ 39ce42304aSGreg Roachtrait ModuleDataFixTrait 40ce42304aSGreg Roach{ 41ce42304aSGreg Roach /** 42ce42304aSGreg Roach * Options form. 43ce42304aSGreg Roach * 44ce42304aSGreg Roach * @param Tree $tree 45ce42304aSGreg Roach * 46ce42304aSGreg Roach * @return string 47ce42304aSGreg Roach */ 4849528f2bSGreg Roach public function fixOptions(Tree $tree): string 49ce42304aSGreg Roach { 50ce42304aSGreg Roach return ''; 51ce42304aSGreg Roach } 52ce42304aSGreg Roach 53ce42304aSGreg Roach /** 54ce42304aSGreg Roach * A list of all records that need examining. This may include records 55ce42304aSGreg Roach * that do not need updating, if we can't detect this quickly using SQL. 56ce42304aSGreg Roach * 57ce42304aSGreg Roach * @param Tree $tree 58ce42304aSGreg Roach * @param array<string,string> $params 59ce42304aSGreg Roach * 6036779af1SGreg Roach * @return Collection<int,object> 61ce42304aSGreg Roach */ 62ce42304aSGreg Roach public function recordsToFix(Tree $tree, array $params): Collection 63ce42304aSGreg Roach { 64ce42304aSGreg Roach $families = $this->familiesToFix($tree, $params); 65ce42304aSGreg Roach $individuals = $this->individualsToFix($tree, $params); 667684867eSGreg Roach $locations = $this->locationsToFix($tree, $params); 67ce42304aSGreg Roach $media = $this->mediaToFix($tree, $params); 68ce42304aSGreg Roach $notes = $this->notesToFix($tree, $params); 69ce42304aSGreg Roach $repositories = $this->repositoriesToFix($tree, $params); 70ce42304aSGreg Roach $sources = $this->sourcesToFix($tree, $params); 71ce42304aSGreg Roach $submitters = $this->submittersToFix($tree, $params); 72ce42304aSGreg Roach 73ce42304aSGreg Roach $records = new Collection(); 74ce42304aSGreg Roach 75ce42304aSGreg Roach if ($families !== null) { 76ce42304aSGreg Roach $records = $records->concat($this->mergePendingRecords($families, $tree, Family::RECORD_TYPE)); 77ce42304aSGreg Roach } 78ce42304aSGreg Roach 79ce42304aSGreg Roach if ($individuals !== null) { 80ce42304aSGreg Roach $records = $records->concat($this->mergePendingRecords($individuals, $tree, Individual::RECORD_TYPE)); 81ce42304aSGreg Roach } 82ce42304aSGreg Roach 837684867eSGreg Roach if ($locations !== null) { 847684867eSGreg Roach $records = $records->concat($this->mergePendingRecords($locations, $tree, Location::RECORD_TYPE)); 857684867eSGreg Roach } 867684867eSGreg Roach 87ce42304aSGreg Roach if ($media !== null) { 88ce42304aSGreg Roach $records = $records->concat($this->mergePendingRecords($media, $tree, Media::RECORD_TYPE)); 89ce42304aSGreg Roach } 90ce42304aSGreg Roach 91ce42304aSGreg Roach if ($notes !== null) { 92ce42304aSGreg Roach $records = $records->concat($this->mergePendingRecords($notes, $tree, Note::RECORD_TYPE)); 93ce42304aSGreg Roach } 94ce42304aSGreg Roach 95ce42304aSGreg Roach if ($repositories !== null) { 96ce42304aSGreg Roach $records = $records->concat($this->mergePendingRecords($repositories, $tree, Repository::RECORD_TYPE)); 97ce42304aSGreg Roach } 98ce42304aSGreg Roach 99ce42304aSGreg Roach if ($sources !== null) { 100ce42304aSGreg Roach $records = $records->concat($this->mergePendingRecords($sources, $tree, Source::RECORD_TYPE)); 101ce42304aSGreg Roach } 102ce42304aSGreg Roach 103ce42304aSGreg Roach if ($submitters !== null) { 104ce42304aSGreg Roach $records = $records->concat($this->mergePendingRecords($submitters, $tree, Submitter::RECORD_TYPE)); 105ce42304aSGreg Roach } 106ce42304aSGreg Roach 107ce42304aSGreg Roach return $records 108ce42304aSGreg Roach ->unique() 109f25fc0f9SGreg Roach ->sort(static fn (object $x, object $y) => $x->xref <=> $y->xref); 110ce42304aSGreg Roach } 111ce42304aSGreg Roach 112ce42304aSGreg Roach /** 113ce42304aSGreg Roach * Does a record need updating? 114ce42304aSGreg Roach * 115ce42304aSGreg Roach * @param GedcomRecord $record 116ce42304aSGreg Roach * @param array<string,string> $params 117ce42304aSGreg Roach * 118ce42304aSGreg Roach * @return bool 119ce42304aSGreg Roach */ 12049528f2bSGreg Roach public function doesRecordNeedUpdate(GedcomRecord $record, array $params): bool 121ce42304aSGreg Roach { 122ce42304aSGreg Roach return false; 123ce42304aSGreg Roach } 124ce42304aSGreg Roach 125ce42304aSGreg Roach /** 126ce42304aSGreg Roach * Show the changes we would make 127ce42304aSGreg Roach * 128ce42304aSGreg Roach * @param GedcomRecord $record 129ce42304aSGreg Roach * @param array<string,string> $params 130ce42304aSGreg Roach * 131ce42304aSGreg Roach * @return string 132ce42304aSGreg Roach */ 13349528f2bSGreg Roach public function previewUpdate(GedcomRecord $record, array $params): string 134ce42304aSGreg Roach { 135ce42304aSGreg Roach return $record->fullName(); 136ce42304aSGreg Roach } 137ce42304aSGreg Roach 138ce42304aSGreg Roach /** 139ce42304aSGreg Roach * Fix a record 140ce42304aSGreg Roach * 141ce42304aSGreg Roach * @param GedcomRecord $record 142ce42304aSGreg Roach * @param array<string,string> $params 143ce42304aSGreg Roach * 144ce42304aSGreg Roach * @return void 145ce42304aSGreg Roach */ 14649528f2bSGreg Roach public function updateRecord(GedcomRecord $record, array $params): void 147ce42304aSGreg Roach { 148ce42304aSGreg Roach } 149ce42304aSGreg Roach 150ce42304aSGreg Roach /** 151ce42304aSGreg Roach * XREFs of family records that might need fixing. 152ce42304aSGreg Roach * 153ce42304aSGreg Roach * @param Tree $tree 154ce42304aSGreg Roach * @param array<string,string> $params 155ce42304aSGreg Roach * 15636779af1SGreg Roach * @return Collection<int,string>|null 157ce42304aSGreg Roach */ 158*1ff45046SGreg Roach protected function familiesToFix(Tree $tree, array $params): Collection|null 159ce42304aSGreg Roach { 160ce42304aSGreg Roach return null; 161ce42304aSGreg Roach } 162ce42304aSGreg Roach 163ce42304aSGreg Roach /** 1647684867eSGreg Roach * @param Tree $tree 1657684867eSGreg Roach * @param array<string,string> $params 1667684867eSGreg Roach * 1677684867eSGreg Roach * @return Builder 1687684867eSGreg Roach */ 1697684867eSGreg Roach protected function familiesToFixQuery(Tree $tree, array $params): Builder 1707684867eSGreg Roach { 1717684867eSGreg Roach $query = DB::table('families') 1725062b1caSJonathan Jaubart ->where('f_file', '=', $tree->id()); 1737684867eSGreg Roach 1747684867eSGreg Roach if (isset($params['start'], $params['end'])) { 1755062b1caSJonathan Jaubart $query->whereBetween('f_id', [$params['start'], $params['end']]); 1767684867eSGreg Roach } 1777684867eSGreg Roach 1787684867eSGreg Roach return $query; 1797684867eSGreg Roach } 1807684867eSGreg Roach 1817684867eSGreg Roach /** 182ce42304aSGreg Roach * XREFs of individual records that might need fixing. 183ce42304aSGreg Roach * 184ce42304aSGreg Roach * @param Tree $tree 185ce42304aSGreg Roach * @param array<string,string> $params 186ce42304aSGreg Roach * 18736779af1SGreg Roach * @return Collection<int,string>|null 188ce42304aSGreg Roach */ 189*1ff45046SGreg Roach protected function individualsToFix(Tree $tree, array $params): Collection|null 190ce42304aSGreg Roach { 191ce42304aSGreg Roach return null; 192ce42304aSGreg Roach } 193ce42304aSGreg Roach 194ce42304aSGreg Roach /** 1957684867eSGreg Roach * @param Tree $tree 1967684867eSGreg Roach * @param array<string,string> $params 1977684867eSGreg Roach * 1987684867eSGreg Roach * @return Builder 1997684867eSGreg Roach */ 2007684867eSGreg Roach protected function individualsToFixQuery(Tree $tree, array $params): Builder 2017684867eSGreg Roach { 2027684867eSGreg Roach $query = DB::table('individuals') 2037684867eSGreg Roach ->where('i_file', '=', $tree->id()); 2047684867eSGreg Roach 2057684867eSGreg Roach if (isset($params['start'], $params['end'])) { 2067684867eSGreg Roach $query->whereBetween('i_id', [$params['start'], $params['end']]); 2077684867eSGreg Roach } 2087684867eSGreg Roach 2097684867eSGreg Roach return $query; 2107684867eSGreg Roach } 2117684867eSGreg Roach 2127684867eSGreg Roach /** 2137684867eSGreg Roach * XREFs of location records that might need fixing. 2147684867eSGreg Roach * 2157684867eSGreg Roach * @param Tree $tree 2167684867eSGreg Roach * @param array<string,string> $params 2177684867eSGreg Roach * 21836779af1SGreg Roach * @return Collection<int,string>|null 2197684867eSGreg Roach */ 220*1ff45046SGreg Roach protected function locationsToFix(Tree $tree, array $params): Collection|null 2217684867eSGreg Roach { 2227684867eSGreg Roach return null; 2237684867eSGreg Roach } 2247684867eSGreg Roach 2257684867eSGreg Roach /** 2267684867eSGreg Roach * @param Tree $tree 2277684867eSGreg Roach * @param array<string,string> $params 2287684867eSGreg Roach * 2297684867eSGreg Roach * @return Builder 2307684867eSGreg Roach */ 2317684867eSGreg Roach protected function locationsToFixQuery(Tree $tree, array $params): Builder 2327684867eSGreg Roach { 2337684867eSGreg Roach $query = DB::table('other') 2347684867eSGreg Roach ->where('o_type', '=', Location::RECORD_TYPE) 2357684867eSGreg Roach ->where('o_file', '=', $tree->id()); 2367684867eSGreg Roach 2377684867eSGreg Roach if (isset($params['start'], $params['end'])) { 2387684867eSGreg Roach $query->whereBetween('o_id', [$params['start'], $params['end']]); 2397684867eSGreg Roach } 2407684867eSGreg Roach 2417684867eSGreg Roach return $query; 2427684867eSGreg Roach } 2437684867eSGreg Roach 2447684867eSGreg Roach /** 245ce42304aSGreg Roach * XREFs of media records that might need fixing. 246ce42304aSGreg Roach * 247ce42304aSGreg Roach * @param Tree $tree 248ce42304aSGreg Roach * @param array<string,string> $params 249ce42304aSGreg Roach * 25036779af1SGreg Roach * @return Collection<int,string>|null 251ce42304aSGreg Roach */ 252*1ff45046SGreg Roach protected function mediaToFix(Tree $tree, array $params): Collection|null 253ce42304aSGreg Roach { 254ce42304aSGreg Roach return null; 255ce42304aSGreg Roach } 256ce42304aSGreg Roach 257ce42304aSGreg Roach /** 2587684867eSGreg Roach * @param Tree $tree 2597684867eSGreg Roach * @param array<string,string> $params 2607684867eSGreg Roach * 2617684867eSGreg Roach * @return Builder 2627684867eSGreg Roach */ 2637684867eSGreg Roach protected function mediaToFixQuery(Tree $tree, array $params): Builder 2647684867eSGreg Roach { 2657684867eSGreg Roach $query = DB::table('media') 2667684867eSGreg Roach ->where('m_file', '=', $tree->id()); 2677684867eSGreg Roach 2687684867eSGreg Roach if (isset($params['start'], $params['end'])) { 2697684867eSGreg Roach $query->whereBetween('m_id', [$params['start'], $params['end']]); 2707684867eSGreg Roach } 2717684867eSGreg Roach 2727684867eSGreg Roach return $query; 2737684867eSGreg Roach } 2747684867eSGreg Roach 2757684867eSGreg Roach /** 276ce42304aSGreg Roach * XREFs of note records that might need fixing. 277ce42304aSGreg Roach * 278ce42304aSGreg Roach * @param Tree $tree 279ce42304aSGreg Roach * @param array<string,string> $params 280ce42304aSGreg Roach * 28136779af1SGreg Roach * @return Collection<int,string>|null 282ce42304aSGreg Roach */ 283*1ff45046SGreg Roach protected function notesToFix(Tree $tree, array $params): Collection|null 284ce42304aSGreg Roach { 285ce42304aSGreg Roach return null; 286ce42304aSGreg Roach } 287ce42304aSGreg Roach 288ce42304aSGreg Roach /** 2897684867eSGreg Roach * @param Tree $tree 2907684867eSGreg Roach * @param array<string,string> $params 2917684867eSGreg Roach * 2927684867eSGreg Roach * @return Builder 2937684867eSGreg Roach */ 2947684867eSGreg Roach protected function notesToFixQuery(Tree $tree, array $params): Builder 2957684867eSGreg Roach { 2967684867eSGreg Roach $query = DB::table('other') 2977684867eSGreg Roach ->where('o_type', '=', Note::RECORD_TYPE) 2987684867eSGreg Roach ->where('o_file', '=', $tree->id()); 2997684867eSGreg Roach 3007684867eSGreg Roach if (isset($params['start'], $params['end'])) { 3017684867eSGreg Roach $query->whereBetween('o_id', [$params['start'], $params['end']]); 3027684867eSGreg Roach } 3037684867eSGreg Roach 3047684867eSGreg Roach return $query; 3057684867eSGreg Roach } 3067684867eSGreg Roach 3077684867eSGreg Roach /** 308ce42304aSGreg Roach * XREFs of repository records that might need fixing. 309ce42304aSGreg Roach * 310ce42304aSGreg Roach * @param Tree $tree 311ce42304aSGreg Roach * @param array<string,string> $params 312ce42304aSGreg Roach * 31336779af1SGreg Roach * @return Collection<int,string>|null 314ce42304aSGreg Roach */ 315*1ff45046SGreg Roach protected function repositoriesToFix(Tree $tree, array $params): Collection|null 316ce42304aSGreg Roach { 317ce42304aSGreg Roach return null; 318ce42304aSGreg Roach } 319ce42304aSGreg Roach 320ce42304aSGreg Roach /** 3217684867eSGreg Roach * @param Tree $tree 3227684867eSGreg Roach * @param array<string,string> $params 3237684867eSGreg Roach * 3247684867eSGreg Roach * @return Builder 3257684867eSGreg Roach */ 3267684867eSGreg Roach protected function repositoriesToFixQuery(Tree $tree, array $params): Builder 3277684867eSGreg Roach { 3287684867eSGreg Roach $query = DB::table('other') 3297684867eSGreg Roach ->where('o_type', '=', Repository::RECORD_TYPE) 3307684867eSGreg Roach ->where('o_file', '=', $tree->id()); 3317684867eSGreg Roach 3327684867eSGreg Roach if (isset($params['start'], $params['end'])) { 3337684867eSGreg Roach $query->whereBetween('o_id', [$params['start'], $params['end']]); 3347684867eSGreg Roach } 3357684867eSGreg Roach 3367684867eSGreg Roach return $query; 3377684867eSGreg Roach } 3387684867eSGreg Roach 3397684867eSGreg Roach /** 340ce42304aSGreg Roach * XREFs of source records that might need fixing. 341ce42304aSGreg Roach * 342ce42304aSGreg Roach * @param Tree $tree 343ce42304aSGreg Roach * @param array<string,string> $params 344ce42304aSGreg Roach * 34536779af1SGreg Roach * @return Collection<int,string>|null 346ce42304aSGreg Roach */ 347*1ff45046SGreg Roach protected function sourcesToFix(Tree $tree, array $params): Collection|null 348ce42304aSGreg Roach { 349ce42304aSGreg Roach return null; 350ce42304aSGreg Roach } 351ce42304aSGreg Roach 352ce42304aSGreg Roach /** 3537684867eSGreg Roach * @param Tree $tree 3547684867eSGreg Roach * @param array<string,string> $params 3557684867eSGreg Roach * 3567684867eSGreg Roach * @return Builder 3577684867eSGreg Roach */ 3587684867eSGreg Roach protected function sourcesToFixQuery(Tree $tree, array $params): Builder 3597684867eSGreg Roach { 3607684867eSGreg Roach $query = DB::table('sources') 3617684867eSGreg Roach ->where('s_file', '=', $tree->id()); 3627684867eSGreg Roach 3637684867eSGreg Roach if (isset($params['start'], $params['end'])) { 3647684867eSGreg Roach $query->whereBetween('s_id', [$params['start'], $params['end']]); 3657684867eSGreg Roach } 3667684867eSGreg Roach 3677684867eSGreg Roach return $query; 3687684867eSGreg Roach } 3697684867eSGreg Roach 3707684867eSGreg Roach /** 371ce42304aSGreg Roach * XREFs of submitter records that might need fixing. 372ce42304aSGreg Roach * 373ce42304aSGreg Roach * @param Tree $tree 374ce42304aSGreg Roach * @param array<string,string> $params 375ce42304aSGreg Roach * 37636779af1SGreg Roach * @return Collection<int,string>|null 377ce42304aSGreg Roach */ 378*1ff45046SGreg Roach protected function submittersToFix(Tree $tree, array $params): Collection|null 379ce42304aSGreg Roach { 380ce42304aSGreg Roach return null; 381ce42304aSGreg Roach } 382ce42304aSGreg Roach 383ce42304aSGreg Roach /** 3847684867eSGreg Roach * @param Tree $tree 3857684867eSGreg Roach * @param array<string,string> $params 3867684867eSGreg Roach * 3877684867eSGreg Roach * @return Builder 3887684867eSGreg Roach */ 3897684867eSGreg Roach protected function submittersToFixQuery(Tree $tree, array $params): Builder 3907684867eSGreg Roach { 3917684867eSGreg Roach $query = DB::table('other') 3927684867eSGreg Roach ->where('o_type', '=', Submitter::RECORD_TYPE) 3937684867eSGreg Roach ->where('o_file', '=', $tree->id()); 3947684867eSGreg Roach 3957684867eSGreg Roach if (isset($params['start'], $params['end'])) { 3967684867eSGreg Roach $query->whereBetween('o_id', [$params['start'], $params['end']]); 3977684867eSGreg Roach } 3987684867eSGreg Roach 3997684867eSGreg Roach return $query; 4007684867eSGreg Roach } 4017684867eSGreg Roach 4027684867eSGreg Roach /** 403ce42304aSGreg Roach * Merge pending changes of a given type. We need to check all pending records. 404ce42304aSGreg Roach * 40536779af1SGreg Roach * @param Collection<int,string> $records 406ce42304aSGreg Roach * @param Tree $tree 407ce42304aSGreg Roach * @param string $type 408ce42304aSGreg Roach * 409e93aa0bdSGreg Roach * @return Collection<int,object> 410ce42304aSGreg Roach */ 411ce42304aSGreg Roach private function mergePendingRecords(Collection $records, Tree $tree, string $type): Collection 412ce42304aSGreg Roach { 413ce42304aSGreg Roach $pending = DB::table('change') 414ce42304aSGreg Roach ->where('gedcom_id', '=', $tree->id()) 415ce42304aSGreg Roach ->where('status', '=', 'pending') 416ce42304aSGreg Roach ->where(static function (Builder $query) use ($type): void { 417ce42304aSGreg Roach $query 418e93aa0bdSGreg Roach ->where('old_gedcom', 'LIKE', '%@ ' . $type . '\n%') 419e93aa0bdSGreg Roach ->orWhere('new_gedcom', 'LIKE', '%@ ' . $type . '\n%'); 420ce42304aSGreg Roach }) 421ce42304aSGreg Roach ->pluck('xref'); 422ce42304aSGreg Roach 423ce42304aSGreg Roach return $records 424ce42304aSGreg Roach ->concat($pending) 425f25fc0f9SGreg Roach ->map(static fn (string $xref): object => (object) ['xref' => $xref, 'type' => $type]); 426ce42304aSGreg Roach } 427ce42304aSGreg Roach} 428