xref: /webtrees/app/Module/FixPlaceNames.php (revision cf7c85896a54223772c76826381a9151d4a21e10)
1<?php
2
3/**
4 * webtrees: online genealogy
5 * Copyright (C) 2020 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\Module;
21
22use Fisharebest\Webtrees\GedcomRecord;
23use Fisharebest\Webtrees\I18N;
24use Fisharebest\Webtrees\Services\DataFixService;
25use Fisharebest\Webtrees\Tree;
26use Illuminate\Database\Capsule\Manager as DB;
27use Illuminate\Support\Collection;
28
29use function addcslashes;
30use function preg_match;
31use function preg_quote;
32use function preg_replace;
33use function view;
34
35/**
36 * Class FixPlaceNames
37 */
38class FixPlaceNames extends AbstractModule implements ModuleDataFixInterface
39{
40    use ModuleDataFixTrait;
41
42    /** @var DataFixService */
43    private $data_fix_service;
44
45    /**
46     * FixMissingDeaths constructor.
47     *
48     * @param DataFixService $data_fix_service
49     */
50    public function __construct(DataFixService $data_fix_service)
51    {
52        $this->data_fix_service = $data_fix_service;
53    }
54
55    /**
56     * How should this module be identified in the control panel, etc.?
57     *
58     * @return string
59     */
60    public function title(): string
61    {
62        /* I18N: Name of a module */
63        return I18N::translate('Update place names');
64    }
65
66    /**
67     * A sentence describing what this module does.
68     *
69     * @return string
70     */
71    public function description(): string
72    {
73        /* I18N: Description of a “Data fix” module */
74        return I18N::translate('Update the higher-level parts of place names, while keeping the lower-level parts.');
75    }
76
77    /**
78     * Options form.
79     *
80     * @param Tree $tree
81     *
82     * @return string
83     */
84    public function fixOptions(Tree $tree): string
85    {
86        return view('modules/fix-place-names/options', []);
87    }
88
89    /**
90     * A list of all records that need examining.  This may include records
91     * that do not need updating, if we can't detect this quickly using SQL.
92     *
93     * @param Tree                 $tree
94     * @param array<string,string> $params
95     *
96     * @return Collection<string>|null
97     */
98    protected function familiesToFix(Tree $tree, array $params): ?Collection
99    {
100        if ($params['search'] === '' || $params['replace'] === '') {
101            return null;
102        }
103
104        $search = '%' . addcslashes($params['search'], '\\%_') . '%';
105
106        return  DB::table('families')
107            ->where('f_file', '=', $tree->id())
108            ->where('f_gedcom', 'LIKE', $search)
109            ->pluck('f_id');
110    }
111
112    /**
113     * A list of all records that need examining.  This may include records
114     * that do not need updating, if we can't detect this quickly using SQL.
115     *
116     * @param Tree                 $tree
117     * @param array<string,string> $params
118     *
119     * @return Collection<string>|null
120     */
121    protected function individualsToFix(Tree $tree, array $params): ?Collection
122    {
123        if ($params['search'] === '' || $params['replace'] === '') {
124            return null;
125        }
126
127        $search = '%' . addcslashes($params['search'], '\\%_') . '%';
128
129        return  DB::table('individuals')
130            ->where('i_file', '=', $tree->id())
131            ->where('i_gedcom', 'LIKE', $search)
132            ->pluck('i_id');
133    }
134
135    /**
136     * Does a record need updating?
137     *
138     * @param GedcomRecord         $record
139     * @param array<string,string> $params
140     *
141     * @return bool
142     */
143    public function doesRecordNeedUpdate(GedcomRecord $record, array $params): bool
144    {
145        $search = preg_quote($params['search'], '/');
146        $regex  = '/\n2 PLAC (?:.*, )?' . $search . '(\n|$)/';
147
148        return preg_match($regex, $record->gedcom()) === 1;
149    }
150
151    /**
152     * Show the changes we would make
153     *
154     * @param GedcomRecord         $record
155     * @param array<string,string> $params
156     *
157     * @return string
158     */
159    public function previewUpdate(GedcomRecord $record, array $params): string
160    {
161        $old = $record->gedcom();
162        $new = $this->updateGedcom($record, $params);
163
164        return $this->data_fix_service->gedcomDiff($record->tree(), $old, $new);
165    }
166
167    /**
168     * Fix a record
169     *
170     * @param GedcomRecord         $record
171     * @param array<string,string> $params
172     *
173     * @return void
174     */
175    public function updateRecord(GedcomRecord $record, array $params): void
176    {
177        $record->updateRecord($this->updateGedcom($record, $params), false);
178    }
179
180    /**
181     * @param GedcomRecord         $record
182     * @param array<string,string> $params
183     *
184     * @return string
185     */
186    private function updateGedcom(GedcomRecord $record, array $params): string
187    {
188        $search  = preg_quote($params['search'], '/');
189        $regex   = '/(\n2 PLAC (?:.*, )?)' . $search . '(\n|$)/';
190        $replace = '$1' . addcslashes($params['replace'], '$\\') . '$2';
191
192        return preg_replace($regex, $replace, $record->gedcom());
193    }
194}
195