122e73debSGreg Roach<?php 222e73debSGreg Roach 322e73debSGreg Roach/** 422e73debSGreg Roach * webtrees: online genealogy 5d11be702SGreg Roach * Copyright (C) 2023 webtrees development team 622e73debSGreg Roach * This program is free software: you can redistribute it and/or modify 722e73debSGreg Roach * it under the terms of the GNU General Public License as published by 822e73debSGreg Roach * the Free Software Foundation, either version 3 of the License, or 922e73debSGreg Roach * (at your option) any later version. 1022e73debSGreg Roach * This program is distributed in the hope that it will be useful, 1122e73debSGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of 1222e73debSGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1322e73debSGreg Roach * GNU General Public License for more details. 1422e73debSGreg 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/>. 1622e73debSGreg Roach */ 1722e73debSGreg Roach 1822e73debSGreg Roachdeclare(strict_types=1); 1922e73debSGreg Roach 2022e73debSGreg Roachnamespace Fisharebest\Webtrees\Services; 2122e73debSGreg Roach 22*6f4ec3caSGreg Roachuse Fisharebest\Webtrees\DB; 230d047a8cSGreg Roachuse Fisharebest\Webtrees\Exceptions\GedcomErrorException; 2422ad3b5bSGreg Roachuse Fisharebest\Webtrees\Family; 2522ad3b5bSGreg Roachuse Fisharebest\Webtrees\Gedcom; 2622e73debSGreg Roachuse Fisharebest\Webtrees\GedcomRecord; 2722ad3b5bSGreg Roachuse Fisharebest\Webtrees\Header; 2822ad3b5bSGreg Roachuse Fisharebest\Webtrees\Individual; 2922ad3b5bSGreg Roachuse Fisharebest\Webtrees\Location; 3022ad3b5bSGreg Roachuse Fisharebest\Webtrees\Media; 3122ad3b5bSGreg Roachuse Fisharebest\Webtrees\Note; 3281b729d3SGreg Roachuse Fisharebest\Webtrees\Registry; 3322ad3b5bSGreg Roachuse Fisharebest\Webtrees\Repository; 3422ad3b5bSGreg Roachuse Fisharebest\Webtrees\Source; 3522ad3b5bSGreg Roachuse Fisharebest\Webtrees\Submission; 3622ad3b5bSGreg Roachuse Fisharebest\Webtrees\Submitter; 3722e73debSGreg Roachuse Fisharebest\Webtrees\Tree; 3822e73debSGreg Roachuse Illuminate\Database\Query\Builder; 3922e73debSGreg Roachuse Illuminate\Database\Query\Expression; 4022ad3b5bSGreg Roachuse Illuminate\Support\Collection; 4122ad3b5bSGreg Roach 42b5961194SGreg Roachuse function addcslashes; 4322ad3b5bSGreg Roachuse function preg_match; 44b5961194SGreg Roach 4522e73debSGreg Roach/** 4622e73debSGreg Roach * Manage pending changes 4722e73debSGreg Roach */ 4822e73debSGreg Roachclass PendingChangesService 4922e73debSGreg Roach{ 502c685d76SGreg Roach private GedcomImportService $gedcom_import_service; 512c685d76SGreg Roach 522c685d76SGreg Roach /** 532c685d76SGreg Roach * @param GedcomImportService $gedcom_import_service 542c685d76SGreg Roach */ 552c685d76SGreg Roach public function __construct(GedcomImportService $gedcom_import_service) 562c685d76SGreg Roach { 572c685d76SGreg Roach $this->gedcom_import_service = $gedcom_import_service; 582c685d76SGreg Roach } 592c685d76SGreg Roach 6022e73debSGreg Roach /** 6122ad3b5bSGreg Roach * Which records have pending changes 6222ad3b5bSGreg Roach * 6322ad3b5bSGreg Roach * @param Tree $tree 6422ad3b5bSGreg Roach * 6536779af1SGreg Roach * @return Collection<int,string> 6622ad3b5bSGreg Roach */ 6722ad3b5bSGreg Roach public function pendingXrefs(Tree $tree): Collection 6822ad3b5bSGreg Roach { 6922ad3b5bSGreg Roach return DB::table('change') 7022ad3b5bSGreg Roach ->where('status', '=', 'pending') 7122ad3b5bSGreg Roach ->where('gedcom_id', '=', $tree->id()) 7222ad3b5bSGreg Roach ->orderBy('xref') 7322ad3b5bSGreg Roach ->groupBy(['xref']) 7422ad3b5bSGreg Roach ->pluck('xref'); 7522ad3b5bSGreg Roach } 7622ad3b5bSGreg Roach 7722ad3b5bSGreg Roach /** 7822ad3b5bSGreg Roach * @param Tree $tree 7922ad3b5bSGreg Roach * @param int $n 8022ad3b5bSGreg Roach * 81f70bcff5SGreg Roach * @return array<array<object>> 8222ad3b5bSGreg Roach */ 8322ad3b5bSGreg Roach public function pendingChanges(Tree $tree, int $n): array 8422ad3b5bSGreg Roach { 8522ad3b5bSGreg Roach $xrefs = $this->pendingXrefs($tree); 8622ad3b5bSGreg Roach 8722ad3b5bSGreg Roach $rows = DB::table('change') 8822ad3b5bSGreg Roach ->join('user', 'user.user_id', '=', 'change.user_id') 8922ad3b5bSGreg Roach ->where('status', '=', 'pending') 9022ad3b5bSGreg Roach ->where('gedcom_id', '=', $tree->id()) 9122ad3b5bSGreg Roach ->whereIn('xref', $xrefs->slice(0, $n)) 9222ad3b5bSGreg Roach ->orderBy('change.change_id') 9322ad3b5bSGreg Roach ->select(['change.*', 'user.user_name', 'user.real_name']) 9422ad3b5bSGreg Roach ->get(); 9522ad3b5bSGreg Roach 9622ad3b5bSGreg Roach $changes = []; 9722ad3b5bSGreg Roach 9822ad3b5bSGreg Roach $factories = [ 996b9cb339SGreg Roach Individual::RECORD_TYPE => Registry::individualFactory(), 1006b9cb339SGreg Roach Family::RECORD_TYPE => Registry::familyFactory(), 1016b9cb339SGreg Roach Source::RECORD_TYPE => Registry::sourceFactory(), 1026b9cb339SGreg Roach Repository::RECORD_TYPE => Registry::repositoryFactory(), 1036b9cb339SGreg Roach Media::RECORD_TYPE => Registry::mediaFactory(), 1046b9cb339SGreg Roach Note::RECORD_TYPE => Registry::noteFactory(), 1056b9cb339SGreg Roach Submitter::RECORD_TYPE => Registry::submitterFactory(), 1066b9cb339SGreg Roach Submission::RECORD_TYPE => Registry::submissionFactory(), 1076b9cb339SGreg Roach Location::RECORD_TYPE => Registry::locationFactory(), 1086b9cb339SGreg Roach Header::RECORD_TYPE => Registry::headerFactory(), 10922ad3b5bSGreg Roach ]; 11022ad3b5bSGreg Roach 11122ad3b5bSGreg Roach foreach ($rows as $row) { 112d97083feSGreg Roach $row->change_time = Registry::timestampFactory()->fromString($row->change_time); 11322ad3b5bSGreg Roach 11422ad3b5bSGreg Roach preg_match('/^0 (?:@' . Gedcom::REGEX_XREF . '@ )?(' . Gedcom::REGEX_TAG . ')/', $row->old_gedcom . $row->new_gedcom, $match); 11522ad3b5bSGreg Roach 1166b9cb339SGreg Roach $factory = $factories[$match[1]] ?? Registry::gedcomRecordFactory(); 11722ad3b5bSGreg Roach 11822ad3b5bSGreg Roach $row->record = $factory->new($row->xref, $row->old_gedcom, $row->new_gedcom, $tree); 11922ad3b5bSGreg Roach 12022ad3b5bSGreg Roach $changes[$row->xref][] = $row; 12122ad3b5bSGreg Roach } 12222ad3b5bSGreg Roach 12322ad3b5bSGreg Roach return $changes; 12422ad3b5bSGreg Roach } 12522ad3b5bSGreg Roach 12622ad3b5bSGreg Roach /** 12722e73debSGreg Roach * Accept all changes to a tree. 12822e73debSGreg Roach * 12922e73debSGreg Roach * @param Tree $tree 13022e73debSGreg Roach * 13122ad3b5bSGreg Roach * @param int $n 13222ad3b5bSGreg Roach * 13322e73debSGreg Roach * @return void 1340d047a8cSGreg Roach * @throws GedcomErrorException 13522e73debSGreg Roach */ 13622ad3b5bSGreg Roach public function acceptTree(Tree $tree, int $n): void 13722e73debSGreg Roach { 13822ad3b5bSGreg Roach $xrefs = $this->pendingXrefs($tree); 13922ad3b5bSGreg Roach 14022e73debSGreg Roach $changes = DB::table('change') 14122e73debSGreg Roach ->where('gedcom_id', '=', $tree->id()) 14222e73debSGreg Roach ->where('status', '=', 'pending') 14322ad3b5bSGreg Roach ->whereIn('xref', $xrefs->slice(0, $n)) 14422e73debSGreg Roach ->orderBy('change_id') 14522ad3b5bSGreg Roach ->lockForUpdate() 14622e73debSGreg Roach ->get(); 14722e73debSGreg Roach 14822e73debSGreg Roach foreach ($changes as $change) { 14922e73debSGreg Roach if ($change->new_gedcom === '') { 15022e73debSGreg Roach // delete 1512c685d76SGreg Roach $this->gedcom_import_service->updateRecord($change->old_gedcom, $tree, true); 15222e73debSGreg Roach } else { 15322e73debSGreg Roach // add/update 1542c685d76SGreg Roach $this->gedcom_import_service->updateRecord($change->new_gedcom, $tree, false); 15522e73debSGreg Roach } 15622e73debSGreg Roach 15722e73debSGreg Roach DB::table('change') 15822e73debSGreg Roach ->where('change_id', '=', $change->change_id) 15922e73debSGreg Roach ->update(['status' => 'accepted']); 16022e73debSGreg Roach } 16122e73debSGreg Roach } 16222e73debSGreg Roach 16322e73debSGreg Roach /** 16422e73debSGreg Roach * Accept all changes to a record. 16522e73debSGreg Roach * 16622e73debSGreg Roach * @param GedcomRecord $record 16722e73debSGreg Roach */ 16822e73debSGreg Roach public function acceptRecord(GedcomRecord $record): void 16922e73debSGreg Roach { 17022e73debSGreg Roach $changes = DB::table('change') 17122e73debSGreg Roach ->where('gedcom_id', '=', $record->tree()->id()) 17222e73debSGreg Roach ->where('xref', '=', $record->xref()) 17322e73debSGreg Roach ->where('status', '=', 'pending') 17422e73debSGreg Roach ->orderBy('change_id') 17522ad3b5bSGreg Roach ->lockForUpdate() 17622e73debSGreg Roach ->get(); 17722e73debSGreg Roach 17822e73debSGreg Roach foreach ($changes as $change) { 17922e73debSGreg Roach if ($change->new_gedcom === '') { 18022e73debSGreg Roach // delete 1812c685d76SGreg Roach $this->gedcom_import_service->updateRecord($change->old_gedcom, $record->tree(), true); 18222e73debSGreg Roach } else { 18322e73debSGreg Roach // add/update 1842c685d76SGreg Roach $this->gedcom_import_service->updateRecord($change->new_gedcom, $record->tree(), false); 18522e73debSGreg Roach } 18622e73debSGreg Roach 18722e73debSGreg Roach DB::table('change') 18822e73debSGreg Roach ->where('change_id', '=', $change->change_id) 18922e73debSGreg Roach ->update(['status' => 'accepted']); 19022e73debSGreg Roach } 19122e73debSGreg Roach } 19222e73debSGreg Roach 19322e73debSGreg Roach /** 19422e73debSGreg Roach * Accept a change (and previous changes) to a record. 19522e73debSGreg Roach * 19622e73debSGreg Roach * @param GedcomRecord $record 19722e73debSGreg Roach * @param string $change_id 19822e73debSGreg Roach */ 19922e73debSGreg Roach public function acceptChange(GedcomRecord $record, string $change_id): void 20022e73debSGreg Roach { 20122e73debSGreg Roach $changes = DB::table('change') 20222e73debSGreg Roach ->where('gedcom_id', '=', $record->tree()->id()) 20322e73debSGreg Roach ->where('xref', '=', $record->xref()) 20422e73debSGreg Roach ->where('change_id', '<=', $change_id) 20522e73debSGreg Roach ->where('status', '=', 'pending') 20622e73debSGreg Roach ->orderBy('change_id') 20722e73debSGreg Roach ->get(); 20822e73debSGreg Roach 20922e73debSGreg Roach foreach ($changes as $change) { 21022e73debSGreg Roach if ($change->new_gedcom === '') { 21122e73debSGreg Roach // delete 2122c685d76SGreg Roach $this->gedcom_import_service->updateRecord($change->old_gedcom, $record->tree(), true); 21322e73debSGreg Roach } else { 21422e73debSGreg Roach // add/update 2152c685d76SGreg Roach $this->gedcom_import_service->updateRecord($change->new_gedcom, $record->tree(), false); 21622e73debSGreg Roach } 21722e73debSGreg Roach 21822e73debSGreg Roach DB::table('change') 21922e73debSGreg Roach ->where('change_id', '=', $change->change_id) 22022e73debSGreg Roach ->update(['status' => 'accepted']); 22122e73debSGreg Roach } 22222e73debSGreg Roach } 22322e73debSGreg Roach 22422e73debSGreg Roach /** 22522e73debSGreg Roach * Reject all changes to a tree. 22622e73debSGreg Roach * 22722e73debSGreg Roach * @param Tree $tree 22822e73debSGreg Roach */ 22922e73debSGreg Roach public function rejectTree(Tree $tree): void 23022e73debSGreg Roach { 23122e73debSGreg Roach DB::table('change') 23222e73debSGreg Roach ->where('gedcom_id', '=', $tree->id()) 23322e73debSGreg Roach ->where('status', '=', 'pending') 23422e73debSGreg Roach ->update(['status' => 'rejected']); 23522e73debSGreg Roach } 23622e73debSGreg Roach 23722e73debSGreg Roach /** 23822e73debSGreg Roach * Reject a change (subsequent changes) to a record. 23922e73debSGreg Roach * 24022e73debSGreg Roach * @param GedcomRecord $record 24122e73debSGreg Roach * @param string $change_id 24222e73debSGreg Roach */ 24322e73debSGreg Roach public function rejectChange(GedcomRecord $record, string $change_id): void 24422e73debSGreg Roach { 24522e73debSGreg Roach DB::table('change') 24622e73debSGreg Roach ->where('gedcom_id', '=', $record->tree()->id()) 24722e73debSGreg Roach ->where('xref', '=', $record->xref()) 24822e73debSGreg Roach ->where('change_id', '>=', $change_id) 24922e73debSGreg Roach ->where('status', '=', 'pending') 25022e73debSGreg Roach ->update(['status' => 'rejected']); 25122e73debSGreg Roach } 25222e73debSGreg Roach 25322e73debSGreg Roach /** 25422e73debSGreg Roach * Reject all changes to a record. 25522e73debSGreg Roach * 25622e73debSGreg Roach * @param GedcomRecord $record 25722e73debSGreg Roach */ 25822e73debSGreg Roach public function rejectRecord(GedcomRecord $record): void 25922e73debSGreg Roach { 26022e73debSGreg Roach DB::table('change') 26122e73debSGreg Roach ->where('gedcom_id', '=', $record->tree()->id()) 26222e73debSGreg Roach ->where('xref', '=', $record->xref()) 26322e73debSGreg Roach ->where('status', '=', 'pending') 26422e73debSGreg Roach ->update(['status' => 'rejected']); 26522e73debSGreg Roach } 26622e73debSGreg Roach 26722e73debSGreg Roach /** 26822e73debSGreg Roach * Generate a query for filtering the changes log. 26922e73debSGreg Roach * 27009482a55SGreg Roach * @param array<string> $params 27122e73debSGreg Roach * 27222e73debSGreg Roach * @return Builder 27322e73debSGreg Roach */ 27457bfa969SGreg Roach public function changesQuery(array $params): Builder 27522e73debSGreg Roach { 27657bfa969SGreg Roach $tree = $params['tree']; 27757bfa969SGreg Roach $from = $params['from'] ?? ''; 27857bfa969SGreg Roach $to = $params['to'] ?? ''; 27957bfa969SGreg Roach $type = $params['type'] ?? ''; 28057bfa969SGreg Roach $oldged = $params['oldged'] ?? ''; 28157bfa969SGreg Roach $newged = $params['newged'] ?? ''; 28257bfa969SGreg Roach $xref = $params['xref'] ?? ''; 28357bfa969SGreg Roach $username = $params['username'] ?? ''; 28422e73debSGreg Roach 28522e73debSGreg Roach $query = DB::table('change') 28622e73debSGreg Roach ->leftJoin('user', 'user.user_id', '=', 'change.user_id') 28722e73debSGreg Roach ->join('gedcom', 'gedcom.gedcom_id', '=', 'change.gedcom_id') 28822e73debSGreg Roach ->select(['change.*', new Expression("COALESCE(user_name, '<none>') AS user_name"), 'gedcom_name']) 28957bfa969SGreg Roach ->where('gedcom_name', '=', $tree); 29022e73debSGreg Roach 29122e73debSGreg Roach if ($from !== '') { 292d97083feSGreg Roach $query->where('change_time', '>=', Registry::timestampFactory()->fromString($from, 'Y-m-d')->toDateString()); 29322e73debSGreg Roach } 29422e73debSGreg Roach 29522e73debSGreg Roach if ($to !== '') { 29622e73debSGreg Roach // before end of the day 297d97083feSGreg Roach $query->where('change_time', '<', Registry::timestampFactory()->fromString($to, 'Y-m-d')->addDays(1)->toDateString()); 29822e73debSGreg Roach } 29922e73debSGreg Roach 30022e73debSGreg Roach if ($type !== '') { 30122e73debSGreg Roach $query->where('status', '=', $type); 30222e73debSGreg Roach } 30322e73debSGreg Roach 30422e73debSGreg Roach if ($oldged !== '') { 305b5961194SGreg Roach $query->where('old_gedcom', 'LIKE', '%' . addcslashes($oldged, '\\%_') . '%'); 30622e73debSGreg Roach } 30722e73debSGreg Roach if ($newged !== '') { 308b5961194SGreg Roach $query->where('new_gedcom', 'LIKE', '%' . addcslashes($newged, '\\%_') . '%'); 30922e73debSGreg Roach } 31022e73debSGreg Roach 31122e73debSGreg Roach if ($xref !== '') { 31222e73debSGreg Roach $query->where('xref', '=', $xref); 31322e73debSGreg Roach } 31422e73debSGreg Roach 31522e73debSGreg Roach if ($username !== '') { 316b5961194SGreg Roach $query->where('user_name', 'LIKE', '%' . addcslashes($username, '\\%_') . '%'); 31722e73debSGreg Roach } 31822e73debSGreg Roach 31922e73debSGreg Roach return $query; 32022e73debSGreg Roach } 32122e73debSGreg Roach} 322