1f5402f3dSGreg Roach<?php 2f5402f3dSGreg Roach 3f5402f3dSGreg Roach/** 4f5402f3dSGreg Roach * webtrees: online genealogy 5*d11be702SGreg Roach * Copyright (C) 2023 webtrees development team 6f5402f3dSGreg Roach * This program is free software: you can redistribute it and/or modify 7f5402f3dSGreg Roach * it under the terms of the GNU General Public License as published by 8f5402f3dSGreg Roach * the Free Software Foundation, either version 3 of the License, or 9f5402f3dSGreg Roach * (at your option) any later version. 10f5402f3dSGreg Roach * This program is distributed in the hope that it will be useful, 11f5402f3dSGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of 12f5402f3dSGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13f5402f3dSGreg Roach * GNU General Public License for more details. 14f5402f3dSGreg 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/>. 16f5402f3dSGreg Roach */ 17f5402f3dSGreg Roach 18f5402f3dSGreg Roachdeclare(strict_types=1); 19f5402f3dSGreg Roach 20f5402f3dSGreg Roachnamespace Fisharebest\Webtrees\Http\RequestHandlers; 21f5402f3dSGreg Roach 22f5402f3dSGreg Roachuse Fisharebest\Webtrees\FlashMessages; 23ac701fbdSGreg Roachuse Fisharebest\Webtrees\GedcomRecord; 24f5402f3dSGreg Roachuse Fisharebest\Webtrees\Http\ViewResponseTrait; 25f5402f3dSGreg Roachuse Fisharebest\Webtrees\I18N; 26de7821c6SGreg Roachuse Fisharebest\Webtrees\Registry; 27f5402f3dSGreg Roachuse Fisharebest\Webtrees\Services\SearchService; 28b55cbc6bSGreg Roachuse Fisharebest\Webtrees\Validator; 29f5402f3dSGreg Roachuse Illuminate\Support\Collection; 30f5402f3dSGreg Roachuse Psr\Http\Message\ResponseInterface; 31f5402f3dSGreg Roachuse Psr\Http\Message\ServerRequestInterface; 32f5402f3dSGreg Roachuse Psr\Http\Server\RequestHandlerInterface; 33f5402f3dSGreg Roach 34f5402f3dSGreg Roach/** 35f5402f3dSGreg Roach * Search and replace genealogy data 36f5402f3dSGreg Roach */ 37f5402f3dSGreg Roachclass SearchReplaceAction implements RequestHandlerInterface 38f5402f3dSGreg Roach{ 39f5402f3dSGreg Roach use ViewResponseTrait; 40f5402f3dSGreg Roach 41c4943cffSGreg Roach private SearchService $search_service; 42f5402f3dSGreg Roach 43f5402f3dSGreg Roach /** 44f5402f3dSGreg Roach * @param SearchService $search_service 45f5402f3dSGreg Roach */ 46c303ebb3SGreg Roach public function __construct(SearchService $search_service) 47f5402f3dSGreg Roach { 48f5402f3dSGreg Roach $this->search_service = $search_service; 49f5402f3dSGreg Roach } 50f5402f3dSGreg Roach 51f5402f3dSGreg Roach /** 52f5402f3dSGreg Roach * Search and replace. 53f5402f3dSGreg Roach * 54f5402f3dSGreg Roach * @param ServerRequestInterface $request 55f5402f3dSGreg Roach * 56f5402f3dSGreg Roach * @return ResponseInterface 57f5402f3dSGreg Roach */ 58f5402f3dSGreg Roach public function handle(ServerRequestInterface $request): ResponseInterface 59f5402f3dSGreg Roach { 60b55cbc6bSGreg Roach $tree = Validator::attributes($request)->tree(); 61748dbe15SGreg Roach $search = Validator::parsedBody($request)->string('search'); 62748dbe15SGreg Roach $replace = Validator::parsedBody($request)->string('replace'); 63748dbe15SGreg Roach $context = Validator::parsedBody($request)->string('context'); 64f5402f3dSGreg Roach 65f5402f3dSGreg Roach switch ($context) { 66f5402f3dSGreg Roach case 'all': 67f5402f3dSGreg Roach $records = $this->search_service->searchIndividuals([$tree], [$search]); 68f5402f3dSGreg Roach $count = $this->replaceRecords($records, $search, $replace); 69f5402f3dSGreg Roach FlashMessages::addMessage(I18N::plural('%s individual has been updated.', '%s individuals have been updated.', $count, I18N::number($count))); 70f5402f3dSGreg Roach 71f5402f3dSGreg Roach $records = $this->search_service->searchFamilies([$tree], [$search]); 72f5402f3dSGreg Roach $count = $this->replaceRecords($records, $search, $replace); 73f5402f3dSGreg Roach FlashMessages::addMessage(I18N::plural('%s family has been updated.', '%s families have been updated.', $count, I18N::number($count))); 74f5402f3dSGreg Roach 75f5402f3dSGreg Roach $records = $this->search_service->searchRepositories([$tree], [$search]); 76f5402f3dSGreg Roach $count = $this->replaceRecords($records, $search, $replace); 77f5402f3dSGreg Roach FlashMessages::addMessage(I18N::plural('%s repository has been updated.', '%s repositories have been updated.', $count, I18N::number($count))); 78f5402f3dSGreg Roach 79f5402f3dSGreg Roach $records = $this->search_service->searchSources([$tree], [$search]); 80f5402f3dSGreg Roach $count = $this->replaceRecords($records, $search, $replace); 81f5402f3dSGreg Roach FlashMessages::addMessage(I18N::plural('%s source has been updated.', '%s sources have been updated.', $count, I18N::number($count))); 82f5402f3dSGreg Roach 83f5402f3dSGreg Roach $records = $this->search_service->searchNotes([$tree], [$search]); 84f5402f3dSGreg Roach $count = $this->replaceRecords($records, $search, $replace); 85f5402f3dSGreg Roach FlashMessages::addMessage(I18N::plural('%s note has been updated.', '%s notes have been updated.', $count, I18N::number($count))); 86f5402f3dSGreg Roach break; 87f5402f3dSGreg Roach 88f5402f3dSGreg Roach case 'name': 89de7821c6SGreg Roach $name_tags = Registry::elementFactory()->make('INDI:NAME')->subtags(); 9005babb96SGreg Roach $name_tags = array_map(static fn (string $tag): string => '2 ' . $tag, $name_tags); 91de7821c6SGreg Roach $name_tags[] = '1 NAME'; 92f5402f3dSGreg Roach 93f5402f3dSGreg Roach $records = $this->search_service->searchIndividuals([$tree], [$search]); 94f5402f3dSGreg Roach $count = $this->replaceIndividualNames($records, $search, $replace, $name_tags); 95f5402f3dSGreg Roach FlashMessages::addMessage(I18N::plural('%s individual has been updated.', '%s individuals have been updated.', $count, I18N::number($count))); 96f5402f3dSGreg Roach break; 97f5402f3dSGreg Roach 98f5402f3dSGreg Roach case 'place': 99f5402f3dSGreg Roach $records = $this->search_service->searchIndividuals([$tree], [$search]); 100f5402f3dSGreg Roach $count = $this->replacePlaces($records, $search, $replace); 101f5402f3dSGreg Roach FlashMessages::addMessage(I18N::plural('%s individual has been updated.', '%s individuals have been updated.', $count, I18N::number($count))); 102f5402f3dSGreg Roach 103f5402f3dSGreg Roach $records = $this->search_service->searchFamilies([$tree], [$search]); 104f5402f3dSGreg Roach $count = $this->replacePlaces($records, $search, $replace); 105f5402f3dSGreg Roach FlashMessages::addMessage(I18N::plural('%s family has been updated.', '%s families have been updated.', $count, I18N::number($count))); 106f5402f3dSGreg Roach break; 107f5402f3dSGreg Roach } 108f5402f3dSGreg Roach 109f5402f3dSGreg Roach $url = route(SearchReplacePage::class, [ 110f5402f3dSGreg Roach 'search' => $search, 111f5402f3dSGreg Roach 'replace' => $replace, 112f5402f3dSGreg Roach 'context' => $context, 113f5402f3dSGreg Roach 'tree' => $tree->name(), 114f5402f3dSGreg Roach ]); 115f5402f3dSGreg Roach 116f5402f3dSGreg Roach return redirect($url); 117f5402f3dSGreg Roach } 118f5402f3dSGreg Roach 119f5402f3dSGreg Roach /** 12036779af1SGreg Roach * @param Collection<int,GedcomRecord> $records 121f5402f3dSGreg Roach * @param string $search 122f5402f3dSGreg Roach * @param string $replace 123f5402f3dSGreg Roach * 124f5402f3dSGreg Roach * @return int 125f5402f3dSGreg Roach */ 126f5402f3dSGreg Roach private function replaceRecords(Collection $records, string $search, string $replace): int 127f5402f3dSGreg Roach { 128f5402f3dSGreg Roach $count = 0; 129f5402f3dSGreg Roach $query = preg_quote($search, '/'); 130f5402f3dSGreg Roach 131f5402f3dSGreg Roach foreach ($records as $record) { 132f5402f3dSGreg Roach $old_record = $record->gedcom(); 133f5402f3dSGreg Roach $new_record = preg_replace('/(\n\d [A-Z0-9_]+ )' . $query . '/i', '$1' . $replace, $old_record); 134f5402f3dSGreg Roach 135f5402f3dSGreg Roach if ($new_record !== $old_record) { 136f5402f3dSGreg Roach $record->updateRecord($new_record, true); 137f5402f3dSGreg Roach $count++; 138f5402f3dSGreg Roach } 139f5402f3dSGreg Roach } 140f5402f3dSGreg Roach 141f5402f3dSGreg Roach return $count; 142f5402f3dSGreg Roach } 143f5402f3dSGreg Roach 144f5402f3dSGreg Roach /** 14536779af1SGreg Roach * @param Collection<int,GedcomRecord> $records 146f5402f3dSGreg Roach * @param string $search 147f5402f3dSGreg Roach * @param string $replace 14809482a55SGreg Roach * @param array<string> $name_tags 149f5402f3dSGreg Roach * 150f5402f3dSGreg Roach * @return int 151f5402f3dSGreg Roach */ 152f5402f3dSGreg Roach private function replaceIndividualNames(Collection $records, string $search, string $replace, array $name_tags): int 153f5402f3dSGreg Roach { 154dce4f3a4SGreg Roach $pattern = '/(\n(?:' . implode('|', $name_tags) . ') .*)' . preg_quote($search, '/') . '/i'; 155f5402f3dSGreg Roach $replacement = '$1' . $replace; 156f5402f3dSGreg Roach $count = 0; 157f5402f3dSGreg Roach 158f5402f3dSGreg Roach foreach ($records as $record) { 159f5402f3dSGreg Roach $old_gedcom = $record->gedcom(); 160f5402f3dSGreg Roach $new_gedcom = preg_replace($pattern, $replacement, $old_gedcom); 161f5402f3dSGreg Roach 162f5402f3dSGreg Roach if ($new_gedcom !== $old_gedcom) { 163f5402f3dSGreg Roach $record->updateRecord($new_gedcom, true); 164f5402f3dSGreg Roach $count++; 165f5402f3dSGreg Roach } 166f5402f3dSGreg Roach } 167f5402f3dSGreg Roach 168f5402f3dSGreg Roach return $count; 169f5402f3dSGreg Roach } 170f5402f3dSGreg Roach 171f5402f3dSGreg Roach /** 17236779af1SGreg Roach * @param Collection<int,GedcomRecord> $records 173f5402f3dSGreg Roach * @param string $search 174f5402f3dSGreg Roach * @param string $replace 175f5402f3dSGreg Roach * 176f5402f3dSGreg Roach * @return int 177f5402f3dSGreg Roach */ 178f5402f3dSGreg Roach private function replacePlaces(Collection $records, string $search, string $replace): int 179f5402f3dSGreg Roach { 180f5402f3dSGreg Roach $pattern = '/(\n\d PLAC\b.* )' . preg_quote($search, '/') . '([,\n])/i'; 181f5402f3dSGreg Roach $replacement = '$1' . $replace . '$2'; 182f5402f3dSGreg Roach $count = 0; 183f5402f3dSGreg Roach 184f5402f3dSGreg Roach foreach ($records as $record) { 185f5402f3dSGreg Roach $old_gedcom = $record->gedcom(); 186f5402f3dSGreg Roach $new_gedcom = preg_replace($pattern, $replacement, $old_gedcom); 187f5402f3dSGreg Roach 188f5402f3dSGreg Roach if ($new_gedcom !== $old_gedcom) { 189f5402f3dSGreg Roach $record->updateRecord($new_gedcom, true); 190f5402f3dSGreg Roach $count++; 191f5402f3dSGreg Roach } 192f5402f3dSGreg Roach } 193f5402f3dSGreg Roach 194f5402f3dSGreg Roach return $count; 195f5402f3dSGreg Roach } 196f5402f3dSGreg Roach} 197