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 225cac87aeSGreg Roachuse DateInterval; 235cac87aeSGreg Roachuse DateTimeImmutable; 245cac87aeSGreg Roachuse DateTimeZone; 255cac87aeSGreg Roachuse Fisharebest\Webtrees\Auth; 265cac87aeSGreg Roachuse Fisharebest\Webtrees\Contracts\UserInterface; 276f4ec3caSGreg Roachuse Fisharebest\Webtrees\DB; 280d047a8cSGreg Roachuse Fisharebest\Webtrees\Exceptions\GedcomErrorException; 2922ad3b5bSGreg Roachuse Fisharebest\Webtrees\Family; 3022ad3b5bSGreg Roachuse Fisharebest\Webtrees\Gedcom; 3122e73debSGreg Roachuse Fisharebest\Webtrees\GedcomRecord; 3222ad3b5bSGreg Roachuse Fisharebest\Webtrees\Header; 3322ad3b5bSGreg Roachuse Fisharebest\Webtrees\Individual; 3422ad3b5bSGreg Roachuse Fisharebest\Webtrees\Location; 3522ad3b5bSGreg Roachuse Fisharebest\Webtrees\Media; 3622ad3b5bSGreg Roachuse Fisharebest\Webtrees\Note; 3781b729d3SGreg Roachuse Fisharebest\Webtrees\Registry; 3822ad3b5bSGreg Roachuse Fisharebest\Webtrees\Repository; 3922ad3b5bSGreg Roachuse Fisharebest\Webtrees\Source; 4022ad3b5bSGreg Roachuse Fisharebest\Webtrees\Submission; 4122ad3b5bSGreg Roachuse Fisharebest\Webtrees\Submitter; 4222e73debSGreg Roachuse Fisharebest\Webtrees\Tree; 4322e73debSGreg Roachuse Illuminate\Database\Query\Builder; 4422e73debSGreg Roachuse Illuminate\Database\Query\Expression; 4522ad3b5bSGreg Roachuse Illuminate\Support\Collection; 4622ad3b5bSGreg Roach 47b5961194SGreg Roachuse function addcslashes; 4822ad3b5bSGreg Roachuse function preg_match; 495cac87aeSGreg Roachuse function var_dump; 50b5961194SGreg Roach 5122e73debSGreg Roach/** 5222e73debSGreg Roach * Manage pending changes 5322e73debSGreg Roach */ 5422e73debSGreg Roachclass PendingChangesService 5522e73debSGreg Roach{ 562c685d76SGreg Roach private GedcomImportService $gedcom_import_service; 572c685d76SGreg Roach 582c685d76SGreg Roach /** 592c685d76SGreg Roach * @param GedcomImportService $gedcom_import_service 602c685d76SGreg Roach */ 612c685d76SGreg Roach public function __construct(GedcomImportService $gedcom_import_service) 622c685d76SGreg Roach { 632c685d76SGreg Roach $this->gedcom_import_service = $gedcom_import_service; 642c685d76SGreg Roach } 652c685d76SGreg Roach 6622e73debSGreg Roach /** 6722ad3b5bSGreg Roach * Which records have pending changes 6822ad3b5bSGreg Roach * 6922ad3b5bSGreg Roach * @param Tree $tree 7022ad3b5bSGreg Roach * 7136779af1SGreg Roach * @return Collection<int,string> 7222ad3b5bSGreg Roach */ 7322ad3b5bSGreg Roach public function pendingXrefs(Tree $tree): Collection 7422ad3b5bSGreg Roach { 7522ad3b5bSGreg Roach return DB::table('change') 7622ad3b5bSGreg Roach ->where('status', '=', 'pending') 7722ad3b5bSGreg Roach ->where('gedcom_id', '=', $tree->id()) 7822ad3b5bSGreg Roach ->orderBy('xref') 7922ad3b5bSGreg Roach ->groupBy(['xref']) 8022ad3b5bSGreg Roach ->pluck('xref'); 8122ad3b5bSGreg Roach } 8222ad3b5bSGreg Roach 8322ad3b5bSGreg Roach /** 8422ad3b5bSGreg Roach * @param Tree $tree 8522ad3b5bSGreg Roach * @param int $n 8622ad3b5bSGreg Roach * 87f70bcff5SGreg Roach * @return array<array<object>> 8822ad3b5bSGreg Roach */ 8922ad3b5bSGreg Roach public function pendingChanges(Tree $tree, int $n): array 9022ad3b5bSGreg Roach { 9122ad3b5bSGreg Roach $xrefs = $this->pendingXrefs($tree); 9222ad3b5bSGreg Roach 9322ad3b5bSGreg Roach $rows = DB::table('change') 9422ad3b5bSGreg Roach ->join('user', 'user.user_id', '=', 'change.user_id') 9522ad3b5bSGreg Roach ->where('status', '=', 'pending') 9622ad3b5bSGreg Roach ->where('gedcom_id', '=', $tree->id()) 9722ad3b5bSGreg Roach ->whereIn('xref', $xrefs->slice(0, $n)) 9822ad3b5bSGreg Roach ->orderBy('change.change_id') 9922ad3b5bSGreg Roach ->select(['change.*', 'user.user_name', 'user.real_name']) 10022ad3b5bSGreg Roach ->get(); 10122ad3b5bSGreg Roach 10222ad3b5bSGreg Roach $changes = []; 10322ad3b5bSGreg Roach 10422ad3b5bSGreg Roach $factories = [ 1056b9cb339SGreg Roach Individual::RECORD_TYPE => Registry::individualFactory(), 1066b9cb339SGreg Roach Family::RECORD_TYPE => Registry::familyFactory(), 1076b9cb339SGreg Roach Source::RECORD_TYPE => Registry::sourceFactory(), 1086b9cb339SGreg Roach Repository::RECORD_TYPE => Registry::repositoryFactory(), 1096b9cb339SGreg Roach Media::RECORD_TYPE => Registry::mediaFactory(), 1106b9cb339SGreg Roach Note::RECORD_TYPE => Registry::noteFactory(), 1116b9cb339SGreg Roach Submitter::RECORD_TYPE => Registry::submitterFactory(), 1126b9cb339SGreg Roach Submission::RECORD_TYPE => Registry::submissionFactory(), 1136b9cb339SGreg Roach Location::RECORD_TYPE => Registry::locationFactory(), 1146b9cb339SGreg Roach Header::RECORD_TYPE => Registry::headerFactory(), 11522ad3b5bSGreg Roach ]; 11622ad3b5bSGreg Roach 11722ad3b5bSGreg Roach foreach ($rows as $row) { 118d97083feSGreg Roach $row->change_time = Registry::timestampFactory()->fromString($row->change_time); 11922ad3b5bSGreg Roach 12022ad3b5bSGreg Roach preg_match('/^0 (?:@' . Gedcom::REGEX_XREF . '@ )?(' . Gedcom::REGEX_TAG . ')/', $row->old_gedcom . $row->new_gedcom, $match); 12122ad3b5bSGreg Roach 1226b9cb339SGreg Roach $factory = $factories[$match[1]] ?? Registry::gedcomRecordFactory(); 12322ad3b5bSGreg Roach 12422ad3b5bSGreg Roach $row->record = $factory->new($row->xref, $row->old_gedcom, $row->new_gedcom, $tree); 12522ad3b5bSGreg Roach 12622ad3b5bSGreg Roach $changes[$row->xref][] = $row; 12722ad3b5bSGreg Roach } 12822ad3b5bSGreg Roach 12922ad3b5bSGreg Roach return $changes; 13022ad3b5bSGreg Roach } 13122ad3b5bSGreg Roach 13222ad3b5bSGreg Roach /** 13322e73debSGreg Roach * Accept all changes to a tree. 13422e73debSGreg Roach * 13522e73debSGreg Roach * @param Tree $tree 13622e73debSGreg Roach * 13722ad3b5bSGreg Roach * @param int $n 13822ad3b5bSGreg Roach * 13922e73debSGreg Roach * @return void 1400d047a8cSGreg Roach * @throws GedcomErrorException 14122e73debSGreg Roach */ 14222ad3b5bSGreg Roach public function acceptTree(Tree $tree, int $n): void 14322e73debSGreg Roach { 14422ad3b5bSGreg Roach $xrefs = $this->pendingXrefs($tree); 14522ad3b5bSGreg Roach 14622e73debSGreg Roach $changes = DB::table('change') 14722e73debSGreg Roach ->where('gedcom_id', '=', $tree->id()) 14822e73debSGreg Roach ->where('status', '=', 'pending') 14922ad3b5bSGreg Roach ->whereIn('xref', $xrefs->slice(0, $n)) 15022e73debSGreg Roach ->orderBy('change_id') 15122ad3b5bSGreg Roach ->lockForUpdate() 15222e73debSGreg Roach ->get(); 15322e73debSGreg Roach 15422e73debSGreg Roach foreach ($changes as $change) { 15522e73debSGreg Roach if ($change->new_gedcom === '') { 15622e73debSGreg Roach // delete 1572c685d76SGreg Roach $this->gedcom_import_service->updateRecord($change->old_gedcom, $tree, true); 15822e73debSGreg Roach } else { 15922e73debSGreg Roach // add/update 1602c685d76SGreg Roach $this->gedcom_import_service->updateRecord($change->new_gedcom, $tree, false); 16122e73debSGreg Roach } 16222e73debSGreg Roach 16322e73debSGreg Roach DB::table('change') 16422e73debSGreg Roach ->where('change_id', '=', $change->change_id) 16522e73debSGreg Roach ->update(['status' => 'accepted']); 16622e73debSGreg Roach } 16722e73debSGreg Roach } 16822e73debSGreg Roach 16922e73debSGreg Roach /** 17022e73debSGreg Roach * Accept all changes to a record. 17122e73debSGreg Roach * 17222e73debSGreg Roach * @param GedcomRecord $record 17322e73debSGreg Roach */ 17422e73debSGreg Roach public function acceptRecord(GedcomRecord $record): void 17522e73debSGreg Roach { 17622e73debSGreg Roach $changes = DB::table('change') 17722e73debSGreg Roach ->where('gedcom_id', '=', $record->tree()->id()) 17822e73debSGreg Roach ->where('xref', '=', $record->xref()) 17922e73debSGreg Roach ->where('status', '=', 'pending') 18022e73debSGreg Roach ->orderBy('change_id') 18122ad3b5bSGreg Roach ->lockForUpdate() 18222e73debSGreg Roach ->get(); 18322e73debSGreg Roach 18422e73debSGreg Roach foreach ($changes as $change) { 18522e73debSGreg Roach if ($change->new_gedcom === '') { 18622e73debSGreg Roach // delete 1872c685d76SGreg Roach $this->gedcom_import_service->updateRecord($change->old_gedcom, $record->tree(), true); 18822e73debSGreg Roach } else { 18922e73debSGreg Roach // add/update 1902c685d76SGreg Roach $this->gedcom_import_service->updateRecord($change->new_gedcom, $record->tree(), false); 19122e73debSGreg Roach } 19222e73debSGreg Roach 19322e73debSGreg Roach DB::table('change') 19422e73debSGreg Roach ->where('change_id', '=', $change->change_id) 19522e73debSGreg Roach ->update(['status' => 'accepted']); 19622e73debSGreg Roach } 19722e73debSGreg Roach } 19822e73debSGreg Roach 19922e73debSGreg Roach /** 20022e73debSGreg Roach * Accept a change (and previous changes) to a record. 20122e73debSGreg Roach * 20222e73debSGreg Roach * @param GedcomRecord $record 20322e73debSGreg Roach * @param string $change_id 20422e73debSGreg Roach */ 20522e73debSGreg Roach public function acceptChange(GedcomRecord $record, string $change_id): void 20622e73debSGreg Roach { 20722e73debSGreg Roach $changes = DB::table('change') 20822e73debSGreg Roach ->where('gedcom_id', '=', $record->tree()->id()) 20922e73debSGreg Roach ->where('xref', '=', $record->xref()) 21022e73debSGreg Roach ->where('change_id', '<=', $change_id) 21122e73debSGreg Roach ->where('status', '=', 'pending') 21222e73debSGreg Roach ->orderBy('change_id') 21322e73debSGreg Roach ->get(); 21422e73debSGreg Roach 21522e73debSGreg Roach foreach ($changes as $change) { 21622e73debSGreg Roach if ($change->new_gedcom === '') { 21722e73debSGreg Roach // delete 2182c685d76SGreg Roach $this->gedcom_import_service->updateRecord($change->old_gedcom, $record->tree(), true); 21922e73debSGreg Roach } else { 22022e73debSGreg Roach // add/update 2212c685d76SGreg Roach $this->gedcom_import_service->updateRecord($change->new_gedcom, $record->tree(), false); 22222e73debSGreg Roach } 22322e73debSGreg Roach 22422e73debSGreg Roach DB::table('change') 22522e73debSGreg Roach ->where('change_id', '=', $change->change_id) 22622e73debSGreg Roach ->update(['status' => 'accepted']); 22722e73debSGreg Roach } 22822e73debSGreg Roach } 22922e73debSGreg Roach 23022e73debSGreg Roach /** 23122e73debSGreg Roach * Reject all changes to a tree. 23222e73debSGreg Roach * 23322e73debSGreg Roach * @param Tree $tree 23422e73debSGreg Roach */ 23522e73debSGreg Roach public function rejectTree(Tree $tree): void 23622e73debSGreg Roach { 23722e73debSGreg Roach DB::table('change') 23822e73debSGreg Roach ->where('gedcom_id', '=', $tree->id()) 23922e73debSGreg Roach ->where('status', '=', 'pending') 24022e73debSGreg Roach ->update(['status' => 'rejected']); 24122e73debSGreg Roach } 24222e73debSGreg Roach 24322e73debSGreg Roach /** 24422e73debSGreg Roach * Reject a change (subsequent changes) to a record. 24522e73debSGreg Roach * 24622e73debSGreg Roach * @param GedcomRecord $record 24722e73debSGreg Roach * @param string $change_id 24822e73debSGreg Roach */ 24922e73debSGreg Roach public function rejectChange(GedcomRecord $record, string $change_id): void 25022e73debSGreg Roach { 25122e73debSGreg Roach DB::table('change') 25222e73debSGreg Roach ->where('gedcom_id', '=', $record->tree()->id()) 25322e73debSGreg Roach ->where('xref', '=', $record->xref()) 25422e73debSGreg Roach ->where('change_id', '>=', $change_id) 25522e73debSGreg Roach ->where('status', '=', 'pending') 25622e73debSGreg Roach ->update(['status' => 'rejected']); 25722e73debSGreg Roach } 25822e73debSGreg Roach 25922e73debSGreg Roach /** 26022e73debSGreg Roach * Reject all changes to a record. 26122e73debSGreg Roach * 26222e73debSGreg Roach * @param GedcomRecord $record 26322e73debSGreg Roach */ 26422e73debSGreg Roach public function rejectRecord(GedcomRecord $record): void 26522e73debSGreg Roach { 26622e73debSGreg Roach DB::table('change') 26722e73debSGreg Roach ->where('gedcom_id', '=', $record->tree()->id()) 26822e73debSGreg Roach ->where('xref', '=', $record->xref()) 26922e73debSGreg Roach ->where('status', '=', 'pending') 27022e73debSGreg Roach ->update(['status' => 'rejected']); 27122e73debSGreg Roach } 27222e73debSGreg Roach 27322e73debSGreg Roach /** 27422e73debSGreg Roach * Generate a query for filtering the changes log. 27522e73debSGreg Roach * 27609482a55SGreg Roach * @param array<string> $params 27722e73debSGreg Roach * 27822e73debSGreg Roach * @return Builder 27922e73debSGreg Roach */ 28057bfa969SGreg Roach public function changesQuery(array $params): Builder 28122e73debSGreg Roach { 28257bfa969SGreg Roach $tree = $params['tree']; 28357bfa969SGreg Roach $from = $params['from'] ?? ''; 28457bfa969SGreg Roach $to = $params['to'] ?? ''; 28557bfa969SGreg Roach $type = $params['type'] ?? ''; 28657bfa969SGreg Roach $oldged = $params['oldged'] ?? ''; 28757bfa969SGreg Roach $newged = $params['newged'] ?? ''; 28857bfa969SGreg Roach $xref = $params['xref'] ?? ''; 28957bfa969SGreg Roach $username = $params['username'] ?? ''; 29022e73debSGreg Roach 29122e73debSGreg Roach $query = DB::table('change') 29222e73debSGreg Roach ->leftJoin('user', 'user.user_id', '=', 'change.user_id') 29322e73debSGreg Roach ->join('gedcom', 'gedcom.gedcom_id', '=', 'change.gedcom_id') 29422e73debSGreg Roach ->select(['change.*', new Expression("COALESCE(user_name, '<none>') AS user_name"), 'gedcom_name']) 29557bfa969SGreg Roach ->where('gedcom_name', '=', $tree); 29622e73debSGreg Roach 2975cac87aeSGreg Roach $tz = new DateTimeZone(Auth::user()->getPreference(UserInterface::PREF_TIME_ZONE, 'UTC')); 2985cac87aeSGreg Roach $utc = new DateTimeZone('UTC'); 2995cac87aeSGreg Roach 30022e73debSGreg Roach if ($from !== '') { 301*6bd19c8cSGreg Roach $from_time = DateTimeImmutable::createFromFormat('!Y-m-d', $from, $tz) 3025cac87aeSGreg Roach ->setTimezone($utc) 3035cac87aeSGreg Roach ->format('Y-m-d H:i:s'); 3045cac87aeSGreg Roach 3055cac87aeSGreg Roach $query->where('change_time', '>=', $from_time); 30622e73debSGreg Roach } 30722e73debSGreg Roach 30822e73debSGreg Roach if ($to !== '') { 30922e73debSGreg Roach // before end of the day 310*6bd19c8cSGreg Roach $to_time = DateTimeImmutable::createFromFormat('!Y-m-d', $from, $tz) 3115cac87aeSGreg Roach ->add(new DateInterval('P1D')) 3125cac87aeSGreg Roach ->setTimezone($utc) 3135cac87aeSGreg Roach ->format('Y-m-d H:i:s'); 3145cac87aeSGreg Roach 3155cac87aeSGreg Roach $query->where('change_time', '<', $to_time); 31622e73debSGreg Roach } 31722e73debSGreg Roach 31822e73debSGreg Roach if ($type !== '') { 31922e73debSGreg Roach $query->where('status', '=', $type); 32022e73debSGreg Roach } 32122e73debSGreg Roach 32222e73debSGreg Roach if ($oldged !== '') { 323b5961194SGreg Roach $query->where('old_gedcom', 'LIKE', '%' . addcslashes($oldged, '\\%_') . '%'); 32422e73debSGreg Roach } 32522e73debSGreg Roach if ($newged !== '') { 326b5961194SGreg Roach $query->where('new_gedcom', 'LIKE', '%' . addcslashes($newged, '\\%_') . '%'); 32722e73debSGreg Roach } 32822e73debSGreg Roach 32922e73debSGreg Roach if ($xref !== '') { 33022e73debSGreg Roach $query->where('xref', '=', $xref); 33122e73debSGreg Roach } 33222e73debSGreg Roach 33322e73debSGreg Roach if ($username !== '') { 334b5961194SGreg Roach $query->where('user_name', 'LIKE', '%' . addcslashes($username, '\\%_') . '%'); 33522e73debSGreg Roach } 33622e73debSGreg Roach 33722e73debSGreg Roach return $query; 33822e73debSGreg Roach } 33922e73debSGreg Roach} 340