Passed
Push — master ( b9de05...5e23c3 )
by Greg
06:49
created

AdminController::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 3
rs 10
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\Http\Controllers;
21
22
use Fisharebest\Webtrees\Auth;
23
use Fisharebest\Webtrees\Family;
24
use Fisharebest\Webtrees\FlashMessages;
25
use Fisharebest\Webtrees\GedcomRecord;
26
use Fisharebest\Webtrees\GedcomTag;
27
use Fisharebest\Webtrees\I18N;
28
use Fisharebest\Webtrees\Individual;
29
use Fisharebest\Webtrees\Media;
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 Psr\Http\Message\ResponseInterface;
38
use Psr\Http\Message\ServerRequestInterface;
39
use stdClass;
40
41
/**
42
 * Controller for the administration pages
43
 */
44
class AdminController extends AbstractBaseController
45
{
46
    /** @var string */
47
    protected $layout = 'layouts/administration';
48
49
    /** @var TreeService */
50
    private $tree_service;
51
52
    /**
53
     * TreesMenuModule constructor.
54
     *
55
     * @param TreeService $tree_service
56
     */
57
    public function __construct(TreeService $tree_service)
58
    {
59
        $this->tree_service = $tree_service;
60
    }
61
62
    /**
63
     * Merge two genealogy records.
64
     *
65
     * @param ServerRequestInterface $request
66
     *
67
     * @return ResponseInterface
68
     */
69
    public function mergeRecords(ServerRequestInterface $request): ResponseInterface
70
    {
71
        $tree  = $request->getAttribute('tree');
72
        $title = I18N::translate('Merge records') . ' — ' . e($tree->title());
73
74
        $params = $request->getQueryParams();
75
        $xref1  = $params['xref1'] ?? '';
76
        $xref2  = $params['xref2'] ?? '';
77
78
        $record1 = GedcomRecord::getInstance($xref1, $tree);
79
        $record2 = GedcomRecord::getInstance($xref2, $tree);
80
81
        if ($xref1 !== '' && $record1 === null) {
82
            $xref1 = '';
83
        }
84
85
        if ($xref2 !== '' && $record2 === null) {
86
            $xref2 = '';
87
        }
88
89
        if ($record1 === $record2) {
90
            $xref2 = '';
91
        }
92
93
        if ($record1 !== null && $record2 && $record1::RECORD_TYPE !== $record2::RECORD_TYPE) {
94
            $xref2 = '';
95
        }
96
97
        if ($xref1 === '' || $xref2 === '') {
98
            return $this->viewResponse('admin/merge-records-step-1', [
99
                'individual1' => $record1 instanceof Individual ? $record1 : null,
100
                'individual2' => $record2 instanceof Individual ? $record2 : null,
101
                'family1'     => $record1 instanceof Family ? $record1 : null,
102
                'family2'     => $record2 instanceof Family ? $record2 : null,
103
                'source1'     => $record1 instanceof Source ? $record1 : null,
104
                'source2'     => $record2 instanceof Source ? $record2 : null,
105
                'repository1' => $record1 instanceof Repository ? $record1 : null,
106
                'repository2' => $record2 instanceof Repository ? $record2 : null,
107
                'media1'      => $record1 instanceof Media ? $record1 : null,
108
                'media2'      => $record2 instanceof Media ? $record2 : null,
109
                'note1'       => $record1 instanceof Note ? $record1 : null,
110
                'note2'       => $record2 instanceof Note ? $record2 : null,
111
                'title'       => $title,
112
            ]);
113
        }
114
115
        // Facts found both records
116
        $facts = [];
117
        // Facts found in only one record
118
        $facts1 = [];
119
        $facts2 = [];
120
121
        foreach ($record1->facts() as $fact) {
122
            if (!$fact->isPendingDeletion() && $fact->getTag() !== 'CHAN') {
123
                $facts1[$fact->id()] = $fact;
124
            }
125
        }
126
127
        foreach ($record2->facts() as $fact) {
128
            if (!$fact->isPendingDeletion() && $fact->getTag() !== 'CHAN') {
129
                $facts2[$fact->id()] = $fact;
130
            }
131
        }
132
133
        foreach ($facts1 as $id1 => $fact1) {
134
            foreach ($facts2 as $id2 => $fact2) {
135
                if ($fact1->id() === $fact2->id()) {
136
                    $facts[] = $fact1;
137
                    unset($facts1[$id1], $facts2[$id2]);
138
                }
139
            }
140
        }
141
142
        return $this->viewResponse('admin/merge-records-step-2', [
143
            'facts'   => $facts,
144
            'facts1'  => $facts1,
145
            'facts2'  => $facts2,
146
            'record1' => $record1,
147
            'record2' => $record2,
148
            'title'   => $title,
149
        ]);
150
    }
151
152
    /**
153
     * @param ServerRequestInterface $request
154
     *
155
     * @return ResponseInterface
156
     */
157
    public function mergeRecordsAction(ServerRequestInterface $request): ResponseInterface
158
    {
159
        $tree  = $request->getAttribute('tree');
160
        $xref1 = $request->getQueryParams()['xref1'];
161
        $xref2 = $request->getQueryParams()['xref2'];
162
        $keep1 = $request->getParsedBody()['keep1'] ?? [];
163
        $keep2 = $request->getParsedBody()['keep2'] ?? [];
164
165
        // Merge record2 into record1
166
        $record1 = GedcomRecord::getInstance($xref1, $tree);
167
        $record2 = GedcomRecord::getInstance($xref2, $tree);
168
169
        // Facts found both records
170
        $facts = [];
171
        // Facts found in only one record
172
        $facts1 = [];
173
        $facts2 = [];
174
175
        foreach ($record1->facts() as $fact) {
176
            if (!$fact->isPendingDeletion() && $fact->getTag() !== 'CHAN') {
177
                $facts1[$fact->id()] = $fact;
178
            }
179
        }
180
181
        foreach ($record2->facts() as $fact) {
182
            if (!$fact->isPendingDeletion() && $fact->getTag() !== 'CHAN') {
183
                $facts2[$fact->id()] = $fact;
184
            }
185
        }
186
187
        // If we are not auto-accepting, then we can show a link to the pending deletion
188
        if (Auth::user()->getPreference('auto_accept')) {
189
            $record2_name = $record2->fullName();
190
        } else {
191
            $record2_name = '<a class="alert-link" href="' . e($record2->url()) . '">' . $record2->fullName() . '</a>';
192
        }
193
194
        // Update records that link to the one we will be removing.
195
        $linking_records = $record2->linkingRecords();
196
197
        foreach ($linking_records as $record) {
198
            if (!$record->isPendingDeletion()) {
199
                /* I18N: The placeholders are the names of individuals, sources, etc. */
200
                FlashMessages::addMessage(I18N::translate(
201
                    'The link from “%1$s” to “%2$s” has been updated.',
202
                    '<a class="alert-link" href="' . e($record->url()) . '">' . $record->fullName() . '</a>',
203
                    $record2_name
204
                ), 'info');
205
                $gedcom = str_replace('@' . $xref2 . '@', '@' . $xref1 . '@', $record->gedcom());
206
                $gedcom = preg_replace(
207
                    '/(\n1.*@.+@.*(?:(?:\n[2-9].*)*))((?:\n1.*(?:\n[2-9].*)*)*\1)/',
208
                    '$2',
209
                    $gedcom
210
                );
211
                $record->updateRecord($gedcom, true);
212
            }
213
        }
214
215
        // Update any linked user-accounts
216
        DB::table('user_gedcom_setting')
217
            ->where('gedcom_id', '=', $tree->id())
218
            ->whereIn('setting_name', ['gedcomid', 'rootid'])
219
            ->where('setting_value', '=', $xref2)
220
            ->update(['setting_value' => $xref1]);
221
222
        // Merge hit counters
223
        $hits = DB::table('hit_counter')
224
            ->where('gedcom_id', '=', $tree->id())
225
            ->whereIn('page_parameter', [$xref1, $xref2])
226
            ->groupBy(['page_name'])
227
            ->pluck(new Expression('SUM(page_count)'), 'page_name');
228
229
        foreach ($hits as $page_name => $page_count) {
230
            DB::table('hit_counter')
231
                ->where('gedcom_id', '=', $tree->id())
232
                ->where('page_name', '=', $page_name)
233
                ->update(['page_count' => $page_count]);
234
        }
235
236
        DB::table('hit_counter')
237
            ->where('gedcom_id', '=', $tree->id())
238
            ->where('page_parameter', '=', $xref2)
239
            ->delete();
240
241
        $gedcom = '0 @' . $record1->xref() . '@ ' . $record1::RECORD_TYPE;
242
        foreach ($facts as $fact_id => $fact) {
243
            $gedcom .= "\n" . $fact->gedcom();
244
        }
245
        foreach ($facts1 as $fact_id => $fact) {
246
            if (in_array($fact_id, $keep1, true)) {
247
                $gedcom .= "\n" . $fact->gedcom();
248
            }
249
        }
250
        foreach ($facts2 as $fact_id => $fact) {
251
            if (in_array($fact_id, $keep2, true)) {
252
                $gedcom .= "\n" . $fact->gedcom();
253
            }
254
        }
255
256
        DB::table('favorite')
257
            ->where('gedcom_id', '=', $tree->id())
258
            ->where('xref', '=', $xref2)
259
            ->update(['xref' => $xref1]);
260
261
        $record1->updateRecord($gedcom, true);
262
        $record2->deleteRecord();
263
264
        /* I18N: Records are individuals, sources, etc. */
265
        FlashMessages::addMessage(I18N::translate(
266
            'The records “%1$s” and “%2$s” have been merged.',
267
            '<a class="alert-link" href="' . e($record1->url()) . '">' . $record1->fullName() . '</a>',
268
            $record2_name
269
        ), 'success');
270
271
        return redirect(route('merge-records', ['tree' => $tree->name()]));
272
    }
273
274
    /**
275
     * @param ServerRequestInterface $request
276
     *
277
     * @return ResponseInterface
278
     */
279
    public function treePrivacyEdit(ServerRequestInterface $request): ResponseInterface
280
    {
281
        $tree                 = $request->getAttribute('tree');
282
        $title                = e($tree->name()) . ' — ' . I18N::translate('Privacy');
283
        $all_tags             = $this->tagsForPrivacy($tree);
284
        $privacy_constants    = $this->privacyConstants();
285
        $privacy_restrictions = $this->privacyRestrictions($tree);
286
287
        return $this->viewResponse('admin/trees-privacy', [
288
            'all_tags'             => $all_tags,
289
            'count_trees'          => $this->tree_service->all()->count(),
290
            'privacy_constants'    => $privacy_constants,
291
            'privacy_restrictions' => $privacy_restrictions,
292
            'title'                => $title,
293
            'tree'                 => $tree,
294
        ]);
295
    }
296
297
    /**
298
     * @param ServerRequestInterface $request
299
     *
300
     * @return ResponseInterface
301
     */
302
    public function treePrivacyUpdate(ServerRequestInterface $request): ResponseInterface
303
    {
304
        $tree   = $request->getAttribute('tree');
305
        $params = $request->getParsedBody();
306
307
        $delete_default_resn_id = $params['delete'] ?? [];
308
309
        DB::table('default_resn')
310
            ->whereIn('default_resn_id', $delete_default_resn_id)
311
            ->delete();
312
313
        $xrefs     = $params['xref'] ?? [];
314
        $tag_types = $params['tag_type'] ?? [];
315
        $resns     = $params['resn'] ?? [];
316
317
        foreach ($xrefs as $n => $xref) {
318
            $tag_type = $tag_types[$n];
319
            $resn     = $resns[$n];
320
321
            // Delete any existing data
322
            if ($tag_type !== '' && $xref !== '') {
323
                DB::table('default_resn')
324
                    ->where('gedcom_id', '=', $tree->id())
325
                    ->where('tag_type', '=', $tag_type)
326
                    ->where('xref', '=', $xref)
327
                    ->delete();
328
            }
329
330
            if ($tag_type !== '' && $xref === '') {
331
                DB::table('default_resn')
332
                    ->where('gedcom_id', '=', $tree->id())
333
                    ->where('tag_type', '=', $tag_type)
334
                    ->whereNull('xref')
335
                    ->delete();
336
            }
337
338
            if ($tag_type === '' && $xref !== '') {
339
                DB::table('default_resn')
340
                    ->where('gedcom_id', '=', $tree->id())
341
                    ->whereNull('tag_type')
342
                    ->where('xref', '=', $xref)
343
                    ->delete();
344
            }
345
346
            // Add (or update) the new data
347
            if ($tag_type !== '' || $xref !== '') {
348
                DB::table('default_resn')->insert([
349
                    'gedcom_id' => $tree->id(),
350
                    'xref'      => $xref === '' ? null : $xref,
351
                    'tag_type'  => $tag_type === '' ? null : $tag_type,
352
                    'resn'      => $resn,
353
                ]);
354
            }
355
        }
356
357
        $tree->setPreference('HIDE_LIVE_PEOPLE', $params['HIDE_LIVE_PEOPLE']);
358
        $tree->setPreference('KEEP_ALIVE_YEARS_BIRTH', $params['KEEP_ALIVE_YEARS_BIRTH']);
359
        $tree->setPreference('KEEP_ALIVE_YEARS_DEATH', $params['KEEP_ALIVE_YEARS_DEATH']);
360
        $tree->setPreference('MAX_ALIVE_AGE', $params['MAX_ALIVE_AGE']);
361
        $tree->setPreference('REQUIRE_AUTHENTICATION', $params['REQUIRE_AUTHENTICATION']);
362
        $tree->setPreference('SHOW_DEAD_PEOPLE', $params['SHOW_DEAD_PEOPLE']);
363
        $tree->setPreference('SHOW_LIVING_NAMES', $params['SHOW_LIVING_NAMES']);
364
        $tree->setPreference('SHOW_PRIVATE_RELATIONSHIPS', $params['SHOW_PRIVATE_RELATIONSHIPS']);
365
366
        FlashMessages::addMessage(I18N::translate('The preferences for the family tree “%s” have been updated.', e($tree->title()), 'success'));
367
368
        // Coming soon...
369
        if ($params['all_trees'] ?? false) {
370
            FlashMessages::addMessage(I18N::translate('The preferences for all family trees have been updated.', e($tree->title())), 'success');
371
        }
372
        if ($params['new_trees'] ?? false) {
373
            FlashMessages::addMessage(I18N::translate('The preferences for new family trees have been updated.', e($tree->title())), 'success');
374
        }
375
376
        return redirect(route('manage-trees', ['tree' => $tree->name()]));
377
    }
378
379
    /**
380
     * Names of our privacy levels
381
     *
382
     * @return array
383
     */
384
    private function privacyConstants(): array
385
    {
386
        return [
387
            'none'         => I18N::translate('Show to visitors'),
388
            'privacy'      => I18N::translate('Show to members'),
389
            'confidential' => I18N::translate('Show to managers'),
390
            'hidden'       => I18N::translate('Hide from everyone'),
391
        ];
392
    }
393
394
    /**
395
     * The current privacy restrictions for a tree.
396
     *
397
     * @param Tree $tree
398
     *
399
     * @return array
400
     */
401
    private function privacyRestrictions(Tree $tree): array
402
    {
403
        return DB::table('default_resn')
404
            ->where('gedcom_id', '=', $tree->id())
405
            ->get()
406
            ->map(static function (stdClass $row) use ($tree): stdClass {
407
                $row->record = null;
408
                $row->label  = '';
409
410
                if ($row->xref !== null) {
411
                    $row->record = GedcomRecord::getInstance($row->xref, $tree);
412
                }
413
414
                if ($row->tag_type) {
415
                    $row->tag_label = GedcomTag::getLabel($row->tag_type);
416
                } else {
417
                    $row->tag_label = '';
418
                }
419
420
                return $row;
421
            })
422
            ->sort(static function (stdClass $x, stdClass $y): int {
423
                return I18N::strcasecmp($x->tag_label, $y->tag_label);
424
            })
425
            ->all();
426
    }
427
428
    /**
429
     * Generate a list of potential problems with the server.
430
     *
431
     * @param Tree $tree
432
     *
433
     * @return string[]
434
     */
435
    private function tagsForPrivacy(Tree $tree): array
436
    {
437
        $tags = array_unique(array_merge(
438
            explode(',', $tree->getPreference('INDI_FACTS_ADD')),
439
            explode(',', $tree->getPreference('INDI_FACTS_UNIQUE')),
440
            explode(',', $tree->getPreference('FAM_FACTS_ADD')),
441
            explode(',', $tree->getPreference('FAM_FACTS_UNIQUE')),
442
            explode(',', $tree->getPreference('NOTE_FACTS_ADD')),
443
            explode(',', $tree->getPreference('NOTE_FACTS_UNIQUE')),
444
            explode(',', $tree->getPreference('SOUR_FACTS_ADD')),
445
            explode(',', $tree->getPreference('SOUR_FACTS_UNIQUE')),
446
            explode(',', $tree->getPreference('REPO_FACTS_ADD')),
447
            explode(',', $tree->getPreference('REPO_FACTS_UNIQUE')),
448
            [
449
                'SOUR',
450
                'REPO',
451
                'OBJE',
452
                '_PRIM',
453
                'NOTE',
454
                'SUBM',
455
                'SUBN',
456
                '_UID',
457
                'CHAN',
458
            ]
459
        ));
460
461
        $all_tags = [];
462
463
        foreach ($tags as $tag) {
464
            if ($tag) {
465
                $all_tags[$tag] = GedcomTag::getLabel($tag);
466
            }
467
        }
468
469
        uasort($all_tags, '\Fisharebest\Webtrees\I18N::strcasecmp');
470
471
        return array_merge(
472
            ['' => I18N::translate('All facts and events')],
473
            $all_tags
474
        );
475
    }
476
}
477