15bbfbb82SGreg Roach<?php 25bbfbb82SGreg Roach 35bbfbb82SGreg Roach/** 45bbfbb82SGreg Roach * webtrees: online genealogy 5d11be702SGreg Roach * Copyright (C) 2023 webtrees development team 65bbfbb82SGreg Roach * This program is free software: you can redistribute it and/or modify 75bbfbb82SGreg Roach * it under the terms of the GNU General Public License as published by 85bbfbb82SGreg Roach * the Free Software Foundation, either version 3 of the License, or 95bbfbb82SGreg Roach * (at your option) any later version. 105bbfbb82SGreg Roach * This program is distributed in the hope that it will be useful, 115bbfbb82SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of 125bbfbb82SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 135bbfbb82SGreg Roach * GNU General Public License for more details. 145bbfbb82SGreg 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/>. 165bbfbb82SGreg Roach */ 175bbfbb82SGreg Roach 185bbfbb82SGreg Roachdeclare(strict_types=1); 195bbfbb82SGreg Roach 205bbfbb82SGreg Roachnamespace Fisharebest\Webtrees\Http\RequestHandlers; 215bbfbb82SGreg Roach 225bbfbb82SGreg Roachuse Fisharebest\Webtrees\Auth; 231fe542e9SGreg Roachuse Fisharebest\Webtrees\Contracts\UserInterface; 246f4ec3caSGreg Roachuse Fisharebest\Webtrees\DB; 255bbfbb82SGreg Roachuse Fisharebest\Webtrees\FlashMessages; 265bbfbb82SGreg Roachuse Fisharebest\Webtrees\I18N; 276b9cb339SGreg Roachuse Fisharebest\Webtrees\Registry; 284991f205SGreg Roachuse Fisharebest\Webtrees\Services\LinkedRecordService; 29b55cbc6bSGreg Roachuse Fisharebest\Webtrees\Validator; 305bbfbb82SGreg Roachuse Illuminate\Database\Query\Expression; 315bbfbb82SGreg Roachuse Psr\Http\Message\ResponseInterface; 325bbfbb82SGreg Roachuse Psr\Http\Message\ServerRequestInterface; 335bbfbb82SGreg Roachuse Psr\Http\Server\RequestHandlerInterface; 345bbfbb82SGreg Roach 355bbfbb82SGreg Roachuse function e; 365bbfbb82SGreg Roachuse function in_array; 375bbfbb82SGreg Roachuse function preg_replace; 385bbfbb82SGreg Roachuse function redirect; 395bbfbb82SGreg Roachuse function route; 405bbfbb82SGreg Roachuse function str_replace; 415bbfbb82SGreg Roach 425bbfbb82SGreg Roach/** 435bbfbb82SGreg Roach * Merge records 445bbfbb82SGreg Roach */ 455bbfbb82SGreg Roachclass MergeFactsAction implements RequestHandlerInterface 465bbfbb82SGreg Roach{ 474991f205SGreg Roach private LinkedRecordService $linked_record_service; 484991f205SGreg Roach 494991f205SGreg Roach /** 504991f205SGreg Roach * @param LinkedRecordService $linked_record_service 514991f205SGreg Roach */ 524991f205SGreg Roach public function __construct(LinkedRecordService $linked_record_service) 534991f205SGreg Roach { 544991f205SGreg Roach $this->linked_record_service = $linked_record_service; 554991f205SGreg Roach } 564991f205SGreg Roach 575bbfbb82SGreg Roach /** 585bbfbb82SGreg Roach * @param ServerRequestInterface $request 595bbfbb82SGreg Roach * 605bbfbb82SGreg Roach * @return ResponseInterface 615bbfbb82SGreg Roach */ 625bbfbb82SGreg Roach public function handle(ServerRequestInterface $request): ResponseInterface 635bbfbb82SGreg Roach { 64b55cbc6bSGreg Roach $tree = Validator::attributes($request)->tree(); 65748dbe15SGreg Roach $xref1 = Validator::parsedBody($request)->isXref()->string('xref1'); 66748dbe15SGreg Roach $xref2 = Validator::parsedBody($request)->isXref()->string('xref2'); 67748dbe15SGreg Roach $keep1 = Validator::parsedBody($request)->array('keep1'); 68748dbe15SGreg Roach $keep2 = Validator::parsedBody($request)->array('keep2'); 695bbfbb82SGreg Roach 705bbfbb82SGreg Roach // Merge record2 into record1 716b9cb339SGreg Roach $record1 = Registry::gedcomRecordFactory()->make($xref1, $tree); 726b9cb339SGreg Roach $record2 = Registry::gedcomRecordFactory()->make($xref2, $tree); 735bbfbb82SGreg Roach 745bbfbb82SGreg Roach if ( 755bbfbb82SGreg Roach $record1 === null || 765bbfbb82SGreg Roach $record2 === null || 775bbfbb82SGreg Roach $record1 === $record2 || 7802467d32SGreg Roach $record1->tag() !== $record2->tag() || 795bbfbb82SGreg Roach $record1->isPendingDeletion() || 805bbfbb82SGreg Roach $record2->isPendingDeletion() 815bbfbb82SGreg Roach ) { 825bbfbb82SGreg Roach return redirect(route(MergeRecordsPage::class, [ 835bbfbb82SGreg Roach 'tree' => $tree->name(), 845bbfbb82SGreg Roach 'xref1' => $xref1, 855bbfbb82SGreg Roach 'xref2' => $xref2, 865bbfbb82SGreg Roach ])); 875bbfbb82SGreg Roach } 885bbfbb82SGreg Roach 895bbfbb82SGreg Roach // If we are not auto-accepting, then we can show a link to the pending deletion 901fe542e9SGreg Roach if (Auth::user()->getPreference(UserInterface::PREF_AUTO_ACCEPT_EDITS) === '1') { 915bbfbb82SGreg Roach $record2_name = $record2->fullName(); 925bbfbb82SGreg Roach } else { 935bbfbb82SGreg Roach $record2_name = '<a class="alert-link" href="' . e($record2->url()) . '">' . $record2->fullName() . '</a>'; 945bbfbb82SGreg Roach } 955bbfbb82SGreg Roach 965bbfbb82SGreg Roach // Update records that link to the one we will be removing. 974991f205SGreg Roach $linking_records = $this->linked_record_service->allLinkedRecords($record2); 985bbfbb82SGreg Roach 995bbfbb82SGreg Roach foreach ($linking_records as $record) { 1005bbfbb82SGreg Roach if (!$record->isPendingDeletion()) { 1015bbfbb82SGreg Roach /* I18N: The placeholders are the names of individuals, sources, etc. */ 1025bbfbb82SGreg Roach FlashMessages::addMessage(I18N::translate( 1035bbfbb82SGreg Roach 'The link from “%1$s” to “%2$s” has been updated.', 1045bbfbb82SGreg Roach '<a class="alert-link" href="' . e($record->url()) . '">' . $record->fullName() . '</a>', 1055bbfbb82SGreg Roach $record2_name 1065bbfbb82SGreg Roach ), 'info'); 1075bbfbb82SGreg Roach $gedcom = str_replace('@' . $xref2 . '@', '@' . $xref1 . '@', $record->gedcom()); 1085bbfbb82SGreg Roach $gedcom = preg_replace( 109ecc5b3c4SGreg Roach '/(\n1.*@.+@.*(?:\n[2-9].*)*)((?:\n1.*(?:\n[2-9].*)*)*\1)/', 1105bbfbb82SGreg Roach '$2', 1115bbfbb82SGreg Roach $gedcom 1125bbfbb82SGreg Roach ); 1135bbfbb82SGreg Roach $record->updateRecord($gedcom, true); 1145bbfbb82SGreg Roach } 1155bbfbb82SGreg Roach } 1165bbfbb82SGreg Roach 1175bbfbb82SGreg Roach // Update any linked user-accounts 1185bbfbb82SGreg Roach DB::table('user_gedcom_setting') 1195bbfbb82SGreg Roach ->where('gedcom_id', '=', $tree->id()) 1201fe542e9SGreg Roach ->whereIn('setting_name', [UserInterface::PREF_TREE_ACCOUNT_XREF, UserInterface::PREF_TREE_DEFAULT_XREF]) 1215bbfbb82SGreg Roach ->where('setting_value', '=', $xref2) 1225bbfbb82SGreg Roach ->update(['setting_value' => $xref1]); 1235bbfbb82SGreg Roach 124ecc5b3c4SGreg Roach // Merge stories, etc. 125ecc5b3c4SGreg Roach DB::table('block') 126ecc5b3c4SGreg Roach ->where('gedcom_id', '=', $tree->id()) 127ecc5b3c4SGreg Roach ->where('xref', '=', $xref2) 128ecc5b3c4SGreg Roach ->update(['xref' => $xref1]); 129ecc5b3c4SGreg Roach 1305bbfbb82SGreg Roach // Merge hit counters 1315bbfbb82SGreg Roach $hits = DB::table('hit_counter') 1325bbfbb82SGreg Roach ->where('gedcom_id', '=', $tree->id()) 1335bbfbb82SGreg Roach ->whereIn('page_parameter', [$xref1, $xref2]) 1345bbfbb82SGreg Roach ->groupBy(['page_name']) 135*11225d5eSGreg Roach ->pluck(new Expression('SUM(page_count) AS total'), 'page_name'); 1365bbfbb82SGreg Roach 1375bbfbb82SGreg Roach foreach ($hits as $page_name => $page_count) { 1385bbfbb82SGreg Roach DB::table('hit_counter') 1395bbfbb82SGreg Roach ->where('gedcom_id', '=', $tree->id()) 1405bbfbb82SGreg Roach ->where('page_name', '=', $page_name) 1414d74848fSGreg Roach ->where('page_parameter', '=', $xref1) 1425bbfbb82SGreg Roach ->update(['page_count' => $page_count]); 1435bbfbb82SGreg Roach } 1445bbfbb82SGreg Roach 1455bbfbb82SGreg Roach DB::table('hit_counter') 1465bbfbb82SGreg Roach ->where('gedcom_id', '=', $tree->id()) 1475bbfbb82SGreg Roach ->where('page_parameter', '=', $xref2) 1485bbfbb82SGreg Roach ->delete(); 1495bbfbb82SGreg Roach 15002467d32SGreg Roach $gedcom = '0 @' . $record1->xref() . '@ ' . $record1->tag(); 1515bbfbb82SGreg Roach 1525bbfbb82SGreg Roach foreach ($record1->facts() as $fact) { 1535bbfbb82SGreg Roach if (in_array($fact->id(), $keep1, true)) { 1545bbfbb82SGreg Roach $gedcom .= "\n" . $fact->gedcom(); 1555bbfbb82SGreg Roach } 1565bbfbb82SGreg Roach } 1575bbfbb82SGreg Roach 1585bbfbb82SGreg Roach foreach ($record2->facts() as $fact) { 1595bbfbb82SGreg Roach if (in_array($fact->id(), $keep2, true)) { 1605bbfbb82SGreg Roach $gedcom .= "\n" . $fact->gedcom(); 1615bbfbb82SGreg Roach } 1625bbfbb82SGreg Roach } 1635bbfbb82SGreg Roach 1645bbfbb82SGreg Roach DB::table('favorite') 1655bbfbb82SGreg Roach ->where('gedcom_id', '=', $tree->id()) 1665bbfbb82SGreg Roach ->where('xref', '=', $xref2) 1675bbfbb82SGreg Roach ->update(['xref' => $xref1]); 1685bbfbb82SGreg Roach 1695bbfbb82SGreg Roach $record1->updateRecord($gedcom, true); 1705bbfbb82SGreg Roach $record2->deleteRecord(); 1715bbfbb82SGreg Roach 1725bbfbb82SGreg Roach /* I18N: Records are individuals, sources, etc. */ 1735bbfbb82SGreg Roach FlashMessages::addMessage(I18N::translate( 1745bbfbb82SGreg Roach 'The records “%1$s” and “%2$s” have been merged.', 1755bbfbb82SGreg Roach '<a class="alert-link" href="' . e($record1->url()) . '">' . $record1->fullName() . '</a>', 1765bbfbb82SGreg Roach $record2_name 1775bbfbb82SGreg Roach ), 'success'); 1785bbfbb82SGreg Roach 17941413128SGreg Roach return redirect(route(ManageTrees::class, ['tree' => $tree->name()])); 1805bbfbb82SGreg Roach } 1815bbfbb82SGreg Roach} 182