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