Passed
Push — master ( e92d9e...22ad3b )
by Greg
09:02
created

PendingChangesService::pendingXrefs()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 6
nc 1
nop 1
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * webtrees: online genealogy
5
 * Copyright (C) 2020 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\Services;
21
22
use Fisharebest\Webtrees\Carbon;
23
use Fisharebest\Webtrees\Factory;
24
use Fisharebest\Webtrees\Family;
25
use Fisharebest\Webtrees\Functions\FunctionsImport;
26
use Fisharebest\Webtrees\Gedcom;
27
use Fisharebest\Webtrees\GedcomRecord;
28
use Fisharebest\Webtrees\Header;
29
use Fisharebest\Webtrees\Individual;
30
use Fisharebest\Webtrees\Location;
31
use Fisharebest\Webtrees\Media;
32
use Fisharebest\Webtrees\Note;
33
use Fisharebest\Webtrees\Repository;
34
use Fisharebest\Webtrees\Source;
35
use Fisharebest\Webtrees\Submission;
36
use Fisharebest\Webtrees\Submitter;
37
use Fisharebest\Webtrees\Tree;
38
use Illuminate\Database\Capsule\Manager as DB;
39
use Illuminate\Database\Query\Builder;
40
use Illuminate\Database\Query\Expression;
41
42
use Illuminate\Support\Collection;
43
44
use stdClass;
45
46
use function addcslashes;
47
use function preg_match;
48
use function random_int;
49
50
/**
51
 * Manage pending changes
52
 */
