122e73debSGreg Roach<?php 222e73debSGreg Roach 322e73debSGreg Roach/** 422e73debSGreg Roach * webtrees: online genealogy 589f7189bSGreg Roach * Copyright (C) 2021 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 2222e73debSGreg Roachuse Fisharebest\Webtrees\Carbon; 230d047a8cSGreg Roachuse Fisharebest\Webtrees\Exceptions\GedcomErrorException; 2422ad3b5bSGreg Roachuse Fisharebest\Webtrees\Family; 2522e73debSGreg Roachuse Fisharebest\Webtrees\Functions\FunctionsImport; 2622ad3b5bSGreg Roachuse Fisharebest\Webtrees\Gedcom; 2722e73debSGreg Roachuse Fisharebest\Webtrees\GedcomRecord; 2822ad3b5bSGreg Roachuse Fisharebest\Webtrees\Header; 2922ad3b5bSGreg Roachuse Fisharebest\Webtrees\Individual; 3022ad3b5bSGreg Roachuse Fisharebest\Webtrees\Location; 3122ad3b5bSGreg Roachuse Fisharebest\Webtrees\Media; 3222ad3b5bSGreg Roachuse Fisharebest\Webtrees\Note; 3381b729d3SGreg Roachuse Fisharebest\Webtrees\Registry; 3422ad3b5bSGreg Roachuse Fisharebest\Webtrees\Repository; 3522ad3b5bSGreg Roachuse Fisharebest\Webtrees\Source; 3622ad3b5bSGreg Roachuse Fisharebest\Webtrees\Submission; 3722ad3b5bSGreg Roachuse Fisharebest\Webtrees\Submitter; 3822e73debSGreg Roachuse Fisharebest\Webtrees\Tree; 3922e73debSGreg Roachuse Illuminate\Database\Capsule\Manager as DB; 4022e73debSGreg Roachuse Illuminate\Database\Query\Builder; 4122e73debSGreg Roachuse Illuminate\Database\Query\Expression; 4222ad3b5bSGreg Roachuse Illuminate\Support\Collection; 4322ad3b5bSGreg Roach 44b5961194SGreg Roachuse function addcslashes; 4522ad3b5bSGreg Roachuse function preg_match; 46b5961194SGreg Roach 4722e73debSGreg Roach/** 4822e73debSGreg Roach * Manage pending changes 4922e73debSGreg Roach */ 5022e73debSGreg Roachclass PendingChangesService 5122e73debSGreg Roach{ 5222e73debSGreg Roach /** 5322ad3b5bSGreg Roach * Which records have pending changes 5422ad3b5bSGreg Roach * 5522ad3b5bSGreg Roach * @param Tree $tree 5622ad3b5bSGreg Roach * 57*36779af1SGreg Roach * @return Collection<int,string> 5822ad3b5bSGreg Roach */ 5922ad3b5bSGreg Roach public function pendingXrefs(Tree $tree): Collection 6022ad3b5bSGreg Roach { 6122ad3b5bSGreg Roach return DB::table('change') 6222ad3b5bSGreg Roach ->where('status', '=', 'pending') 6322ad3b5bSGreg Roach ->where('gedcom_id', '=', $tree->id()) 6422ad3b5bSGreg Roach ->orderBy('xref') 6522ad3b5bSGreg Roach ->groupBy(['xref']) 6622ad3b5bSGreg Roach ->pluck('xref'); 6722ad3b5bSGreg Roach } 6822ad3b5bSGreg Roach 6922ad3b5bSGreg Roach /** 7022ad3b5bSGreg Roach * @param Tree $tree 7122ad3b5bSGreg Roach * @param int $n 7222ad3b5bSGreg Roach * 73f70bcff5SGreg Roach * @return array<array<object>> 7422ad3b5bSGreg Roach */ 7522ad3b5bSGreg Roach public function pendingChanges(Tree $tree, int $n): array 7622ad3b5bSGreg Roach { 7722ad3b5bSGreg Roach $xrefs = $this->pendingXrefs($tree); 7822ad3b5bSGreg Roach 7922ad3b5bSGreg Roach $rows = DB::table('change') 8022ad3b5bSGreg Roach ->join('user', 'user.user_id', '=', 'change.user_id') 8122ad3b5bSGreg Roach ->where('status', '=', 'pending') 8222ad3b5bSGreg Roach ->where('gedcom_id', '=', $tree->id()) 8322ad3b5bSGreg Roach ->whereIn('xref', $xrefs->slice(0, $n)) 8422ad3b5bSGreg Roach ->orderBy('change.change_id') 8522ad3b5bSGreg Roach ->select(['change.*', 'user.user_name', 'user.real_name']) 8622ad3b5bSGreg Roach ->get(); 8722ad3b5bSGreg Roach 8822ad3b5bSGreg Roach $changes = []; 8922ad3b5bSGreg Roach 9022ad3b5bSGreg Roach $factories = [ 916b9cb339SGreg Roach Individual::RECORD_TYPE => Registry::individualFactory(), 926b9cb339SGreg Roach Family::RECORD_TYPE => Registry::familyFactory(), 936b9cb339SGreg Roach Source::RECORD_TYPE => Registry::sourceFactory(), 946b9cb339SGreg Roach Repository::RECORD_TYPE => Registry::repositoryFactory(), 956b9cb339SGreg Roach Media::RECORD_TYPE => Registry::mediaFactory(), 966b9cb339SGreg Roach Note::RECORD_TYPE => Registry::noteFactory(), 976b9cb339SGreg Roach Submitter::RECORD_TYPE => Registry::submitterFactory(), 986b9cb339SGreg Roach Submission::RECORD_TYPE => Registry::submissionFactory(), 996b9cb339SGreg Roach Location::RECORD_TYPE => Registry::locationFactory(), 1006b9cb339SGreg Roach Header::RECORD_TYPE => Registry::headerFactory(), 10122ad3b5bSGreg Roach ]; 10222ad3b5bSGreg Roach 10322ad3b5bSGreg Roach foreach ($rows as $row) { 10422ad3b5bSGreg Roach $row->change_time = Carbon::make($row->change_time); 10522ad3b5bSGreg Roach 10622ad3b5bSGreg Roach preg_match('/^0 (?:@' . Gedcom::REGEX_XREF . '@ )?(' . Gedcom::REGEX_TAG . ')/', $row->old_gedcom . $row->new_gedcom, $match); 10722ad3b5bSGreg Roach 1086b9cb339SGreg Roach $factory = $factories[$match[1]] ?? Registry::gedcomRecordFactory(); 10922ad3b5bSGreg Roach 11022ad3b5bSGreg Roach $row->record = $factory->new($row->xref, $row->old_gedcom, $row->new_gedcom, $tree); 11122ad3b5bSGreg Roach 11222ad3b5bSGreg Roach $changes[$row->xref][] = $row; 11322ad3b5bSGreg Roach } 11422ad3b5bSGreg Roach 11522ad3b5bSGreg Roach return $changes; 11622ad3b5bSGreg Roach } 11722ad3b5bSGreg Roach 11822ad3b5bSGreg Roach /** 11922e73debSGreg Roach * Accept all changes to a tree. 12022e73debSGreg Roach * 12122e73debSGreg Roach * @param Tree $tree 12222e73debSGreg Roach * 12322ad3b5bSGreg Roach * @param int $n 12422ad3b5bSGreg Roach * 12522e73debSGreg Roach * @return void 1260d047a8cSGreg Roach * @throws GedcomErrorException 12722e73debSGreg Roach */ 12822ad3b5bSGreg Roach public function acceptTree(Tree $tree, int $n): void 12922e73debSGreg Roach { 13022ad3b5bSGreg Roach $xrefs = $this->pendingXrefs($tree); 13122ad3b5bSGreg Roach 13222e73debSGreg Roach $changes = DB::table('change') 13322e73debSGreg Roach ->where('gedcom_id', '=', $tree->id()) 13422e73debSGreg Roach ->where('status', '=', 'pending') 13522ad3b5bSGreg Roach ->whereIn('xref', $xrefs->slice(0, $n)) 13622e73debSGreg Roach ->orderBy('change_id') 13722ad3b5bSGreg Roach ->lockForUpdate() 13822e73debSGreg Roach ->get(); 13922e73debSGreg Roach 14022e73debSGreg Roach foreach ($changes as $change) { 14122e73debSGreg Roach if ($change->new_gedcom === '') { 14222e73debSGreg Roach // delete 14322e73debSGreg Roach FunctionsImport::updateRecord($change->old_gedcom, $tree, true); 14422e73debSGreg Roach } else { 14522e73debSGreg Roach // add/update 14622e73debSGreg Roach FunctionsImport::updateRecord($change->new_gedcom, $tree, false); 14722e73debSGreg Roach } 14822e73debSGreg Roach 14922e73debSGreg Roach DB::table('change') 15022e73debSGreg Roach ->where('change_id', '=', $change->change_id) 15122e73debSGreg Roach ->update(['status' => 'accepted']); 15222e73debSGreg Roach } 15322e73debSGreg Roach } 15422e73debSGreg Roach 15522e73debSGreg Roach /** 15622e73debSGreg Roach * Accept all changes to a record. 15722e73debSGreg Roach * 15822e73debSGreg Roach * @param GedcomRecord $record 15922e73debSGreg Roach */ 16022e73debSGreg Roach public function acceptRecord(GedcomRecord $record): void 16122e73debSGreg Roach { 16222e73debSGreg Roach $changes = DB::table('change') 16322e73debSGreg Roach ->where('gedcom_id', '=', $record->tree()->id()) 16422e73debSGreg Roach ->where('xref', '=', $record->xref()) 16522e73debSGreg Roach ->where('status', '=', 'pending') 16622e73debSGreg Roach ->orderBy('change_id') 16722ad3b5bSGreg Roach ->lockForUpdate() 16822e73debSGreg Roach ->get(); 16922e73debSGreg Roach 17022e73debSGreg Roach foreach ($changes as $change) { 17122e73debSGreg Roach if ($change->new_gedcom === '') { 17222e73debSGreg Roach // delete 17322e73debSGreg Roach FunctionsImport::updateRecord($change->old_gedcom, $record->tree(), true); 17422e73debSGreg Roach } else { 17522e73debSGreg Roach // add/update 17622e73debSGreg Roach FunctionsImport::updateRecord($change->new_gedcom, $record->tree(), false); 17722e73debSGreg Roach } 17822e73debSGreg Roach 17922e73debSGreg Roach DB::table('change') 18022e73debSGreg Roach ->where('change_id', '=', $change->change_id) 18122e73debSGreg Roach ->update(['status' => 'accepted']); 18222e73debSGreg Roach } 18322e73debSGreg Roach } 18422e73debSGreg Roach 18522e73debSGreg Roach /** 18622e73debSGreg Roach * Accept a change (and previous changes) to a record. 18722e73debSGreg Roach * 18822e73debSGreg Roach * @param GedcomRecord $record 18922e73debSGreg Roach * @param string $change_id 19022e73debSGreg Roach */ 19122e73debSGreg Roach public function acceptChange(GedcomRecord $record, string $change_id): void 19222e73debSGreg Roach { 19322e73debSGreg Roach $changes = DB::table('change') 19422e73debSGreg Roach ->where('gedcom_id', '=', $record->tree()->id()) 19522e73debSGreg Roach ->where('xref', '=', $record->xref()) 19622e73debSGreg Roach ->where('change_id', '<=', $change_id) 19722e73debSGreg Roach ->where('status', '=', 'pending') 19822e73debSGreg Roach ->orderBy('change_id') 19922e73debSGreg Roach ->get(); 20022e73debSGreg Roach 20122e73debSGreg Roach foreach ($changes as $change) { 20222e73debSGreg Roach if ($change->new_gedcom === '') { 20322e73debSGreg Roach // delete 20422e73debSGreg Roach FunctionsImport::updateRecord($change->old_gedcom, $record->tree(), true); 20522e73debSGreg Roach } else { 20622e73debSGreg Roach // add/update 20722e73debSGreg Roach FunctionsImport::updateRecord($change->new_gedcom, $record->tree(), false); 20822e73debSGreg Roach } 20922e73debSGreg Roach 21022e73debSGreg Roach DB::table('change') 21122e73debSGreg Roach ->where('change_id', '=', $change->change_id) 21222e73debSGreg Roach ->update(['status' => 'accepted']); 21322e73debSGreg Roach } 21422e73debSGreg Roach } 21522e73debSGreg Roach 21622e73debSGreg Roach /** 21722e73debSGreg Roach * Reject all changes to a tree. 21822e73debSGreg Roach * 21922e73debSGreg Roach * @param Tree $tree 22022e73debSGreg Roach */ 22122e73debSGreg Roach public function rejectTree(Tree $tree): void 22222e73debSGreg Roach { 22322e73debSGreg Roach DB::table('change') 22422e73debSGreg Roach ->where('gedcom_id', '=', $tree->id()) 22522e73debSGreg Roach ->where('status', '=', 'pending') 22622e73debSGreg Roach ->update(['status' => 'rejected']); 22722e73debSGreg Roach } 22822e73debSGreg Roach 22922e73debSGreg Roach /** 23022e73debSGreg Roach * Reject a change (subsequent changes) to a record. 23122e73debSGreg Roach * 23222e73debSGreg Roach * @param GedcomRecord $record 23322e73debSGreg Roach * @param string $change_id 23422e73debSGreg Roach */ 23522e73debSGreg Roach public function rejectChange(GedcomRecord $record, string $change_id): void 23622e73debSGreg Roach { 23722e73debSGreg Roach DB::table('change') 23822e73debSGreg Roach ->where('gedcom_id', '=', $record->tree()->id()) 23922e73debSGreg Roach ->where('xref', '=', $record->xref()) 24022e73debSGreg Roach ->where('change_id', '>=', $change_id) 24122e73debSGreg Roach ->where('status', '=', 'pending') 24222e73debSGreg Roach ->update(['status' => 'rejected']); 24322e73debSGreg Roach } 24422e73debSGreg Roach 24522e73debSGreg Roach /** 24622e73debSGreg Roach * Reject all changes to a record. 24722e73debSGreg Roach * 24822e73debSGreg Roach * @param GedcomRecord $record 24922e73debSGreg Roach */ 25022e73debSGreg Roach public function rejectRecord(GedcomRecord $record): void 25122e73debSGreg Roach { 25222e73debSGreg Roach DB::table('change') 25322e73debSGreg Roach ->where('gedcom_id', '=', $record->tree()->id()) 25422e73debSGreg Roach ->where('xref', '=', $record->xref()) 25522e73debSGreg Roach ->where('status', '=', 'pending') 25622e73debSGreg Roach ->update(['status' => 'rejected']); 25722e73debSGreg Roach } 25822e73debSGreg Roach 25922e73debSGreg Roach /** 26022e73debSGreg Roach * Generate a query for filtering the changes log. 26122e73debSGreg Roach * 26209482a55SGreg Roach * @param array<string> $params 26322e73debSGreg Roach * 26422e73debSGreg Roach * @return Builder 26522e73debSGreg Roach */ 26657bfa969SGreg Roach public function changesQuery(array $params): Builder 26722e73debSGreg Roach { 26857bfa969SGreg Roach $tree = $params['tree']; 26957bfa969SGreg Roach $from = $params['from'] ?? ''; 27057bfa969SGreg Roach $to = $params['to'] ?? ''; 27157bfa969SGreg Roach $type = $params['type'] ?? ''; 27257bfa969SGreg Roach $oldged = $params['oldged'] ?? ''; 27357bfa969SGreg Roach $newged = $params['newged'] ?? ''; 27457bfa969SGreg Roach $xref = $params['xref'] ?? ''; 27557bfa969SGreg Roach $username = $params['username'] ?? ''; 27622e73debSGreg Roach 27722e73debSGreg Roach $query = DB::table('change') 27822e73debSGreg Roach ->leftJoin('user', 'user.user_id', '=', 'change.user_id') 27922e73debSGreg Roach ->join('gedcom', 'gedcom.gedcom_id', '=', 'change.gedcom_id') 28022e73debSGreg Roach ->select(['change.*', new Expression("COALESCE(user_name, '<none>') AS user_name"), 'gedcom_name']) 28157bfa969SGreg Roach ->where('gedcom_name', '=', $tree); 28222e73debSGreg Roach 28322e73debSGreg Roach if ($from !== '') { 28422e73debSGreg Roach $query->where('change_time', '>=', $from); 28522e73debSGreg Roach } 28622e73debSGreg Roach 28722e73debSGreg Roach if ($to !== '') { 28822e73debSGreg Roach // before end of the day 28922e73debSGreg Roach $query->where('change_time', '<', Carbon::make($to)->addDay()); 29022e73debSGreg Roach } 29122e73debSGreg Roach 29222e73debSGreg Roach if ($type !== '') { 29322e73debSGreg Roach $query->where('status', '=', $type); 29422e73debSGreg Roach } 29522e73debSGreg Roach 29622e73debSGreg Roach if ($oldged !== '') { 297b5961194SGreg Roach $query->where('old_gedcom', 'LIKE', '%' . addcslashes($oldged, '\\%_') . '%'); 29822e73debSGreg Roach } 29922e73debSGreg Roach if ($newged !== '') { 300b5961194SGreg Roach $query->where('new_gedcom', 'LIKE', '%' . addcslashes($newged, '\\%_') . '%'); 30122e73debSGreg Roach } 30222e73debSGreg Roach 30322e73debSGreg Roach if ($xref !== '') { 30422e73debSGreg Roach $query->where('xref', '=', $xref); 30522e73debSGreg Roach } 30622e73debSGreg Roach 30722e73debSGreg Roach if ($username !== '') { 308b5961194SGreg Roach $query->where('user_name', 'LIKE', '%' . addcslashes($username, '\\%_') . '%'); 30922e73debSGreg Roach } 31022e73debSGreg Roach 31122e73debSGreg Roach return $query; 31222e73debSGreg Roach } 31322e73debSGreg Roach} 314