Passed
Push — master ( 94abca...7bb122 )
by Greg
05:28
created

FixMissingMarriedNames::updateGedcom()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 35
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 25
nc 5
nop 2
dl 0
loc 35
rs 9.2088
c 0
b 0
f 0
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\Module;
21
22
use Fisharebest\Webtrees\Family;
23
use Fisharebest\Webtrees\GedcomRecord;
24
use Fisharebest\Webtrees\I18N;
25
use Fisharebest\Webtrees\Individual;
26
use Fisharebest\Webtrees\Services\DataFixService;
27
use Fisharebest\Webtrees\Tree;
28
use Illuminate\Database\Capsule\Manager as DB;
29
use Illuminate\Support\Collection;
30
31
use function array_merge;
32
use function array_unique;
33
use function assert;
34
use function implode;
35
use function in_array;
36
use function preg_match;
37
use function preg_match_all;
38
use function preg_replace;
39
use function str_replace;
40
use function view;
41
42
/**
43
 * Class FixMissingMarriedNames
44
 */
45
class FixMissingMarriedNames extends AbstractModule implements ModuleDataFixInterface
46
{
47
    use ModuleDataFixTrait;
0 ignored issues
show
Bug introduced by
The trait Fisharebest\Webtrees\Module\ModuleDataFixTrait requires the property $xref which is not provided by Fisharebest\Webtrees\Module\FixMissingMarriedNames.
Loading history...
48
49
    /** @var DataFixService */
50
    private $data_fix_service;
51
52
    /**
53
     * FixMissingDeaths constructor.
54
     *
55
     * @param DataFixService $data_fix_service
56
     */
57
    public function __construct(DataFixService $data_fix_service)
58
    {
59
        $this->data_fix_service = $data_fix_service;
60
    }
61
62
    /**
63
     * How should this module be identified in the control panel, etc.?
64
     *
65
     * @return string
66
     */
67
    public function title(): string
68
    {
69
        /* I18N: Name of a module */
70
        return I18N::translate('Add married names');
71
    }
72
73
    /**
74
     * A sentence describing what this module does.
75
     *
76
     * @return string
77
     */
78
    public function description(): string
79
    {
80
        /* I18N: Description of a “Data fix” module */
81
        return I18N::translate('You can make it easier to search for married women by recording their married name. However not all women take their husband’s surname, so beware of introducing incorrect information into your database.');
82
    }
83
84
    /**
85
     * Options form.
86
     *
87
     * @param Tree $tree
88
     *
89
     * @return string
90
     */
91
    public function fixOptions(Tree $tree): string
92
    {
93
        $options = [
94
            'replace' => I18N::translate('Wife’s surname replaced by husband’s surname'),
95
            'add'     => I18N::translate('Wife’s maiden surname becomes new given name'),
96
        ];
97
98
        $selected = 'replace';
99
100
        return view('modules/fix-add-marr-names/options', [
101
            'options'  => $options,
102
            'selected' => $selected,
103
        ]);
104
    }
105
106
    /**
107
     * A list of all records that need examining.  This may include records
108
     * that do not need updating, if we can't detect this quickly using SQL.
109
     *
110
     * @param Tree                 $tree
111
     * @param array<string,string> $params
112
     *
113
     * @return Collection<string>|null
114
     */
115
    protected function individualsToFix(Tree $tree, array $params): ?Collection
116
    {
117
        // No DB querying possible?  Select all.
118
        return DB::table('individuals')
119
            ->where('i_file', '=', $tree->id())
120
            ->where('i_sex', '=', 'F')
121
            ->pluck('i_id');
122
    }
123
124
    /**
125
     * Does a record need updating?
126
     *
127
     * @param GedcomRecord         $record
128
     * @param array<string,string> $params
129
     *
130
     * @return bool
131
     */
132
    public function doesRecordNeedUpdate(GedcomRecord $record, array $params): bool
133
    {
134
        assert($record instanceof Individual);
135
        $gedcom = $record->gedcom();
136
137
        return preg_match('/^1 SEX F/m', $gedcom) && preg_match('/^1 NAME /m', $gedcom) && $this->surnamesToAdd($record);
138
    }
139
140
    /**
141
     * Show the changes we would make
142
     *
143
     * @param GedcomRecord         $record
144
     * @param array<string,string> $params
145
     *
146
     * @return string
147
     */
148
    public function previewUpdate(GedcomRecord $record, array $params): string
149
    {
150
        $old = $record->gedcom();
151
        $new = $this->updateGedcom($record, $params);
152
153
        return $this->data_fix_service->gedcomDiff($record->tree(), $old, $new);
154
    }
155
156
    /**
157
     * Fix a record
158
     *
159
     * @param GedcomRecord         $record
160
     * @param array<string,string> $params
161
     *
162
     * @return void
163
     */
164
    public function updateRecord(GedcomRecord $record, array $params): void
165
    {
166
        $record->updateRecord($this->updateGedcom($record, $params), false);
167
    }
168
169
    /**
170
     * @param GedcomRecord         $record
171
     * @param array<string,string> $params
172
     *
173
     * @return string
174
     */
175
    private function updateGedcom(GedcomRecord $record, array $params): string
176
    {
177
        assert($record instanceof Individual);
178
179
        $old_gedcom = $record->gedcom();
180
        $tree       = $record->tree();
181
182
        $SURNAME_TRADITION = $tree->getPreference('SURNAME_TRADITION');
183
184
        preg_match('/^1 NAME (.*)/m', $old_gedcom, $match);
185
        $wife_name     = $match[1];
186
        $married_names = [];
187
        foreach ($this->surnamesToAdd($record) as $surname) {
188
            switch ($params['surname']) {
189
                case 'add':
190
                    $married_names[] = "\n2 _MARNM " . str_replace('/', '', $wife_name) . ' /' . $surname . '/';
191
                    break;
192
                case 'replace':
193
                    if ($SURNAME_TRADITION === 'polish') {
194
                        $surname = preg_replace([
195
                            '/ski$/',
196
                            '/cki$/',
197
                            '/dzki$/',
198
                        ], [
199
                            'ska',
200
                            'cka',
201
                            'dzka',
202
                        ], $surname);
203
                    }
204
                    $married_names[] = "\n2 _MARNM " . preg_replace('!/.*/!', '/' . $surname . '/', $wife_name);
205
                    break;
206
            }
207
        }
208
209
        return preg_replace('/(^1 NAME .*([\r\n]+[2-9].*)*)/m', '\\1' . implode('', $married_names), $old_gedcom, 1);
210
    }
211
212
213
    /**
214
     * Generate a list of married surnames that are not already present.
215
     *
216
     * @param Individual $record
217
     *
218
     * @return string[]
219
     */
220
    private function surnamesToAdd(Individual $record): array
221
    {
222
        $tree   = $record->tree();
223
224
        $wife_surnames    = $this->surnames($record);
225
        $husb_surnames    = [];
226
        $missing_surnames = [];
227
228
        foreach ($record->spouseFamilies() as $family) {
229
            $famrec = $family->gedcom();
230
231
            if (preg_match('/^1 MARR/m', $famrec) && preg_match('/^1 HUSB @(.+)@/m', $famrec, $hmatch)) {
232
                $spouse = Individual::getInstance($hmatch[1], $tree);
233
234
                if ($spouse instanceof Individual) {
235
                    $husb_surnames = array_unique(array_merge($husb_surnames, $this->surnames($spouse)));
236
                }
237
            }
238
        }
239
240
        foreach ($husb_surnames as $husb_surname) {
241
            if (!in_array($husb_surname, $wife_surnames, true)) {
242
                $missing_surnames[] = $husb_surname;
243
            }
244
        }
245
246
        return $missing_surnames;
247
    }
248
249
    /**
250
     * Extract a list of surnames from a GEDCOM record.
251
     *
252
     * @param GedcomRecord $record
253
     *
254
     * @return string[]
255
     */
256
    private function surnames(GedcomRecord $record): array
257
    {
258
        $gedcom = $record->gedcom();
259
260
        if (preg_match_all('/^(?:1 NAME|2 _MARNM) .*\/(.+)\//m', $gedcom, $match)) {
261
            return $match[1];
262
        }
263
264
        return [];
265
    }
266
}
267