Passed
Push — master ( 00a79d...f5402f )
by Greg
05:33
created

SearchReplaceAction::replacePlaces()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 10
c 1
b 0
f 0
nc 3
nop 3
dl 0
loc 17
rs 9.9332
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
18
declare(strict_types=1);
19
20
namespace Fisharebest\Webtrees\Http\RequestHandlers;
21
22
use Fisharebest\Webtrees\FlashMessages;
23
use Fisharebest\Webtrees\Http\ViewResponseTrait;
24
use Fisharebest\Webtrees\I18N;
25
use Fisharebest\Webtrees\Services\SearchService;
26
use Fisharebest\Webtrees\Services\TreeService;
27
use Fisharebest\Webtrees\Tree;
28
use Illuminate\Support\Collection;
29
use InvalidArgumentException;
30
use Psr\Http\Message\ResponseInterface;
31
use Psr\Http\Message\ServerRequestInterface;
32
use Psr\Http\Server\RequestHandlerInterface;
33
34
use function assert;
35
36
/**
37
 * Search and replace genealogy data
38
 */
39
class SearchReplaceAction implements RequestHandlerInterface
40
{
41
    use ViewResponseTrait;
42
43
    /** @var SearchService */
44
    private $search_service;
45
46
    /** @var TreeService */
47
    private $tree_service;
48
49
    /**
50
     * SearchController constructor.
51
     *
52
     * @param SearchService $search_service
53
     * @param TreeService   $tree_service
54
     */
55
    public function __construct(SearchService $search_service, TreeService $tree_service)
56
    {
57
        $this->search_service = $search_service;
58
        $this->tree_service   = $tree_service;
59
    }
60
61
    /**
62
     * Search and replace.
63
     *
64
     * @param ServerRequestInterface $request
65
     *
66
     * @return ResponseInterface
67
     */
68
    public function handle(ServerRequestInterface $request): ResponseInterface
69
    {
70
        $tree = $request->getAttribute('tree');
71
        assert($tree instanceof Tree, new InvalidArgumentException());
72
73
        $params  = $request->getParsedBody();
74
        $search  = $params['search'] ?? '';
75
        $replace = $params['replace'] ?? '';
76
        $context = $params['context'] ?? 'all';
77
78
        switch ($context) {
79
            case 'all':
80
                $records = $this->search_service->searchIndividuals([$tree], [$search]);
81
                $count   = $this->replaceRecords($records, $search, $replace);
82
                FlashMessages::addMessage(I18N::plural('%s individual has been updated.', '%s individuals have been updated.', $count, I18N::number($count)));
83
84
                $records = $this->search_service->searchFamilies([$tree], [$search]);
85
                $count   = $this->replaceRecords($records, $search, $replace);
86
                FlashMessages::addMessage(I18N::plural('%s family has been updated.', '%s families have been updated.', $count, I18N::number($count)));
87
88
                $records = $this->search_service->searchRepositories([$tree], [$search]);
89
                $count   = $this->replaceRecords($records, $search, $replace);
90
                FlashMessages::addMessage(I18N::plural('%s repository has been updated.', '%s repositories have been updated.', $count, I18N::number($count)));
91
92
                $records = $this->search_service->searchSources([$tree], [$search]);
93
                $count   = $this->replaceRecords($records, $search, $replace);
94
                FlashMessages::addMessage(I18N::plural('%s source has been updated.', '%s sources have been updated.', $count, I18N::number($count)));
95
96
                $records = $this->search_service->searchNotes([$tree], [$search]);
97
                $count   = $this->replaceRecords($records, $search, $replace);
98
                FlashMessages::addMessage(I18N::plural('%s note has been updated.', '%s notes have been updated.', $count, I18N::number($count)));
99
                break;
100
101
            case 'name':
102
                $adv_name_tags = preg_split("/[\s,;: ]+/", $tree->getPreference('ADVANCED_NAME_FACTS'));
103
                $name_tags     = array_unique(array_merge([
104
                    'NAME',
105
                    'NPFX',
106
                    'GIVN',
107
                    'SPFX',
108
                    'SURN',
109
                    'NSFX',
110
                    '_MARNM',
111
                    '_AKA',
112
                ], $adv_name_tags));
0 ignored issues
show
Bug introduced by
It seems like $adv_name_tags can also be of type false; however, parameter $array2 of array_merge() does only seem to accept array|null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

112
                ], /** @scrutinizer ignore-type */ $adv_name_tags));
