1<?php 2 3/** 4 * webtrees: online genealogy 5 * Copyright (C) 2019 webtrees development team 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation, either version 3 of the License, or 9 * (at your option) any later version. 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18declare(strict_types=1); 19 20namespace Fisharebest\Webtrees\Http\RequestHandlers; 21 22use Fisharebest\Webtrees\Auth; 23use Fisharebest\Webtrees\FlashMessages; 24use Fisharebest\Webtrees\GedcomRecord; 25use Fisharebest\Webtrees\I18N; 26use Fisharebest\Webtrees\Tree; 27use Illuminate\Database\Capsule\Manager as DB; 28use Illuminate\Database\Query\Expression; 29use Psr\Http\Message\ResponseInterface; 30use Psr\Http\Message\ServerRequestInterface; 31use Psr\Http\Server\RequestHandlerInterface; 32 33use function assert; 34use function e; 35use function in_array; 36use function preg_replace; 37use function redirect; 38use function route; 39use function str_replace; 40 41/** 42 * Merge records 43 */ 44class MergeFactsAction implements RequestHandlerInterface 45{ 46 /** 47 * @param ServerRequestInterface $request 48 * 49 * @return ResponseInterface 50 */ 51 public function handle(ServerRequestInterface $request): ResponseInterface 52 { 53 $tree = $request->getAttribute('tree'); 54 assert($tree instanceof Tree); 55 56 $xref1 = $request->getParsedBody()['xref1'] ?? ''; 57 $xref2 = $request->getParsedBody()['xref2'] ?? ''; 58 59 $keep1 = $request->getParsedBody()['keep1'] ?? []; 60 $keep2 = $request->getParsedBody()['keep2'] ?? []; 61 62 // Merge record2 into record1 63 $record1 = GedcomRecord::getInstance($xref1, $tree); 64 $record2 = GedcomRecord::getInstance($xref2, $tree); 65 66 if ( 67 $record1 === null || 68 $record2 === null || 69 $record1 === $record2 || 70 $record1::RECORD_TYPE !== $record2::RECORD_TYPE || 71 $record1->isPendingDeletion() || 72 $record2->isPendingDeletion() 73 ) { 74 return redirect(route(MergeRecordsPage::class, [ 75 'tree' => $tree->name(), 76 'xref1' => $xref1, 77 'xref2' => $xref2, 78 ])); 79 } 80 81 // If we are not auto-accepting, then we can show a link to the pending deletion 82 if (Auth::user()->getPreference('auto_accept')) { 83 $record2_name = $record2->fullName(); 84 } else { 85 $record2_name = '<a class="alert-link" href="' . e($record2->url()) . '">' . $record2->fullName() . '</a>'; 86 } 87 88 // Update records that link to the one we will be removing. 89 $linking_records = $record2->linkingRecords(); 90 91 foreach ($linking_records as $record) { 92 if (!$record->isPendingDeletion()) { 93 /* I18N: The placeholders are the names of individuals, sources, etc. */ 94 FlashMessages::addMessage(I18N::translate( 95 'The link from “%1$s” to “%2$s” has been updated.', 96 '<a class="alert-link" href="' . e($record->url()) . '">' . $record->fullName() . '</a>', 97 $record2_name 98 ), 'info'); 99 $gedcom = str_replace('@' . $xref2 . '@', '@' . $xref1 . '@', $record->gedcom()); 100 $gedcom = preg_replace( 101 '/(\n1.*@.+@.*(?:(?:\n[2-9].*)*))((?:\n1.*(?:\n[2-9].*)*)*\1)/', 102 '$2', 103 $gedcom 104 ); 105 $record->updateRecord($gedcom, true); 106 } 107 } 108 109 // Update any linked user-accounts 110 DB::table('user_gedcom_setting') 111 ->where('gedcom_id', '=', $tree->id()) 112 ->whereIn('setting_name', ['gedcomid', 'rootid']) 113 ->where('setting_value', '=', $xref2) 114 ->update(['setting_value' => $xref1]); 115 116 // Merge hit counters 117 $hits = DB::table('hit_counter') 118 ->where('gedcom_id', '=', $tree->id()) 119 ->whereIn('page_parameter', [$xref1, $xref2]) 120 ->groupBy(['page_name']) 121 ->pluck(new Expression('SUM(page_count)'), 'page_name'); 122 123 foreach ($hits as $page_name => $page_count) { 124 DB::table('hit_counter') 125 ->where('gedcom_id', '=', $tree->id()) 126 ->where('page_name', '=', $page_name) 127 ->update(['page_count' => $page_count]); 128 } 129 130 DB::table('hit_counter') 131 ->where('gedcom_id', '=', $tree->id()) 132 ->where('page_parameter', '=', $xref2) 133 ->delete(); 134 135 $gedcom = '0 @' . $record1->xref() . '@ ' . $record1::RECORD_TYPE; 136 137 foreach ($record1->facts() as $fact) { 138 if (in_array($fact->id(), $keep1, true)) { 139 $gedcom .= "\n" . $fact->gedcom(); 140 } 141 } 142 143 foreach ($record2->facts() as $fact) { 144 if (in_array($fact->id(), $keep2, true)) { 145 $gedcom .= "\n" . $fact->gedcom(); 146 } 147 } 148 149 DB::table('favorite') 150 ->where('gedcom_id', '=', $tree->id()) 151 ->where('xref', '=', $xref2) 152 ->update(['xref' => $xref1]); 153 154 $record1->updateRecord($gedcom, true); 155 $record2->deleteRecord(); 156 157 /* I18N: Records are individuals, sources, etc. */ 158 FlashMessages::addMessage(I18N::translate( 159 'The records “%1$s” and “%2$s” have been merged.', 160 '<a class="alert-link" href="' . e($record1->url()) . '">' . $record1->fullName() . '</a>', 161 $record2_name 162 ), 'success'); 163 164 return redirect(route(MergeRecordsPage::class, ['tree' => $tree->name()])); 165 } 166} 167