Passed
Push — master ( b262b3...b6f3a5 )
by Greg
05:50
created

BatchUpdateModule::findNextXref()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 6
nc 4
nop 4
dl 0
loc 12
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * webtrees: online genealogy
5
 * Copyright (C) 2019 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\Module;
21
22
use Fisharebest\Webtrees\Auth;
23
use Fisharebest\Webtrees\Family;
24
use Fisharebest\Webtrees\GedcomRecord;
25
use Fisharebest\Webtrees\Http\RequestHandlers\ControlPanel;
26
use Fisharebest\Webtrees\I18N;
27
use Fisharebest\Webtrees\Individual;
28
use Fisharebest\Webtrees\Media;
29
use Fisharebest\Webtrees\Module\BatchUpdate\BatchUpdateBasePlugin;
30
use Fisharebest\Webtrees\Note;
31
use Fisharebest\Webtrees\Repository;
32
use Fisharebest\Webtrees\Services\TreeService;
33
use Fisharebest\Webtrees\Source;
34
use Fisharebest\Webtrees\Tree;
35
use Illuminate\Database\Capsule\Manager as DB;
36
use Illuminate\Database\Query\Expression;
37
use InvalidArgumentException;
38
use Psr\Http\Message\ResponseInterface;
39
use Psr\Http\Message\ServerRequestInterface;
40
use stdClass;
41
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
42
43
use function array_key_exists;
44
use function assert;
45
46
/**
47
 * Class BatchUpdateModule
48
 */
