xref: /webtrees/app/Module/ModuleDataFixTrait.php (revision 1270d2767576ed4a83917769b0ee3613e3b010bf)
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\DB;
23use Fisharebest\Webtrees\Family;
24use Fisharebest\Webtrees\GedcomRecord;
25use Fisharebest\Webtrees\Individual;
26use Fisharebest\Webtrees\Location;
27use Fisharebest\Webtrees\Media;
28use Fisharebest\Webtrees\Note;
29use Fisharebest\Webtrees\Repository;
30use Fisharebest\Webtrees\Source;
31use Fisharebest\Webtrees\Submitter;
32use Fisharebest\Webtrees\Tree;
33use Illuminate\Database\Query\Builder;
34use Illuminate\Support\Collection;
35
36/**
37 * Trait ModuleDataFixTrait - default implementation of ModuleDataFixTrait
38 */
39trait ModuleDataFixTrait
40{
41    /**
42     * Options form.
43     *
44     * @param Tree $tree
45     *
46     * @return string
47     */
48    public function fixOptions(Tree $tree): string
49    {
50        return '';
51    }
52
53    /**
54     * A list of all records that need examining.  This may include records
55     * that do not need updating, if we can't detect this quickly using SQL.
56     *
57     * @param Tree                 $tree
58     * @param array<string,string> $params
59     *
60     * @return Collection<int,object>
61     */
62    public function recordsToFix(Tree $tree, array $params): Collection
63    {
64        $families     = $this->familiesToFix($tree, $params);
65        $individuals  = $this->individualsToFix($tree, $params);
66        $locations    = $this->locationsToFix($tree, $params);
67        $media        = $this->mediaToFix($tree, $params);
68        $notes        = $this->notesToFix($tree, $params);
69        $repositories = $this->repositoriesToFix($tree, $params);
70        $sources      = $this->sourcesToFix($tree, $params);
71        $submitters   = $this->submittersToFix($tree, $params);
72
73        $records = new Collection();
74
75        if ($families !== null) {
76            $records = $records->concat($this->mergePendingRecords($families, $tree, Family::RECORD_TYPE));
77        }
78
79        if ($individuals !== null) {
80            $records = $records->concat($this->mergePendingRecords($individuals, $tree, Individual::RECORD_TYPE));
81        }
82
83        if ($locations !== null) {
84            $records = $records->concat($this->mergePendingRecords($locations, $tree, Location::RECORD_TYPE));
85        }
86
87        if ($media !== null) {
88            $records = $records->concat($this->mergePendingRecords($media, $tree, Media::RECORD_TYPE));
89        }
90
91        if ($notes !== null) {
92            $records = $records->concat($this->mergePendingRecords($notes, $tree, Note::RECORD_TYPE));
93        }
94
95        if ($repositories !== null) {
96            $records = $records->concat($this->mergePendingRecords($repositories, $tree, Repository::RECORD_TYPE));
97        }
98
99        if ($sources !== null) {
100            $records = $records->concat($this->mergePendingRecords($sources, $tree, Source::RECORD_TYPE));
101        }
102
103        if ($submitters !== null) {
104            $records = $records->concat($this->mergePendingRecords($submitters, $tree, Submitter::RECORD_TYPE));
105        }
106
107        return $records
108            ->unique()
109            ->sort(static fn (object $x, object $y) => $x->xref <=> $y->xref);
110    }
111
112    /**
113     * Does a record need updating?
114     *
115     * @param GedcomRecord         $record
116     * @param array<string,string> $params
117     *
118     * @return bool
119     */
120    public function doesRecordNeedUpdate(GedcomRecord $record, array $params): bool
121    {
122        return false;
123    }
124
125    /**
126     * Show the changes we would make
127     *
128     * @param GedcomRecord         $record
129     * @param array<string,string> $params
130     *
131     * @return string
132     */
133    public function previewUpdate(GedcomRecord $record, array $params): string
134    {
135        return $record->fullName();
136    }
137
138    /**
139     * Fix a record
140     *
141     * @param GedcomRecord         $record
142     * @param array<string,string> $params
143     *
144     * @return void
145     */
146    public function updateRecord(GedcomRecord $record, array $params): void
147    {
148    }
149
150    /**
151     * XREFs of family records that might need fixing.
152     *
153     * @param Tree                 $tree
154     * @param array<string,string> $params
155     *
156     * @return Collection<int,string>|null
157     */
158    protected function familiesToFix(Tree $tree, array $params): Collection|null
159    {
160        return null;
161    }
162
163    /**
164     * @param Tree                 $tree
165     * @param array<string,string> $params
166     *
167     * @return Builder
168     */
169    protected function familiesToFixQuery(Tree $tree, array $params): Builder
170    {
171        $query = DB::table('families')
172            ->where('f_file', '=', $tree->id());
173
174        if (isset($params['start'], $params['end'])) {
175            $query->whereBetween('f_id', [$params['start'], $params['end']]);
176        }
177
178        return $query;
179    }
180
181    /**
182     * XREFs of individual records that might need fixing.
183     *
184     * @param Tree                 $tree
185     * @param array<string,string> $params
186     *
187     * @return Collection<int,string>|null
188     */
189    protected function individualsToFix(Tree $tree, array $params): Collection|null
190    {
191        return null;
192    }
193
194    /**
195     * @param Tree                 $tree
196     * @param array<string,string> $params
197     *
198     * @return Builder
199     */
200    protected function individualsToFixQuery(Tree $tree, array $params): Builder
201    {
202        $query = DB::table('individuals')
203            ->where('i_file', '=', $tree->id());
204
205        if (isset($params['start'], $params['end'])) {
206            $query->whereBetween('i_id', [$params['start'], $params['end']]);
207        }
208
209        return $query;
210    }
211
212    /**
213     * XREFs of location records that might need fixing.
214     *
215     * @param Tree                 $tree
216     * @param array<string,string> $params
217     *
218     * @return Collection<int,string>|null
219     */
220    protected function locationsToFix(Tree $tree, array $params): Collection|null
221    {
222        return null;
223    }
224
225    /**
226     * @param Tree                 $tree
227     * @param array<string,string> $params
228     *
229     * @return Builder
230     */
231    protected function locationsToFixQuery(Tree $tree, array $params): Builder
232    {
233        $query = DB::table('other')
234            ->where('o_type', '=', Location::RECORD_TYPE)
235            ->where('o_file', '=', $tree->id());
236
237        if (isset($params['start'], $params['end'])) {
238            $query->whereBetween('o_id', [$params['start'], $params['end']]);
239        }
240
241        return $query;
242    }
243
244    /**
245     * XREFs of media records that might need fixing.
246     *
247     * @param Tree                 $tree
248     * @param array<string,string> $params
249     *
250     * @return Collection<int,string>|null
251     */
252    protected function mediaToFix(Tree $tree, array $params): Collection|null
253    {
254        return null;
255    }
256
257    /**
258     * @param Tree                 $tree
259     * @param array<string,string> $params
260     *
261     * @return Builder
262     */
263    protected function mediaToFixQuery(Tree $tree, array $params): Builder
264    {
265        $query = DB::table('media')
266            ->where('m_file', '=', $tree->id());
267
268        if (isset($params['start'], $params['end'])) {
269            $query->whereBetween('m_id', [$params['start'], $params['end']]);
270        }
271
272        return $query;
273    }
274
275    /**
276     * XREFs of note records that might need fixing.
277     *
278     * @param Tree                 $tree
279     * @param array<string,string> $params
280     *
281     * @return Collection<int,string>|null
282     */
283    protected function notesToFix(Tree $tree, array $params): Collection|null
284    {
285        return null;
286    }
287
288    /**
289     * @param Tree                 $tree
290     * @param array<string,string> $params
291     *
292     * @return Builder
293     */
294    protected function notesToFixQuery(Tree $tree, array $params): Builder
295    {
296        $query = DB::table('other')
297            ->where('o_type', '=', Note::RECORD_TYPE)
298            ->where('o_file', '=', $tree->id());
299
300        if (isset($params['start'], $params['end'])) {
301            $query->whereBetween('o_id', [$params['start'], $params['end']]);
302        }
303
304        return $query;
305    }
306
307    /**
308     * XREFs of repository records that might need fixing.
309     *
310     * @param Tree                 $tree
311     * @param array<string,string> $params
312     *
313     * @return Collection<int,string>|null
314     */
315    protected function repositoriesToFix(Tree $tree, array $params): Collection|null
316    {
317        return null;
318    }
319
320    /**
321     * @param Tree                 $tree
322     * @param array<string,string> $params
323     *
324     * @return Builder
325     */
326    protected function repositoriesToFixQuery(Tree $tree, array $params): Builder
327    {
328        $query = DB::table('other')
329            ->where('o_type', '=', Repository::RECORD_TYPE)
330            ->where('o_file', '=', $tree->id());
331
332        if (isset($params['start'], $params['end'])) {
333            $query->whereBetween('o_id', [$params['start'], $params['end']]);
334        }
335
336        return $query;
337    }
338
339    /**
340     * XREFs of source records that might need fixing.
341     *
342     * @param Tree                 $tree
343     * @param array<string,string> $params
344     *
345     * @return Collection<int,string>|null
346     */
347    protected function sourcesToFix(Tree $tree, array $params): Collection|null
348    {
349        return null;
350    }
351
352    /**
353     * @param Tree                 $tree
354     * @param array<string,string> $params
355     *
356     * @return Builder
357     */
358    protected function sourcesToFixQuery(Tree $tree, array $params): Builder
359    {
360        $query = DB::table('sources')
361            ->where('s_file', '=', $tree->id());
362
363        if (isset($params['start'], $params['end'])) {
364            $query->whereBetween('s_id', [$params['start'], $params['end']]);
365        }
366
367        return $query;
368    }
369
370    /**
371     * XREFs of submitter records that might need fixing.
372     *
373     * @param Tree                 $tree
374     * @param array<string,string> $params
375     *
376     * @return Collection<int,string>|null
377     */
378    protected function submittersToFix(Tree $tree, array $params): Collection|null
379    {
380        return null;
381    }
382
383    /**
384     * @param Tree                 $tree
385     * @param array<string,string> $params
386     *
387     * @return Builder
388     */
389    protected function submittersToFixQuery(Tree $tree, array $params): Builder
390    {
391        $query = DB::table('other')
392            ->where('o_type', '=', Submitter::RECORD_TYPE)
393            ->where('o_file', '=', $tree->id());
394
395        if (isset($params['start'], $params['end'])) {
396            $query->whereBetween('o_id', [$params['start'], $params['end']]);
397        }
398
399        return $query;
400    }
401
402    /**
403     * Merge pending changes of a given type.  We need to check all pending records.
404     *
405     * @param Collection<int,string> $records
406     * @param Tree                   $tree
407     * @param string                 $type
408     *
409     * @return Collection<int,object>
410     */
411    private function mergePendingRecords(Collection $records, Tree $tree, string $type): Collection
412    {
413        $pending = DB::table('change')
414            ->where('gedcom_id', '=', $tree->id())
415            ->where('status', '=', 'pending')
416            ->where(static function (Builder $query) use ($type): void {
417                $query
418                    ->where('old_gedcom', 'LIKE', '%@ ' . $type . '\n%')
419                    ->orWhere('new_gedcom', 'LIKE', '%@ ' . $type . '\n%');
420            })
421            ->pluck('xref');
422
423        return $records
424            ->concat($pending)
425            ->map(static fn (string $xref): object => (object) ['xref' => $xref, 'type' => $type]);
426    }
427}
428