xref: /webtrees/app/Module/FixDuplicateLinks.php (revision 5f52b64188c4f443eebeb985bb53134753a7560f)
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\Module;
21
22use Fisharebest\Webtrees\GedcomRecord;
23use Fisharebest\Webtrees\I18N;
24use Fisharebest\Webtrees\Services\DataFixService;
25use Fisharebest\Webtrees\Tree;
26use Illuminate\Support\Collection;
27
28use function preg_match;
29use function preg_replace;
30
31/**
32 * Class FixDuplicateLinks
33 */
34class FixDuplicateLinks extends AbstractModule implements ModuleDataFixInterface
35{
36    use ModuleDataFixTrait;
37
38    private DataFixService $data_fix_service;
39
40    /**
41     * @param DataFixService $data_fix_service
42     */
43    public function __construct(DataFixService $data_fix_service)
44    {
45        $this->data_fix_service = $data_fix_service;
46    }
47
48    /**
49     * How should this module be identified in the control panel, etc.?
50     *
51     * @return string
52     */
53    public function title(): string
54    {
55        /* I18N: Name of a module */
56        return I18N::translate('Remove duplicate links');
57    }
58
59    public function description(): string
60    {
61        /* I18N: Description of a “Data fix” module */
62        return I18N::translate('A common error is to have multiple links to the same record, for example listing the same child more than once in a family record.');
63    }
64
65    /**
66     * A list of all records that need examining.  This may include records
67     * that do not need updating, if we can't detect this quickly using SQL.
68     *
69     * @param Tree          $tree
70     * @param array<string> $params
71     *
72     * @return Collection<int,string>
73     */
74    protected function familiesToFix(Tree $tree, array $params): Collection
75    {
76        // No DB querying possible?  Select all.
77        return $this->familiesToFixQuery($tree, $params)
78            ->pluck('f_id');
79    }
80
81    /**
82     * A list of all records that need examining.  This may include records
83     * that do not need updating, if we can't detect this quickly using SQL.
84     *
85     * @param Tree                 $tree
86     * @param array<string,string> $params
87     *
88     * @return Collection<int,string>|null
89     */
90    protected function individualsToFix(Tree $tree, array $params): Collection|null
91    {
92        // No DB querying possible?  Select all.
93        return $this->individualsToFixQuery($tree, $params)
94            ->pluck('i_id');
95    }
96
97    /**
98     * A list of all records that need examining.  This may include records
99     * that do not need updating, if we can't detect this quickly using SQL.
100     *
101     * @param Tree                 $tree
102     * @param array<string,string> $params
103     *
104     * @return Collection<int,string>
105     */
106    protected function mediaToFix(Tree $tree, array $params): Collection
107    {
108        // No DB querying possible?  Select all.
109        return $this->mediaToFixQuery($tree, $params)
110            ->pluck('m_id');
111    }
112
113    /**
114     * A list of all records that need examining.  This may include records
115     * that do not need updating, if we can't detect this quickly using SQL.
116     *
117     * @param Tree                 $tree
118     * @param array<string,string> $params
119     *
120     * @return Collection<int,string>
121     */
122    protected function notesToFix(Tree $tree, array $params): Collection
123    {
124        // No DB querying possible?  Select all.
125        return $this->notesToFixQuery($tree, $params)
126            ->pluck('o_id');
127    }
128
129    /**
130     * A list of all records that need examining.  This may include records
131     * that do not need updating, if we can't detect this quickly using SQL.
132     *
133     * @param Tree                 $tree
134     * @param array<string,string> $params
135     *
136     * @return Collection<int,string>
137     */
138    protected function repositoriesToFix(Tree $tree, array $params): Collection
139    {
140        // No DB querying possible?  Select all.
141        return $this->repositoriesToFixQuery($tree, $params)
142            ->pluck('o_id');
143    }
144
145    /**
146     * A list of all records that need examining.  This may include records
147     * that do not need updating, if we can't detect this quickly using SQL.
148     *
149     * @param Tree                 $tree
150     * @param array<string,string> $params
151     *
152     * @return Collection<int,string>
153     */
154    protected function sourcesToFix(Tree $tree, array $params): Collection
155    {
156        // No DB querying possible?  Select all.
157        return $this->sourcesToFixQuery($tree, $params)
158            ->pluck('s_id');
159    }
160
161    /**
162     * A list of all records that need examining.  This may include records
163     * that do not need updating, if we can't detect this quickly using SQL.
164     *
165     * @param Tree                 $tree
166     * @param array<string,string> $params
167     *
168     * @return Collection<int,string>
169     */
170    protected function submittersToFix(Tree $tree, array $params): Collection
171    {
172        // No DB querying possible?  Select all.
173        return $this->submittersToFixQuery($tree, $params)
174            ->pluck('o_id');
175    }
176
177    /**
178     * Does a record need updating?
179     *
180     * @param GedcomRecord         $record
181     * @param array<string,string> $params
182     *
183     * @return bool
184     */
185    public function doesRecordNeedUpdate(GedcomRecord $record, array $params): bool
186    {
187        $gedcom = $record->gedcom();
188
189        return
190            preg_match('/(\n1.*@.+@.*(?:\n[2-9].*)*)(?:\n1.*(?:\n[2-9].*)*)*\1/', $gedcom) ||
191            preg_match('/(\n2.*@.+@.*(?:\n[3-9].*)*)(?:\n2.*(?:\n[3-9].*)*)*\1/', $gedcom) ||
192            preg_match('/(\n3.*@.+@.*(?:\n[4-9].*)*)(?:\n3.*(?:\n[4-9].*)*)*\1/', $gedcom);
193    }
194
195    /**
196     * Show the changes we would make
197     *
198     * @param GedcomRecord         $record
199     * @param array<string,string> $params
200     *
201     * @return string
202     */
203    public function previewUpdate(GedcomRecord $record, array $params): string
204    {
205        $old = $record->gedcom();
206        $new = $this->updateGedcom($record);
207
208        return $this->data_fix_service->gedcomDiff($record->tree(), $old, $new);
209    }
210
211    /**
212     * Fix a record
213     *
214     * @param GedcomRecord         $record
215     * @param array<string,string> $params
216     *
217     * @return void
218     */
219    public function updateRecord(GedcomRecord $record, array $params): void
220    {
221        $record->updateRecord($this->updateGedcom($record), false);
222    }
223
224    /**
225     * @param GedcomRecord $record
226     *
227     * @return string
228     */
229    private function updateGedcom(GedcomRecord $record): string
230    {
231        $gedcom = $record->gedcom();
232        $gedcom = preg_replace('/(\n1.*@.+@.*(?:\n[2-9].*)*)((?:\n1.*(?:\n[2-9].*)*)*\1)/', '$2', $gedcom);
233        $gedcom = preg_replace('/(\n2.*@.+@.*(?:\n[3-9].*)*)((?:\n2.*(?:\n[3-9].*)*)*\1)/', '$2', $gedcom);
234
235        return preg_replace('/(\n3.*@.+@.*(?:\n[4-9].*)*)((?:\n3.*(?:\n[4-9].*)*)*\1)/', '$2', $gedcom);
236    }
237}
238