Loading history...
113
114
                $records = $this->search_service->searchIndividuals([$tree], [$search]);
115
                $count   = $this->replaceIndividualNames($records, $search, $replace, $name_tags);
116
                FlashMessages::addMessage(I18N::plural('%s individual has been updated.', '%s individuals have been updated.', $count, I18N::number($count)));
117
                break;
118
119
            case 'place':
120
                $records = $this->search_service->searchIndividuals([$tree], [$search]);
121
                $count   = $this->replacePlaces($records, $search, $replace);
122
                FlashMessages::addMessage(I18N::plural('%s individual has been updated.', '%s individuals have been updated.', $count, I18N::number($count)));
123
124
                $records = $this->search_service->searchFamilies([$tree], [$search]);
125
                $count   = $this->replacePlaces($records, $search, $replace);
126
                FlashMessages::addMessage(I18N::plural('%s family has been updated.', '%s families have been updated.', $count, I18N::number($count)));
127
                break;
128
        }
129
130
        $url = route(SearchReplacePage::class, [
131
            'search'  => $search,
132
            'replace' => $replace,
133
            'context' => $context,
134
            'tree'    => $tree->name(),
135
        ]);
136
137
        return redirect($url);
138
    }
139
140
    /**
141
     * @param Collection $records
142
     * @param string     $search
143
     * @param string     $replace
144
     *
145
     * @return int
146
     */
147
    private function replaceRecords(Collection $records, string $search, string $replace): int
148
    {
149
        $count = 0;
150
        $query = preg_quote($search, '/');
151
152
        foreach ($records as $record) {
153
            $old_record = $record->gedcom();
154
            $new_record = preg_replace('/(\n\d [A-Z0-9_]+ )' . $query . '/i', '$1' . $replace, $old_record);
155
156
            if ($new_record !== $old_record) {
157
                $record->updateRecord($new_record, true);
158
                $count++;
159
            }
160
        }
161
162
        return $count;
163
    }
164
165
    /**
166
     * @param Collection $records
167
     * @param string     $search
168
     * @param string     $replace
169
     * @param string[]   $name_tags
170
     *
171
     * @return int
172
     */
173
    private function replaceIndividualNames(Collection $records, string $search, string $replace, array $name_tags): int
174
    {
175
        $pattern     = '/(\n\d (?:' . implode('|', $name_tags) . ') (?:.*))' . preg_quote($search, '/') . '/i';
176
        $replacement = '$1' . $replace;
177
        $count       = 0;
178
179
        foreach ($records as $record) {
180
            $old_gedcom = $record->gedcom();
181
            $new_gedcom = preg_replace($pattern, $replacement, $old_gedcom);
182
183
            if ($new_gedcom !== $old_gedcom) {
184
                $record->updateRecord($new_gedcom, true);
185
                $count++;
186
            }
187
        }
188
189
        return $count;
190
    }
191
192
    /**
193
     * @param Collection $records
194
     * @param string     $search
195
     * @param string     $replace
196
     *
197
     * @return int
198
     */
199
    private function replacePlaces(Collection $records, string $search, string $replace): int
200
    {
201
        $pattern     = '/(\n\d PLAC\b.* )' . preg_quote($search, '/') . '([,\n])/i';
202
        $replacement = '$1' . $replace . '$2';
203
        $count       = 0;
204
205
        foreach ($records as $record) {
206
            $old_gedcom = $record->gedcom();
207
            $new_gedcom = preg_replace($pattern, $replacement, $old_gedcom);
208
209
            if ($new_gedcom !== $old_gedcom) {
210
                $record->updateRecord($new_gedcom, true);
211
                $count++;
212
            }
213
        }
214
215
        return $count;
216
    }
217
}
218