1ce42304aSGreg Roach<?php 2ce42304aSGreg Roach 3ce42304aSGreg Roach/** 4ce42304aSGreg Roach * webtrees: online genealogy 5*5bfc6897SGreg Roach * Copyright (C) 2022 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 22ce42304aSGreg Roachuse Fisharebest\Webtrees\Family; 23ce42304aSGreg Roachuse Fisharebest\Webtrees\GedcomRecord; 24ce42304aSGreg Roachuse Fisharebest\Webtrees\Individual; 257684867eSGreg Roachuse Fisharebest\Webtrees\Location; 26ce42304aSGreg Roachuse Fisharebest\Webtrees\Media; 27ce42304aSGreg Roachuse Fisharebest\Webtrees\Note; 28ce42304aSGreg Roachuse Fisharebest\Webtrees\Repository; 29ce42304aSGreg Roachuse Fisharebest\Webtrees\Source; 30ce42304aSGreg Roachuse Fisharebest\Webtrees\Submitter; 31ce42304aSGreg Roachuse Fisharebest\Webtrees\Tree; 32ce42304aSGreg Roachuse Illuminate\Database\Capsule\Manager as DB; 33ce42304aSGreg Roachuse Illuminate\Database\Query\Builder; 34ce42304aSGreg Roachuse Illuminate\Support\Collection; 3576d39c55SGreg Roachuse stdClass; 36ce42304aSGreg Roach 37ce42304aSGreg Roach/** 38ce42304aSGreg Roach * Trait ModuleDataFixTrait - default implementation of ModuleDataFixTrait 39ce42304aSGreg Roach */ 40ce42304aSGreg Roachtrait ModuleDataFixTrait 41ce42304aSGreg Roach{ 42ce42304aSGreg Roach /** 43ce42304aSGreg Roach * Options form. 44ce42304aSGreg Roach * 45ce42304aSGreg Roach * @param Tree $tree 46ce42304aSGreg Roach * 47ce42304aSGreg Roach * @return string 48ce42304aSGreg Roach */ 49a09ea7ccSGreg Roach public function fixOptions(/** @scrutinizer ignore-unused */ Tree $tree): string 50ce42304aSGreg Roach { 51ce42304aSGreg Roach return ''; 52ce42304aSGreg Roach } 53ce42304aSGreg Roach 54ce42304aSGreg Roach /** 55ce42304aSGreg Roach * A list of all records that need examining. This may include records 56ce42304aSGreg Roach * that do not need updating, if we can't detect this quickly using SQL. 57ce42304aSGreg Roach * 58ce42304aSGreg Roach * @param Tree $tree 59ce42304aSGreg Roach * @param array<string,string> $params 60ce42304aSGreg Roach * 6136779af1SGreg Roach * @return Collection<int,object> 62ce42304aSGreg Roach */ 63ce42304aSGreg Roach public function recordsToFix(Tree $tree, array $params): Collection 64ce42304aSGreg Roach { 65ce42304aSGreg Roach $families = $this->familiesToFix($tree, $params); 66ce42304aSGreg Roach $individuals = $this->individualsToFix($tree, $params); 677684867eSGreg Roach $locations = $this->locationsToFix($tree, $params); 68ce42304aSGreg Roach $media = $this->mediaToFix($tree, $params); 69ce42304aSGreg Roach $notes = $this->notesToFix($tree, $params); 70ce42304aSGreg Roach $repositories = $this->repositoriesToFix($tree, $params); 71ce42304aSGreg Roach $sources = $this->sourcesToFix($tree, $params); 72ce42304aSGreg Roach $submitters = $this->submittersToFix($tree, $params); 73ce42304aSGreg Roach 74ce42304aSGreg Roach $records = new Collection(); 75ce42304aSGreg Roach 76ce42304aSGreg Roach if ($families !== null) { 77ce42304aSGreg Roach $records = $records->concat($this->mergePendingRecords($families, $tree, Family::RECORD_TYPE)); 78ce42304aSGreg Roach } 79ce42304aSGreg Roach 80ce42304aSGreg Roach if ($individuals !== null) { 81ce42304aSGreg Roach $records = $records->concat($this->mergePendingRecords($individuals, $tree, Individual::RECORD_TYPE)); 82ce42304aSGreg Roach } 83ce42304aSGreg Roach 847684867eSGreg Roach if ($locations !== null) { 857684867eSGreg Roach $records = $records->concat($this->mergePendingRecords($locations, $tree, Location::RECORD_TYPE)); 867684867eSGreg Roach } 877684867eSGreg Roach 88ce42304aSGreg Roach if ($media !== null) { 89ce42304aSGreg Roach $records = $records->concat($this->mergePendingRecords($media, $tree, Media::RECORD_TYPE)); 90ce42304aSGreg Roach } 91ce42304aSGreg Roach 92ce42304aSGreg Roach if ($notes !== null) { 93ce42304aSGreg Roach $records = $records->concat($this->mergePendingRecords($notes, $tree, Note::RECORD_TYPE)); 94ce42304aSGreg Roach } 95ce42304aSGreg Roach 96ce42304aSGreg Roach if ($repositories !== null) { 97ce42304aSGreg Roach $records = $records->concat($this->mergePendingRecords($repositories, $tree, Repository::RECORD_TYPE)); 98ce42304aSGreg Roach } 99ce42304aSGreg Roach 100ce42304aSGreg Roach if ($sources !== null) { 101ce42304aSGreg Roach $records = $records->concat($this->mergePendingRecords($sources, $tree, Source::RECORD_TYPE)); 102ce42304aSGreg Roach } 103ce42304aSGreg Roach 104ce42304aSGreg Roach if ($submitters !== null) { 105ce42304aSGreg Roach $records = $records->concat($this->mergePendingRecords($submitters, $tree, Submitter::RECORD_TYPE)); 106ce42304aSGreg Roach } 107ce42304aSGreg Roach 108ce42304aSGreg Roach return $records 109ce42304aSGreg Roach ->unique() 110f70bcff5SGreg Roach ->sort(static function (object $x, object $y) { 111ce42304aSGreg Roach return $x->xref <=> $y->xref; 112ce42304aSGreg Roach }); 113ce42304aSGreg Roach } 114ce42304aSGreg Roach 115ce42304aSGreg Roach /** 116ce42304aSGreg Roach * Does a record need updating? 117ce42304aSGreg Roach * 118ce42304aSGreg Roach * @param GedcomRecord $record 119ce42304aSGreg Roach * @param array<string,string> $params 120ce42304aSGreg Roach * 121ce42304aSGreg Roach * @return bool 122ce42304aSGreg Roach */ 123a09ea7ccSGreg Roach public function doesRecordNeedUpdate(/** @scrutinizer ignore-unused */ GedcomRecord $record, /** @scrutinizer ignore-unused */ array $params): bool 124ce42304aSGreg Roach { 125ce42304aSGreg Roach return false; 126ce42304aSGreg Roach } 127ce42304aSGreg Roach 128ce42304aSGreg Roach /** 129ce42304aSGreg Roach * Show the changes we would make 130ce42304aSGreg Roach * 131ce42304aSGreg Roach * @param GedcomRecord $record 132ce42304aSGreg Roach * @param array<string,string> $params 133ce42304aSGreg Roach * 134ce42304aSGreg Roach * @return string 135ce42304aSGreg Roach */ 136a09ea7ccSGreg Roach public function previewUpdate(GedcomRecord $record, /** @scrutinizer ignore-unused */ array $params): string 137ce42304aSGreg Roach { 138ce42304aSGreg Roach return $record->fullName(); 139ce42304aSGreg Roach } 140ce42304aSGreg Roach 141ce42304aSGreg Roach /** 142ce42304aSGreg Roach * Fix a record 143ce42304aSGreg Roach * 144ce42304aSGreg Roach * @param GedcomRecord $record 145ce42304aSGreg Roach * @param array<string,string> $params 146ce42304aSGreg Roach * 147ce42304aSGreg Roach * @return void 148ce42304aSGreg Roach */ 149a09ea7ccSGreg Roach public function updateRecord(/** @scrutinizer ignore-unused */ GedcomRecord $record, /** @scrutinizer ignore-unused */ array $params): void 150ce42304aSGreg Roach { 151ce42304aSGreg Roach } 152ce42304aSGreg Roach 153ce42304aSGreg Roach /** 154ce42304aSGreg Roach * XREFs of family records that might need fixing. 155ce42304aSGreg Roach * 156ce42304aSGreg Roach * @param Tree $tree 157ce42304aSGreg Roach * @param array<string,string> $params 158ce42304aSGreg Roach * 15936779af1SGreg Roach * @return Collection<int,string>|null 160ce42304aSGreg Roach */ 161be410956SGreg Roach protected function familiesToFix(/** @scrutinizer ignore-unused */ Tree $tree, /** @scrutinizer ignore-unused */ array $params): ?Collection 162ce42304aSGreg Roach { 163ce42304aSGreg Roach return null; 164ce42304aSGreg Roach } 165ce42304aSGreg Roach 166ce42304aSGreg Roach /** 1677684867eSGreg Roach * @param Tree $tree 1687684867eSGreg Roach * @param array<string,string> $params 1697684867eSGreg Roach * 1707684867eSGreg Roach * @return Builder 1717684867eSGreg Roach */ 1727684867eSGreg Roach protected function familiesToFixQuery(Tree $tree, array $params): Builder 1737684867eSGreg Roach { 1747684867eSGreg Roach $query = DB::table('families') 1755062b1caSJonathan Jaubart ->where('f_file', '=', $tree->id()); 1767684867eSGreg Roach 1777684867eSGreg Roach if (isset($params['start'], $params['end'])) { 1785062b1caSJonathan Jaubart $query->whereBetween('f_id', [$params['start'], $params['end']]); 1797684867eSGreg Roach } 1807684867eSGreg Roach 1817684867eSGreg Roach return $query; 1827684867eSGreg Roach } 1837684867eSGreg Roach 1847684867eSGreg Roach /** 185ce42304aSGreg Roach * XREFs of individual records that might need fixing. 186ce42304aSGreg Roach * 187ce42304aSGreg Roach * @param Tree $tree 188ce42304aSGreg Roach * @param array<string,string> $params 189ce42304aSGreg Roach * 19036779af1SGreg Roach * @return Collection<int,string>|null 191ce42304aSGreg Roach */ 192a09ea7ccSGreg Roach protected function individualsToFix(/** @scrutinizer ignore-unused */ Tree $tree, /** @scrutinizer ignore-unused */ array $params): ?Collection 193ce42304aSGreg Roach { 194ce42304aSGreg Roach return null; 195ce42304aSGreg Roach } 196ce42304aSGreg Roach 197ce42304aSGreg Roach /** 1987684867eSGreg Roach * @param Tree $tree 1997684867eSGreg Roach * @param array<string,string> $params 2007684867eSGreg Roach * 2017684867eSGreg Roach * @return Builder 2027684867eSGreg Roach */ 2037684867eSGreg Roach protected function individualsToFixQuery(Tree $tree, array $params): Builder 2047684867eSGreg Roach { 2057684867eSGreg Roach $query = DB::table('individuals') 2067684867eSGreg Roach ->where('i_file', '=', $tree->id()); 2077684867eSGreg Roach 2087684867eSGreg Roach if (isset($params['start'], $params['end'])) { 2097684867eSGreg Roach $query->whereBetween('i_id', [$params['start'], $params['end']]); 2107684867eSGreg Roach } 2117684867eSGreg Roach 2127684867eSGreg Roach return $query; 2137684867eSGreg Roach } 2147684867eSGreg Roach 2157684867eSGreg Roach /** 2167684867eSGreg Roach * XREFs of location records that might need fixing. 2177684867eSGreg Roach * 2187684867eSGreg Roach * @param Tree $tree 2197684867eSGreg Roach * @param array<string,string> $params 2207684867eSGreg Roach * 22136779af1SGreg Roach * @return Collection<int,string>|null 2227684867eSGreg Roach */ 223a09ea7ccSGreg Roach protected function locationsToFix(/** @scrutinizer ignore-unused */ Tree $tree, /** @scrutinizer ignore-unused */ array $params): ?Collection 2247684867eSGreg Roach { 2257684867eSGreg Roach return null; 2267684867eSGreg Roach } 2277684867eSGreg Roach 2287684867eSGreg Roach /** 2297684867eSGreg Roach * @param Tree $tree 2307684867eSGreg Roach * @param array<string,string> $params 2317684867eSGreg Roach * 2327684867eSGreg Roach * @return Builder 2337684867eSGreg Roach */ 2347684867eSGreg Roach protected function locationsToFixQuery(Tree $tree, array $params): Builder 2357684867eSGreg Roach { 2367684867eSGreg Roach $query = DB::table('other') 2377684867eSGreg Roach ->where('o_type', '=', Location::RECORD_TYPE) 2387684867eSGreg Roach ->where('o_file', '=', $tree->id()); 2397684867eSGreg Roach 2407684867eSGreg Roach if (isset($params['start'], $params['end'])) { 2417684867eSGreg Roach $query->whereBetween('o_id', [$params['start'], $params['end']]); 2427684867eSGreg Roach } 2437684867eSGreg Roach 2447684867eSGreg Roach return $query; 2457684867eSGreg Roach } 2467684867eSGreg Roach 2477684867eSGreg Roach /** 248ce42304aSGreg Roach * XREFs of media records that might need fixing. 249ce42304aSGreg Roach * 250ce42304aSGreg Roach * @param Tree $tree 251ce42304aSGreg Roach * @param array<string,string> $params 252ce42304aSGreg Roach * 25336779af1SGreg Roach * @return Collection<int,string>|null 254ce42304aSGreg Roach */ 255a09ea7ccSGreg Roach protected function mediaToFix(/** @scrutinizer ignore-unused */ Tree $tree, /** @scrutinizer ignore-unused */ array $params): ?Collection 256ce42304aSGreg Roach { 257ce42304aSGreg Roach return null; 258ce42304aSGreg Roach } 259ce42304aSGreg Roach 260ce42304aSGreg Roach /** 2617684867eSGreg Roach * @param Tree $tree 2627684867eSGreg Roach * @param array<string,string> $params 2637684867eSGreg Roach * 2647684867eSGreg Roach * @return Builder 2657684867eSGreg Roach */ 2667684867eSGreg Roach protected function mediaToFixQuery(Tree $tree, array $params): Builder 2677684867eSGreg Roach { 2687684867eSGreg Roach $query = DB::table('media') 2697684867eSGreg Roach ->where('m_file', '=', $tree->id()); 2707684867eSGreg Roach 2717684867eSGreg Roach if (isset($params['start'], $params['end'])) { 2727684867eSGreg Roach $query->whereBetween('m_id', [$params['start'], $params['end']]); 2737684867eSGreg Roach } 2747684867eSGreg Roach 2757684867eSGreg Roach return $query; 2767684867eSGreg Roach } 2777684867eSGreg Roach 2787684867eSGreg Roach /** 279ce42304aSGreg Roach * XREFs of note records that might need fixing. 280ce42304aSGreg Roach * 281ce42304aSGreg Roach * @param Tree $tree 282ce42304aSGreg Roach * @param array<string,string> $params 283ce42304aSGreg Roach * 28436779af1SGreg Roach * @return Collection<int,string>|null 285ce42304aSGreg Roach */ 286a09ea7ccSGreg Roach protected function notesToFix(/** @scrutinizer ignore-unused */ Tree $tree, /** @scrutinizer ignore-unused */ array $params): ?Collection 287ce42304aSGreg Roach { 288ce42304aSGreg Roach return null; 289ce42304aSGreg Roach } 290ce42304aSGreg Roach 291ce42304aSGreg Roach /** 2927684867eSGreg Roach * @param Tree $tree 2937684867eSGreg Roach * @param array<string,string> $params 2947684867eSGreg Roach * 2957684867eSGreg Roach * @return Builder 2967684867eSGreg Roach */ 2977684867eSGreg Roach protected function notesToFixQuery(Tree $tree, array $params): Builder 2987684867eSGreg Roach { 2997684867eSGreg Roach $query = DB::table('other') 3007684867eSGreg Roach ->where('o_type', '=', Note::RECORD_TYPE) 3017684867eSGreg Roach ->where('o_file', '=', $tree->id()); 3027684867eSGreg Roach 3037684867eSGreg Roach if (isset($params['start'], $params['end'])) { 3047684867eSGreg Roach $query->whereBetween('o_id', [$params['start'], $params['end']]); 3057684867eSGreg Roach } 3067684867eSGreg Roach 3077684867eSGreg Roach return $query; 3087684867eSGreg Roach } 3097684867eSGreg Roach 3107684867eSGreg Roach /** 311ce42304aSGreg Roach * XREFs of repository records that might need fixing. 312ce42304aSGreg Roach * 313ce42304aSGreg Roach * @param Tree $tree 314ce42304aSGreg Roach * @param array<string,string> $params 315ce42304aSGreg Roach * 31636779af1SGreg Roach * @return Collection<int,string>|null 317ce42304aSGreg Roach */ 318a09ea7ccSGreg Roach protected function repositoriesToFix(/** @scrutinizer ignore-unused */ Tree $tree, /** @scrutinizer ignore-unused */ array $params): ?Collection 319ce42304aSGreg Roach { 320ce42304aSGreg Roach return null; 321ce42304aSGreg Roach } 322ce42304aSGreg Roach 323ce42304aSGreg Roach /** 3247684867eSGreg Roach * @param Tree $tree 3257684867eSGreg Roach * @param array<string,string> $params 3267684867eSGreg Roach * 3277684867eSGreg Roach * @return Builder 3287684867eSGreg Roach */ 3297684867eSGreg Roach protected function repositoriesToFixQuery(Tree $tree, array $params): Builder 3307684867eSGreg Roach { 3317684867eSGreg Roach $query = DB::table('other') 3327684867eSGreg Roach ->where('o_type', '=', Repository::RECORD_TYPE) 3337684867eSGreg Roach ->where('o_file', '=', $tree->id()); 3347684867eSGreg Roach 3357684867eSGreg Roach if (isset($params['start'], $params['end'])) { 3367684867eSGreg Roach $query->whereBetween('o_id', [$params['start'], $params['end']]); 3377684867eSGreg Roach } 3387684867eSGreg Roach 3397684867eSGreg Roach return $query; 3407684867eSGreg Roach } 3417684867eSGreg Roach 3427684867eSGreg Roach /** 343ce42304aSGreg Roach * XREFs of source records that might need fixing. 344ce42304aSGreg Roach * 345ce42304aSGreg Roach * @param Tree $tree 346ce42304aSGreg Roach * @param array<string,string> $params 347ce42304aSGreg Roach * 34836779af1SGreg Roach * @return Collection<int,string>|null 349ce42304aSGreg Roach */ 350a09ea7ccSGreg Roach protected function sourcesToFix(/** @scrutinizer ignore-unused */ Tree $tree, /** @scrutinizer ignore-unused */ array $params): ?Collection 351ce42304aSGreg Roach { 352ce42304aSGreg Roach return null; 353ce42304aSGreg Roach } 354ce42304aSGreg Roach 355ce42304aSGreg Roach /** 3567684867eSGreg Roach * @param Tree $tree 3577684867eSGreg Roach * @param array<string,string> $params 3587684867eSGreg Roach * 3597684867eSGreg Roach * @return Builder 3607684867eSGreg Roach */ 3617684867eSGreg Roach protected function sourcesToFixQuery(Tree $tree, array $params): Builder 3627684867eSGreg Roach { 3637684867eSGreg Roach $query = DB::table('sources') 3647684867eSGreg Roach ->where('s_file', '=', $tree->id()); 3657684867eSGreg Roach 3667684867eSGreg Roach if (isset($params['start'], $params['end'])) { 3677684867eSGreg Roach $query->whereBetween('s_id', [$params['start'], $params['end']]); 3687684867eSGreg Roach } 3697684867eSGreg Roach 3707684867eSGreg Roach return $query; 3717684867eSGreg Roach } 3727684867eSGreg Roach 3737684867eSGreg Roach /** 374ce42304aSGreg Roach * XREFs of submitter records that might need fixing. 375ce42304aSGreg Roach * 376ce42304aSGreg Roach * @param Tree $tree 377ce42304aSGreg Roach * @param array<string,string> $params 378ce42304aSGreg Roach * 37936779af1SGreg Roach * @return Collection<int,string>|null 380ce42304aSGreg Roach */ 381a09ea7ccSGreg Roach protected function submittersToFix(/** @scrutinizer ignore-unused */ Tree $tree, /** @scrutinizer ignore-unused */ array $params): ?Collection 382ce42304aSGreg Roach { 383ce42304aSGreg Roach return null; 384ce42304aSGreg Roach } 385ce42304aSGreg Roach 386ce42304aSGreg Roach /** 3877684867eSGreg Roach * @param Tree $tree 3887684867eSGreg Roach * @param array<string,string> $params 3897684867eSGreg Roach * 3907684867eSGreg Roach * @return Builder 3917684867eSGreg Roach */ 3927684867eSGreg Roach protected function submittersToFixQuery(Tree $tree, array $params): Builder 3937684867eSGreg Roach { 3947684867eSGreg Roach $query = DB::table('other') 3957684867eSGreg Roach ->where('o_type', '=', Submitter::RECORD_TYPE) 3967684867eSGreg Roach ->where('o_file', '=', $tree->id()); 3977684867eSGreg Roach 3987684867eSGreg Roach if (isset($params['start'], $params['end'])) { 3997684867eSGreg Roach $query->whereBetween('o_id', [$params['start'], $params['end']]); 4007684867eSGreg Roach } 4017684867eSGreg Roach 4027684867eSGreg Roach return $query; 4037684867eSGreg Roach } 4047684867eSGreg Roach 4057684867eSGreg Roach /** 406ce42304aSGreg Roach * Merge pending changes of a given type. We need to check all pending records. 407ce42304aSGreg Roach * 40836779af1SGreg Roach * @param Collection<int,string> $records 409ce42304aSGreg Roach * @param Tree $tree 410ce42304aSGreg Roach * @param string $type 411ce42304aSGreg Roach * 41276d39c55SGreg Roach * @return Collection<int,stdClass> 413ce42304aSGreg Roach */ 414ce42304aSGreg Roach private function mergePendingRecords(Collection $records, Tree $tree, string $type): Collection 415ce42304aSGreg Roach { 416ce42304aSGreg Roach $pending = DB::table('change') 417ce42304aSGreg Roach ->where('gedcom_id', '=', $tree->id()) 418ce42304aSGreg Roach ->where('status', '=', 'pending') 419ce42304aSGreg Roach ->where(static function (Builder $query) use ($type): void { 420ce42304aSGreg Roach $query 421ce42304aSGreg Roach ->where('old_gedcom', 'LIKE', '%@ ' . $type . '%') 422ce42304aSGreg Roach ->orWhere('new_gedcom', 'LIKE', '%@ ' . $type . '%'); 423ce42304aSGreg Roach }) 424ce42304aSGreg Roach ->pluck('xref'); 425ce42304aSGreg Roach 426ce42304aSGreg Roach return $records 427ce42304aSGreg Roach ->concat($pending) 428f70bcff5SGreg Roach ->map(static function (string $xref) use ($type): object { 429ce42304aSGreg Roach return (object) ['xref' => $xref, 'type' => $type]; 430ce42304aSGreg Roach }); 431ce42304aSGreg Roach } 432ce42304aSGreg Roach} 433