53
class PendingChangesService
54
{
55
    /**
56
     * Which records have pending changes
57
     *
58
     * @param Tree $tree
59
     *
60
     * @return Collection<string>
61
     */
62
    public function pendingXrefs(Tree $tree): Collection
63
    {
64
        return DB::table('change')
65
            ->where('status', '=', 'pending')
66
            ->where('gedcom_id', '=', $tree->id())
67
            ->orderBy('xref')
68
            ->groupBy(['xref'])
69
            ->pluck('xref');
70
    }
71
72
    /**
73
     * @param Tree $tree
74
     * @param int  $n
75
     *
76
     * @return array<array<stdClass>>
77
     */
78
    public function pendingChanges(Tree $tree, int $n): array
79
    {
80
        $xrefs = $this->pendingXrefs($tree);
81
82
        $rows = DB::table('change')
83
            ->join('user', 'user.user_id', '=', 'change.user_id')
84
            ->where('status', '=', 'pending')
85
            ->where('gedcom_id', '=', $tree->id())
86
            ->whereIn('xref', $xrefs->slice(0, $n))
87
            ->orderBy('change.change_id')
88
            ->select(['change.*', 'user.user_name', 'user.real_name'])
89
            ->get();
90
91
        $changes = [];
92
93
        $factories = [
94
            Individual::RECORD_TYPE => Factory::individual(),
95
            Family::RECORD_TYPE     => Factory::family(),
96
            Source::RECORD_TYPE     => Factory::source(),
97
            Repository::RECORD_TYPE => Factory::repository(),
98
            Media::RECORD_TYPE      => Factory::media(),
99
            Note::RECORD_TYPE       => Factory::note(),
100
            Submitter::RECORD_TYPE  => Factory::submitter(),
101
            Submission::RECORD_TYPE => Factory::submission(),
102
            Location::RECORD_TYPE   => Factory::location(),
103
            Header::RECORD_TYPE     => Factory::header(),
104
        ];
105
106
        foreach ($rows as $row) {
107
            $row->change_time = Carbon::make($row->change_time);
108
109
            preg_match('/^0 (?:@' . Gedcom::REGEX_XREF . '@ )?(' . Gedcom::REGEX_TAG . ')/', $row->old_gedcom . $row->new_gedcom, $match);
110
111
            $factory = $factories[$match[1]] ?? Factory::gedcomRecord();
112
113
            $row->record = $factory->new($row->xref, $row->old_gedcom, $row->new_gedcom, $tree);
114
115
            $changes[$row->xref][] = $row;
116
        }
117
118
        return $changes;
119
    }
120
121
    /**
122
     * Accept all changes to a tree.
123
     *
124
     * @param Tree $tree
125
     *
126
     * @param int  $n
127
     *
128
     * @return void
129
     * @throws \Fisharebest\Webtrees\Exceptions\GedcomErrorException
130
     */
131
    public function acceptTree(Tree $tree, int $n): void
132
    {
133
        $xrefs = $this->pendingXrefs($tree);
134
135
        $changes = DB::table('change')
136
            ->where('gedcom_id', '=', $tree->id())
137
            ->where('status', '=', 'pending')
138
            ->whereIn('xref', $xrefs->slice(0, $n))
139
            ->orderBy('change_id')
140
            ->lockForUpdate()
141
            ->get();
142
143
        foreach ($changes as $change) {
144
            if ($change->new_gedcom === '') {
145
                // delete
146
                FunctionsImport::updateRecord($change->old_gedcom, $tree, true);
147
            } else {
148
                // add/update
149
                FunctionsImport::updateRecord($change->new_gedcom, $tree, false);
150
            }
151
152
            DB::table('change')
153
                ->where('change_id', '=', $change->change_id)
154
                ->update(['status' => 'accepted']);
155
        }
156
    }
157
158
    /**
159
     * Accept all changes to a record.
160
     *
161
     * @param GedcomRecord $record
162
     */
163
    public function acceptRecord(GedcomRecord $record): void
164
    {
165
        $changes = DB::table('change')
166
            ->where('gedcom_id', '=', $record->tree()->id())
167
            ->where('xref', '=', $record->xref())
168
            ->where('status', '=', 'pending')
169
            ->orderBy('change_id')
170
            ->lockForUpdate()
171
            ->get();
172
173
        foreach ($changes as $change) {
174
            if ($change->new_gedcom === '') {
175
                // delete
176
                FunctionsImport::updateRecord($change->old_gedcom, $record->tree(), true);
177
            } else {
178
                // add/update
179
                FunctionsImport::updateRecord($change->new_gedcom, $record->tree(), false);
180
            }
181
182
            DB::table('change')
183
                ->where('change_id', '=', $change->change_id)
184
                ->update(['status' => 'accepted']);
185
        }
186
    }
187
188
    /**
189
     * Accept a change (and previous changes) to a record.
190
     *
191
     * @param GedcomRecord $record
192
     * @param string $change_id
193
     */
194
    public function acceptChange(GedcomRecord $record, string $change_id): void
195
    {
196
        $changes = DB::table('change')
197
            ->where('gedcom_id', '=', $record->tree()->id())
198
            ->where('xref', '=', $record->xref())
199
            ->where('change_id', '<=', $change_id)
200
            ->where('status', '=', 'pending')
201
            ->orderBy('change_id')
202
            ->get();
203
204
        foreach ($changes as $change) {
205
            if ($change->new_gedcom === '') {
206
                // delete
207
                FunctionsImport::updateRecord($change->old_gedcom, $record->tree(), true);
208
            } else {
209
                // add/update
210
                FunctionsImport::updateRecord($change->new_gedcom, $record->tree(), false);
211
            }
212
213
            DB::table('change')
214
                ->where('change_id', '=', $change->change_id)
215
                ->update(['status' => 'accepted']);
216
        }
217
    }
218
219
    /**
220
     * Reject all changes to a tree.
221
     *
222
     * @param Tree $tree
223
     */
224
    public function rejectTree(Tree $tree): void
225
    {
226
        DB::table('change')
227
            ->where('gedcom_id', '=', $tree->id())
228
            ->where('status', '=', 'pending')
229
            ->update(['status' => 'rejected']);
230
    }
231
232
    /**
233
     * Reject a change (subsequent changes) to a record.
234
     *
235
     * @param GedcomRecord $record
236
     * @param string       $change_id
237
     */
238
    public function rejectChange(GedcomRecord $record, string $change_id): void
239
    {
240
        DB::table('change')
241
            ->where('gedcom_id', '=', $record->tree()->id())
242
            ->where('xref', '=', $record->xref())
243
            ->where('change_id', '>=', $change_id)
244
            ->where('status', '=', 'pending')
245
            ->update(['status' => 'rejected']);
246
    }
247
248
    /**
249
     * Reject all changes to a record.
250
     *
251
     * @param GedcomRecord $record
252
     */
253
    public function rejectRecord(GedcomRecord $record): void
254
    {
255
        DB::table('change')
256
            ->where('gedcom_id', '=', $record->tree()->id())
257
            ->where('xref', '=', $record->xref())
258
            ->where('status', '=', 'pending')
259
            ->update(['status' => 'rejected']);
260
    }
261
262
    /**
263
     * Generate a query for filtering the changes log.
264
     *
265
     * @param string[] $params
266
     *
267
     * @return Builder
268
     */
269
    public function changesQuery(array $params): Builder
270
    {
271
        $tree     = $params['tree'];
272
        $from     = $params['from'] ?? '';
273
        $to       = $params['to'] ?? '';
274
        $type     = $params['type'] ?? '';
275
        $oldged   = $params['oldged'] ?? '';
276
        $newged   = $params['newged'] ?? '';
277
        $xref     = $params['xref'] ?? '';
278
        $username = $params['username'] ?? '';
279
280
        $query = DB::table('change')
281
            ->leftJoin('user', 'user.user_id', '=', 'change.user_id')
282
            ->join('gedcom', 'gedcom.gedcom_id', '=', 'change.gedcom_id')
283
            ->select(['change.*', new Expression("COALESCE(user_name, '<none>') AS user_name"), 'gedcom_name'])
284
            ->where('gedcom_name', '=', $tree);
285
286
        if ($from !== '') {
287
            $query->where('change_time', '>=', $from);
288
        }
289
290
        if ($to !== '') {
291
            // before end of the day
292
            $query->where('change_time', '<', Carbon::make($to)->addDay());
293
        }
294
295
        if ($type !== '') {
296
            $query->where('status', '=', $type);
297
        }
298
299
        if ($oldged !== '') {
300
            $query->where('old_gedcom', 'LIKE', '%' . addcslashes($oldged, '\\%_') . '%');
301
        }
302
        if ($newged !== '') {
303
            $query->where('new_gedcom', 'LIKE', '%' . addcslashes($newged, '\\%_') . '%');
304
        }
305
306
        if ($xref !== '') {
307
            $query->where('xref', '=', $xref);
308
        }
309
310
        if ($username !== '') {
311
            $query->where('user_name', 'LIKE', '%' . addcslashes($username, '\\%_') . '%');
312
        }
313
314
        return $query;
315
    }
316
}
317