xref: /webtrees/app/Module/ModuleDataFixTrait.php (revision e47dc774cba11c817773e1490719632c810d988e)
1<?php
2
3/**
4 * webtrees: online genealogy
5 * Copyright (C) 2022 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\Family;
23use Fisharebest\Webtrees\GedcomRecord;
24use Fisharebest\Webtrees\Individual;
25use Fisharebest\Webtrees\Location;
26use Fisharebest\Webtrees\Media;
27use Fisharebest\Webtrees\Note;
28use Fisharebest\Webtrees\Repository;
29use Fisharebest\Webtrees\Source;
30use Fisharebest\Webtrees\Submitter;
31use Fisharebest\Webtrees\Tree;
32use Illuminate\Database\Capsule\Manager as DB;
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(/** @scrutinizer ignore-unused */ 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 function (object $x, object $y) {
110                return $x->xref <=> $y->xref;
111            });
112    }
113
114    /**
115     * Does a record need updating?
116     *
117     * @param GedcomRecord         $record
118     * @param array<string,string> $params
119     *
120     * @return bool
121     */
122    public function doesRecordNeedUpdate(/** @scrutinizer ignore-unused */ GedcomRecord $record, /** @scrutinizer ignore-unused */ array $params): bool
123    {
124        return false;
125    }
126
127    /**
128     * Show the changes we would make
129     *
130     * @param GedcomRecord         $record
131     * @param array<string,string> $params
132     *
133     * @return string
134     */
135    public function previewUpdate(GedcomRecord $record, /** @scrutinizer ignore-unused */ array $params): string
136    {
137        return $record->fullName();
138    }
139
140    /**
141     * Fix a record
142     *
143     * @param GedcomRecord         $record
144     * @param array<string,string> $params
145     *
146     * @return void
147     */
148    public function updateRecord(/** @scrutinizer ignore-unused */ GedcomRecord $record, /** @scrutinizer ignore-unused */ array $params): void
149    {
150    }
151
152    /**
153     * XREFs of family records that might need fixing.
154     *
155     * @param Tree                 $tree
156     * @param array<string,string> $params
157     *
158     * @return Collection<int,string>|null
159     */
160    protected function familiesToFix(/** @scrutinizer ignore-unused */ Tree $tree, /** @scrutinizer ignore-unused */ array $params): ?Collection
161    {
162        return null;
163    }
164
165    /**
166     * @param Tree                 $tree
167     * @param array<string,string> $params
168     *
169     * @return Builder
170     */
171    protected function familiesToFixQuery(Tree $tree, array $params): Builder
172    {
173        $query = DB::table('families')
174            ->where('f_file', '=', $tree->id());
175
176        if (isset($params['start'], $params['end'])) {
177            $query->whereBetween('f_id', [$params['start'], $params['end']]);
178        }
179
180        return $query;
181    }
182
183    /**
184     * XREFs of individual records that might need fixing.
185     *
186     * @param Tree                 $tree
187     * @param array<string,string> $params
188     *
189     * @return Collection<int,string>|null
190     */
191    protected function individualsToFix(/** @scrutinizer ignore-unused */ Tree $tree, /** @scrutinizer ignore-unused */ array $params): ?Collection
192    {
193        return null;
194    }
195
196    /**
197     * @param Tree                 $tree
198     * @param array<string,string> $params
199     *
200     * @return Builder
201     */
202    protected function individualsToFixQuery(Tree $tree, array $params): Builder
203    {
204        $query = DB::table('individuals')
205            ->where('i_file', '=', $tree->id());
206
207        if (isset($params['start'], $params['end'])) {
208            $query->whereBetween('i_id', [$params['start'], $params['end']]);
209        }
210
211        return $query;
212    }
213
214    /**
215     * XREFs of location records that might need fixing.
216     *
217     * @param Tree                 $tree
218     * @param array<string,string> $params
219     *
220     * @return Collection<int,string>|null
221     */
222    protected function locationsToFix(/** @scrutinizer ignore-unused */ Tree $tree, /** @scrutinizer ignore-unused */ array $params): ?Collection
223    {
224        return null;
225    }
226
227    /**
228     * @param Tree                 $tree
229     * @param array<string,string> $params
230     *
231     * @return Builder
232     */
233    protected function locationsToFixQuery(Tree $tree, array $params): Builder
234    {
235        $query = DB::table('other')
236            ->where('o_type', '=', Location::RECORD_TYPE)
237            ->where('o_file', '=', $tree->id());
238
239        if (isset($params['start'], $params['end'])) {
240            $query->whereBetween('o_id', [$params['start'], $params['end']]);
241        }
242
243        return $query;
244    }
245
246    /**
247     * XREFs of media records that might need fixing.
248     *
249     * @param Tree                 $tree
250     * @param array<string,string> $params
251     *
252     * @return Collection<int,string>|null
253     */
254    protected function mediaToFix(/** @scrutinizer ignore-unused */ Tree $tree, /** @scrutinizer ignore-unused */ array $params): ?Collection
255    {
256        return null;
257    }
258
259    /**
260     * @param Tree                 $tree
261     * @param array<string,string> $params
262     *
263     * @return Builder
264     */
265    protected function mediaToFixQuery(Tree $tree, array $params): Builder
266    {
267        $query = DB::table('media')
268            ->where('m_file', '=', $tree->id());
269
270        if (isset($params['start'], $params['end'])) {
271            $query->whereBetween('m_id', [$params['start'], $params['end']]);
272        }
273
274        return $query;
275    }
276
277    /**
278     * XREFs of note records that might need fixing.
279     *
280     * @param Tree                 $tree
281     * @param array<string,string> $params
282     *
283     * @return Collection<int,string>|null
284     */
285    protected function notesToFix(/** @scrutinizer ignore-unused */ Tree $tree, /** @scrutinizer ignore-unused */ array $params): ?Collection
286    {
287        return null;
288    }
289
290    /**
291     * @param Tree                 $tree
292     * @param array<string,string> $params
293     *
294     * @return Builder
295     */
296    protected function notesToFixQuery(Tree $tree, array $params): Builder
297    {
298        $query = DB::table('other')
299            ->where('o_type', '=', Note::RECORD_TYPE)
300            ->where('o_file', '=', $tree->id());
301
302        if (isset($params['start'], $params['end'])) {
303            $query->whereBetween('o_id', [$params['start'], $params['end']]);
304        }
305
306        return $query;
307    }
308
309    /**
310     * XREFs of repository records that might need fixing.
311     *
312     * @param Tree                 $tree
313     * @param array<string,string> $params
314     *
315     * @return Collection<int,string>|null
316     */
317    protected function repositoriesToFix(/** @scrutinizer ignore-unused */ Tree $tree, /** @scrutinizer ignore-unused */ array $params): ?Collection
318    {
319        return null;
320    }
321
322    /**
323     * @param Tree                 $tree
324     * @param array<string,string> $params
325     *
326     * @return Builder
327     */
328    protected function repositoriesToFixQuery(Tree $tree, array $params): Builder
329    {
330        $query = DB::table('other')
331            ->where('o_type', '=', Repository::RECORD_TYPE)
332            ->where('o_file', '=', $tree->id());
333
334        if (isset($params['start'], $params['end'])) {
335            $query->whereBetween('o_id', [$params['start'], $params['end']]);
336        }
337
338        return $query;
339    }
340
341    /**
342     * XREFs of source records that might need fixing.
343     *
344     * @param Tree                 $tree
345     * @param array<string,string> $params
346     *
347     * @return Collection<int,string>|null
348     */
349    protected function sourcesToFix(/** @scrutinizer ignore-unused */ Tree $tree, /** @scrutinizer ignore-unused */ array $params): ?Collection
350    {
351        return null;
352    }
353
354    /**
355     * @param Tree                 $tree
356     * @param array<string,string> $params
357     *
358     * @return Builder
359     */
360    protected function sourcesToFixQuery(Tree $tree, array $params): Builder
361    {
362        $query = DB::table('sources')
363            ->where('s_file', '=', $tree->id());
364
365        if (isset($params['start'], $params['end'])) {
366            $query->whereBetween('s_id', [$params['start'], $params['end']]);
367        }
368
369        return $query;
370    }
371
372    /**
373     * XREFs of submitter records that might need fixing.
374     *
375     * @param Tree                 $tree
376     * @param array<string,string> $params
377     *
378     * @return Collection<int,string>|null
379     */
380    protected function submittersToFix(/** @scrutinizer ignore-unused */ Tree $tree, /** @scrutinizer ignore-unused */ array $params): ?Collection
381    {
382        return null;
383    }
384
385    /**
386     * @param Tree                 $tree
387     * @param array<string,string> $params
388     *
389     * @return Builder
390     */
391    protected function submittersToFixQuery(Tree $tree, array $params): Builder
392    {
393        $query = DB::table('other')
394            ->where('o_type', '=', Submitter::RECORD_TYPE)
395            ->where('o_file', '=', $tree->id());
396
397        if (isset($params['start'], $params['end'])) {
398            $query->whereBetween('o_id', [$params['start'], $params['end']]);
399        }
400
401        return $query;
402    }
403
404    /**
405     * Merge pending changes of a given type.  We need to check all pending records.
406     *
407     * @param Collection<int,string> $records
408     * @param Tree                   $tree
409     * @param string                 $type
410     *
411     * @return Collection<int,object>
412     */
413    private function mergePendingRecords(Collection $records, Tree $tree, string $type): Collection
414    {
415        $pending = DB::table('change')
416            ->where('gedcom_id', '=', $tree->id())
417            ->where('status', '=', 'pending')
418            ->where(static function (Builder $query) use ($type): void {
419                $query
420                    ->where('old_gedcom', 'LIKE', '%@ ' . $type . '\n%')
421                    ->orWhere('new_gedcom', 'LIKE', '%@ ' . $type . '\n%');
422            })
423            ->pluck('xref');
424
425        return $records
426            ->concat($pending)
427            ->map(static function (string $xref) use ($type): object {
428                return (object) ['xref' => $xref, 'type' => $type];
429            });
430    }
431}
432