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