1<?php 2 3/** 4 * webtrees: online genealogy 5 * Copyright (C) 2023 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 <https://www.gnu.org/licenses/>. 16 */ 17 18declare(strict_types=1); 19 20namespace Fisharebest\Webtrees\Http\RequestHandlers; 21 22use Fisharebest\Webtrees\DB; 23use Fisharebest\Webtrees\FlashMessages; 24use Fisharebest\Webtrees\Header; 25use Fisharebest\Webtrees\I18N; 26use Fisharebest\Webtrees\Services\AdminService; 27use Fisharebest\Webtrees\Services\TreeService; 28use Fisharebest\Webtrees\Tree; 29use Fisharebest\Webtrees\Validator; 30use Illuminate\Database\Query\Builder; 31use Illuminate\Database\Query\Expression; 32use Psr\Http\Message\ResponseInterface; 33use Psr\Http\Message\ServerRequestInterface; 34use Psr\Http\Server\RequestHandlerInterface; 35 36use function redirect; 37use function route; 38 39/** 40 * Merge two family trees. 41 */ 42class MergeTreesAction implements RequestHandlerInterface 43{ 44 private AdminService $admin_service; 45 46 private TreeService $tree_service; 47 48 /** 49 * @param AdminService $admin_service 50 * @param TreeService $tree_service 51 */ 52 public function __construct(AdminService $admin_service, TreeService $tree_service) 53 { 54 $this->admin_service = $admin_service; 55 $this->tree_service = $tree_service; 56 } 57 58 /** 59 * @param ServerRequestInterface $request 60 * 61 * @return ResponseInterface 62 */ 63 public function handle(ServerRequestInterface $request): ResponseInterface 64 { 65 $tree1_name = Validator::parsedBody($request)->string('tree1_name'); 66 $tree2_name = Validator::parsedBody($request)->string('tree2_name'); 67 68 $tree1 = $this->tree_service->all()->get($tree1_name); 69 $tree2 = $this->tree_service->all()->get($tree2_name); 70 71 if ($tree1 instanceof Tree && $tree2 instanceof Tree && $tree1 !== $tree2 && $this->admin_service->countCommonXrefs($tree1, $tree2) === 0) { 72 (new Builder(DB::connection()))->from('individuals')->insertUsing([ 73 'i_file', 74 'i_id', 75 'i_rin', 76 'i_sex', 77 'i_gedcom', 78 ], static function (Builder $query) use ($tree1, $tree2): void { 79 $query->select([ 80 new Expression($tree2->id()), 81 'i_id', 82 'i_rin', 83 'i_sex', 84 'i_gedcom', 85 ])->from('individuals') 86 ->where('i_file', '=', $tree1->id()); 87 }); 88 89 (new Builder(DB::connection()))->from('families')->insertUsing([ 90 'f_file', 91 'f_id', 92 'f_husb', 93 'f_wife', 94 'f_gedcom', 95 'f_numchil', 96 ], static function (Builder $query) use ($tree1, $tree2): void { 97 $query->select([ 98 new Expression($tree2->id()), 99 'f_id', 100 'f_husb', 101 'f_wife', 102 'f_gedcom', 103 'f_numchil', 104 ])->from('families') 105 ->where('f_file', '=', $tree1->id()); 106 }); 107 108 (new Builder(DB::connection()))->from('sources')->insertUsing([ 109 's_file', 110 's_id', 111 's_name', 112 's_gedcom', 113 ], static function (Builder $query) use ($tree1, $tree2): void { 114 $query->select([ 115 new Expression($tree2->id()), 116 's_id', 117 's_name', 118 's_gedcom', 119 ])->from('sources') 120 ->where('s_file', '=', $tree1->id()); 121 }); 122 123 (new Builder(DB::connection()))->from('media')->insertUsing([ 124 'm_file', 125 'm_id', 126 'm_gedcom', 127 ], static function (Builder $query) use ($tree1, $tree2): void { 128 $query->select([ 129 new Expression($tree2->id()), 130 'm_id', 131 'm_gedcom', 132 ])->from('media') 133 ->where('m_file', '=', $tree1->id()); 134 }); 135 136 (new Builder(DB::connection()))->from('media_file')->insertUsing([ 137 'm_file', 138 'm_id', 139 'multimedia_file_refn', 140 'multimedia_format', 141 'source_media_type', 142 'descriptive_title', 143 ], static function (Builder $query) use ($tree1, $tree2): void { 144 $query->select([ 145 new Expression($tree2->id()), 146 'm_id', 147 'multimedia_file_refn', 148 'multimedia_format', 149 'source_media_type', 150 'descriptive_title', 151 ])->from('media_file') 152 ->where('m_file', '=', $tree1->id()); 153 }); 154 155 (new Builder(DB::connection()))->from('other')->insertUsing([ 156 'o_file', 157 'o_id', 158 'o_type', 159 'o_gedcom', 160 ], static function (Builder $query) use ($tree1, $tree2): void { 161 $query->select([ 162 new Expression($tree2->id()), 163 'o_id', 164 'o_type', 165 'o_gedcom', 166 ])->from('other') 167 ->whereNotIn('o_type', [Header::RECORD_TYPE, 'TRLR']) 168 ->where('o_file', '=', $tree1->id()); 169 }); 170 171 (new Builder(DB::connection()))->from('name')->insertUsing([ 172 'n_file', 173 'n_id', 174 'n_num', 175 'n_type', 176 'n_sort', 177 'n_full', 178 'n_surname', 179 'n_surn', 180 'n_givn', 181 'n_soundex_givn_std', 182 'n_soundex_surn_std', 183 'n_soundex_givn_dm', 184 'n_soundex_surn_dm', 185 ], static function (Builder $query) use ($tree1, $tree2): void { 186 $query->select([ 187 new Expression($tree2->id()), 188 'n_id', 189 'n_num', 190 'n_type', 191 'n_sort', 192 'n_full', 193 'n_surname', 194 'n_surn', 195 'n_givn', 196 'n_soundex_givn_std', 197 'n_soundex_surn_std', 198 'n_soundex_givn_dm', 199 'n_soundex_surn_dm', 200 ])->from('name') 201 ->where('n_file', '=', $tree1->id()); 202 }); 203 204 (new Builder(DB::connection()))->from('dates')->insertUsing([ 205 'd_file', 206 'd_gid', 207 'd_day', 208 'd_month', 209 'd_mon', 210 'd_year', 211 'd_julianday1', 212 'd_julianday2', 213 'd_fact', 214 'd_type', 215 ], static function (Builder $query) use ($tree1, $tree2): void { 216 $query->select([ 217 new Expression($tree2->id()), 218 'd_gid', 219 'd_day', 220 'd_month', 221 'd_mon', 222 'd_year', 223 'd_julianday1', 224 'd_julianday2', 225 'd_fact', 226 'd_type', 227 ])->from('dates') 228 ->where('d_file', '=', $tree1->id()); 229 }); 230 231 (new Builder(DB::connection()))->from('link')->insertUsing([ 232 'l_file', 233 'l_from', 234 'l_type', 235 'l_to', 236 ], static function (Builder $query) use ($tree1, $tree2): void { 237 $query->select([ 238 new Expression($tree2->id()), 239 'l_from', 240 'l_type', 241 'l_to', 242 ])->from('link') 243 ->whereNotIn('l_from', [Header::RECORD_TYPE, 'TRLR']) 244 ->where('l_file', '=', $tree1->id()); 245 }); 246 247 FlashMessages::addMessage(I18N::translate('The family trees have been merged successfully.'), 'success'); 248 249 $url = route(ManageTrees::class, ['tree' => $tree2->name()]); 250 } else { 251 $url = route(MergeTreesPage::class, [ 252 'tree1_name' => $tree1->name(), 253 'tree2_name' => $tree2->name(), 254 ]); 255 } 256 257 return redirect($url); 258 } 259} 260