49
class BatchUpdateModule extends AbstractModule implements ModuleConfigInterface
50
{
51
    use ModuleConfigTrait;
52
53
    /** @var string */
54
    protected $layout = 'layouts/administration';
55
56
    /** @var TreeService */
57
    private $tree_service;
58
59
    /**
60
     * BatchUpdateModule constructor.
61
     *
62
     * @param TreeService $tree_service
63
     */
64
    public function __construct(TreeService $tree_service)
65
    {
66
        $this->tree_service = $tree_service;
67
    }
68
69
    /**
70
     * How should this module be identified in the control panel, etc.?
71
     *
72
     * @return string
73
     */
74
    public function title(): string
75
    {
76
        /* I18N: Name of a module */
77
        return I18N::translate('Batch update');
78
    }
79
80
    /**
81
     * A sentence describing what this module does.
82
     *
83
     * @return string
84
     */
85
    public function description(): string
86
    {
87
        /* I18N: Description of the “Batch update” module */
88
        return I18N::translate('Apply automatic corrections to your genealogy data.');
89
    }
90
91
    /**
92
     * Main entry point
93
     *
94
     * @param ServerRequestInterface $request
95
     *
96
     * @return ResponseInterface
97
     */
98
    public function getAdminAction(ServerRequestInterface $request): ResponseInterface
99
    {
100
        $tree    = $request->getQueryParams()['tree'] ?? '';
101
        $user    = $request->getAttribute('user');
102
        $plugin  = $request->getQueryParams()['plugin'] ?? '';
103
        $xref    = $request->getQueryParams()['xref'] ?? '';
104
        $plugins = $this->getPluginList();
105
        $plugin  = $plugins[$plugin] ?? null;
106
107
        // This module can't run without a tree
108
        $tree = $this->tree_service->findByName($tree) ?? $this->tree_service->all()->first();
109
        if (!$tree instanceof Tree) {
110
            return redirect(route(ControlPanel::class));
111
        }
112
113
        $curr_xref = '';
114
        $prev_xref = '';
115
        $next_xref = '';
116
117
        // Don't do any processing until a plugin is chosen.
118
        if ($plugin !== null) {
119
            $plugin->getOptions($request);
120
121
            $all_data = $this->allData($plugin, $tree);
122
123
            // Make sure that our requested record really does need updating.
124
            // It may have been updated in another session, or may not have
125
            // been specified at all.
126
            if (array_key_exists($xref, $all_data) && $plugin->doesRecordNeedUpdate($this->getRecord($all_data[$xref], $tree))) {
127
                $curr_xref = $xref;
128
            }
129
130
            // The requested record doesn't need updating - find one that does
131
            if ($curr_xref === '') {
132
                $curr_xref = $this->findNextXref($plugin, $xref, $all_data, $tree);
133
            }
134
            if ($curr_xref === '') {
135
                $curr_xref = $this->findPrevXref($plugin, $xref, $all_data, $tree);
136
            }
137
138
            // If we've found a record to update, get details and look for the next/prev
139
            if ($curr_xref !== '') {
140
                $prev_xref = $this->findPrevXref($plugin, $xref, $all_data, $tree);
141
                $next_xref = $this->findNextXref($plugin, $xref, $all_data, $tree);
142
            }
143
        }
144
145
        return $this->viewResponse('modules/batch_update/admin', [
146
            'auto_accept' => (bool) $user->getPreference('auto_accept'),
147
            'plugins'     => $plugins,
148
            'curr_xref'   => $curr_xref,
149
            'next_xref'   => $next_xref,
150
            'plugin'      => $plugin,
151
            'module'      => $this->name(),
152
            'record'      => GedcomRecord::getInstance($curr_xref, $tree),
153
            'prev_xref'   => $prev_xref,
154
            'title'       => I18N::translate('Batch update'),
155
            'tree'        => $tree,
156
            'trees'       => Tree::getNameList(),
157
        ]);
158
    }
159
160
    /**
161
     * Scan the plugin folder for a list of plugins
162
     *
163
     * @return BatchUpdateBasePlugin[]
164
     */
165
    private function getPluginList(): array
166
    {
167
        $plugins = [];
168
        $files   = glob(__DIR__ . '/BatchUpdate/BatchUpdate*Plugin.php', GLOB_NOSORT);
169
170
        foreach ($files as $file) {
171
            $base_class = basename($file, '.php');
172
173
            if ($base_class !== 'BatchUpdateBasePlugin') {
174
                $class           = __NAMESPACE__ . '\\BatchUpdate\\' . basename($file, '.php');
175
                $plugins[$class] = new $class();
176
            }
177
        }
178
179
        return $plugins;
180
    }
181
182
    /**
183
     * Fetch all records that might need updating.
184
     *
185
     * @param BatchUpdateBasePlugin $plugin
186
     * @param Tree                  $tree
187
     *
188
     * @return object[]
189
     */
190
    private function allData(BatchUpdateBasePlugin $plugin, Tree $tree): array
191
    {
192
        $tmp = [];
193
194
        foreach ($plugin->getRecordTypesToUpdate() as $type) {
195
            switch ($type) {
196
                case 'INDI':
197
                    $rows = DB::table('individuals')
198
                        ->where('i_file', '=', $tree->id())
199
                        ->select(['i_id AS xref', new Expression("'INDI' AS type"), 'i_gedcom AS gedcom'])
200
                        ->get();
201
202
                    $tmp = array_merge($tmp, $rows->all());
203
                    break;
204
205
                case 'FAM':
206
                    $rows = DB::table('families')
207
                        ->where('f_file', '=', $tree->id())
208
                        ->select(['f_id AS xref', new Expression("'FAM' AS type"), 'f_gedcom AS gedcom'])
209
                        ->get();
210
211
                    $tmp = array_merge($tmp, $rows->all());
212
                    break;
213
214
                case 'SOUR':
215
                    $rows = DB::table('sources')
216
                        ->where('s_file', '=', $tree->id())
217
                        ->select(['s_id AS xref', new Expression("'SOUR' AS type"), 's_gedcom AS gedcom'])
218
                        ->get();
219
220
                    $tmp = array_merge($tmp, $rows->all());
221
                    break;
222
223
                case 'OBJE':
224
                    $rows = DB::table('media')
225
                        ->where('m_file', '=', $tree->id())
226
                        ->select(['m_id AS xref', new Expression("'OBJE' AS type"), 'm_gedcom AS gedcom'])
227
                        ->get();
228
229
                    $tmp = array_merge($tmp, $rows->all());
230
                    break;
231
232
                default:
233
                    $rows = DB::table('other')
234
                        ->where('o_file', '=', $tree->id())
235
                        ->where('o_type', '=', $type)
236
                        ->select(['o_id AS xref', 'o_type AS type', 'o_gedcom AS gedcom'])
237
                        ->get();
238
239
                    $tmp = array_merge($tmp, $rows->all());
240
                    break;
241
            }
242
        }
243
244
        $data = [];
245
246
        foreach ($tmp as $value) {
247
            $data[$value->xref] = $value;
248
        }
249
250
        ksort($tmp);
251
252
        return $data;
253
    }
254
255
    /**
256
     * @param stdClass $record
257
     * @param Tree     $tree
258
     *
259
     * @return GedcomRecord
260
     */
261
    public function getRecord(stdClass $record, Tree $tree): GedcomRecord
262
    {
263
        switch ($record->type) {
264
            case 'INDI':
265
                return Individual::getInstance($record->xref, $tree, $record->gedcom);
266
267
            case 'FAM':
268
                return Family::getInstance($record->xref, $tree, $record->gedcom);
269
270
            case 'SOUR':
271
                return Source::getInstance($record->xref, $tree, $record->gedcom);
272
273
            case 'REPO':
274
                return Repository::getInstance($record->xref, $tree, $record->gedcom);
275
276
            case 'OBJE':
277
                return Media::getInstance($record->xref, $tree, $record->gedcom);
278
279
            case 'NOTE':
280
                return Note::getInstance($record->xref, $tree, $record->gedcom);
281
282
            default:
283
                return GedcomRecord::getInstance($record->xref, $tree, $record->gedcom);
284
        }
285
    }
286
287
    /**
288
     * Find the next record that needs to be updated.
289
     *
290
     * @param BatchUpdateBasePlugin $plugin
291
     * @param string                $xref
292
     * @param array                 $all_data
293
     * @param Tree                  $tree
294
     *
295
     * @return string
296
     */
297
    private function findNextXref(BatchUpdateBasePlugin $plugin, string $xref, array $all_data, Tree $tree): string
298
    {
299
        foreach (array_keys($all_data) as $key) {
300
            if ($key > $xref) {
301
                $record = $this->getRecord($all_data[$key], $tree);
302
                if ($plugin->doesRecordNeedUpdate($record)) {
303
                    return $key;
304
                }
305
            }
306
        }
307
308
        return '';
309
    }
310
311
    /**
312
     * Find the previous record that needs to be updated.
313
     *
314
     * @param BatchUpdateBasePlugin $plugin
315
     * @param string                $xref
316
     * @param array                 $all_data
317
     * @param Tree                  $tree
318
     *
319
     * @return string
320
     */
321
    private function findPrevXref(BatchUpdateBasePlugin $plugin, string $xref, array $all_data, Tree $tree): string
322
    {
323
        foreach (array_reverse($all_data) as $key => $value) {
324
            if ($key > $xref) {
325
                $record = $this->getRecord($all_data[$key], $tree);
326
                if ($plugin->doesRecordNeedUpdate($record)) {
327
                    return $key;
328
                }
329
            }
330
        }
331
332
        return '';
333
    }
334
335
    /**
336
     * Perform an update
337
     *
338
     * @param ServerRequestInterface $request
339
     *
340
     * @return ResponseInterface
341
     */
342
    public function postAdminAction(ServerRequestInterface $request): ResponseInterface
343
    {
344
        $tree = $request->getAttribute('tree');
345
        assert($tree instanceof Tree, new InvalidArgumentException());
346
347
        $plugin = $request->getQueryParams()['plugin'] ?? '';
348
        $xref   = $request->getParsedBody()['xref'] ?? '';
349
        $update = $request->getParsedBody()['update'] ?? '';
350
351
        $plugins = $this->getPluginList();
352
        $plugin  = $plugins[$plugin] ?? null;
353
354
        if ($plugin === null) {
355
            throw new NotFoundHttpException();
356
        }
357
        $plugin->getOptions($request);
358
359
        $all_data = $this->allData($plugin, $tree);
360
361
        $parameters = $request->getParsedBody();
362
        unset($parameters['update']);
363
364
        switch ($update) {
365
            case 'one':
366
                if (array_key_exists($xref, $all_data)) {
367
                    $record = $this->getRecord($all_data[$xref], $tree);
368
                    if ($plugin->doesRecordNeedUpdate($record)) {
369
                        $new_gedcom = $plugin->updateRecord($record);
370
                        $record->updateRecord($new_gedcom, false);
371
                    }
372
                }
373
374
                $parameters['xref'] = $this->findNextXref($plugin, $xref, $all_data, $tree);
375
                break;
376
377
            case 'all':
378
                foreach ($all_data as $xref => $value) {
379
                    $record = $this->getRecord($value, $tree);
380
                    if ($plugin->doesRecordNeedUpdate($record)) {
381
                        $new_gedcom = $plugin->updateRecord($record);
382
                        $record->updateRecord($new_gedcom, false);
383
                    }
384
                }
385
                $parameters['xref'] = '';
386
                break;
387
        }
388
389
        $url = route('module', $parameters);
0 ignored issues
show
Bug introduced by
It seems like $parameters can also be of type null and object; however, parameter $parameters of route() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

389
        $url = route('module', /** @scrutinizer ignore-type */ $parameters);
Loading history...
390
391
        return redirect($url);
392
    }
393
}
394