ClippingsCartModule   F
last analyzed

Complexity

Total Complexity 129

Size/Duplication

Total Lines 1104
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 492
dl 0
loc 1104
rs 2
c 0
b 0
f 0
wmc 129

46 Methods

Rating   Name   Duplication   Size   Complexity  
A title() 0 4 1
A defaultMenuOrder() 0 3 1
A description() 0 4 1
A __construct() 0 6 1
A getAddNoteAction() 0 19 1
A addSubmitterLinksToCart() 0 9 4
A addFamilyAndDescendantsToCart() 0 7 3
A postAddFamilyAction() 0 24 4
A addMediaLinksToCart() 0 9 4
A getAddRepositoryAction() 0 19 1
A getMenu() 0 47 4
A getAddMediaAction() 0 19 1
A postAddSourceAction() 0 21 4
A addIndividualToCart() 0 17 3
F postDownloadAction() 0 75 17
A addSourceToCart() 0 15 3
A postRemoveAction() 0 17 2
B postAddIndividualAction() 0 42 10
A addFamilyToCart() 0 22 4
A postAddRepositoryAction() 0 14 2
A addNoteLinksToCart() 0 9 4
A getDownloadFormAction() 0 17 1
A getAddSubmitterAction() 0 19 1
A addFamilyAndChildrenToCart() 0 6 2
A addRepositoryLinksToCart() 0 9 4
A postAddMediaAction() 0 10 1
A getEmptyAction() 0 17 2
A getAddLocationAction() 0 19 1
A addAncestorsToCart() 0 9 3
A getAddIndividualAction() 0 35 2
A getAddFamilyAction() 0 23 1
A addLocationLinksToCart() 0 9 4
A getAddSourceAction() 0 20 1
A getShowAction() 0 9 1
A addSubmitterToCart() 0 13 3
A addAncestorFamiliesToCart() 0 7 3
A addLocationToCart() 0 17 3
A allRecordsInCart() 0 18 3
A isCartEmpty() 0 7 2
A addSourceLinksToCart() 0 9 4
A postAddSubmitterAction() 0 10 1
A postAddNoteAction() 0 10 1
A addRepositoryToCart() 0 14 3
A postAddLocationAction() 0 10 1
A addMediaToCart() 0 14 3
A addNoteToCart() 0 12 3

How to fix   Complexity   

Complex Class

Complex classes like ClippingsCartModule often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ClippingsCartModule, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * webtrees: online genealogy
5
 * Copyright (C) 2023 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
18
declare(strict_types=1);
19
20
namespace Fisharebest\Webtrees\Module;
21
22
use Fisharebest\Webtrees\Auth;
23
use Fisharebest\Webtrees\Encodings\ANSEL;
24
use Fisharebest\Webtrees\Encodings\ASCII;
25
use Fisharebest\Webtrees\Encodings\UTF16BE;
26
use Fisharebest\Webtrees\Encodings\UTF8;
27
use Fisharebest\Webtrees\Encodings\Windows1252;
28
use Fisharebest\Webtrees\Family;
29
use Fisharebest\Webtrees\Gedcom;
30
use Fisharebest\Webtrees\GedcomRecord;
31
use Fisharebest\Webtrees\Http\RequestHandlers\FamilyPage;
32
use Fisharebest\Webtrees\Http\RequestHandlers\IndividualPage;
33
use Fisharebest\Webtrees\Http\RequestHandlers\LocationPage;
34
use Fisharebest\Webtrees\Http\RequestHandlers\MediaPage;
35
use Fisharebest\Webtrees\Http\RequestHandlers\NotePage;
36
use Fisharebest\Webtrees\Http\RequestHandlers\RepositoryPage;
37
use Fisharebest\Webtrees\Http\RequestHandlers\SourcePage;
38
use Fisharebest\Webtrees\Http\RequestHandlers\SubmitterPage;
39
use Fisharebest\Webtrees\I18N;
40
use Fisharebest\Webtrees\Individual;
41
use Fisharebest\Webtrees\Location;
42
use Fisharebest\Webtrees\Media;
43
use Fisharebest\Webtrees\Menu;
44
use Fisharebest\Webtrees\Note;
45
use Fisharebest\Webtrees\Registry;
46
use Fisharebest\Webtrees\Repository;
47
use Fisharebest\Webtrees\Services\GedcomExportService;
48
use Fisharebest\Webtrees\Services\LinkedRecordService;
49
use Fisharebest\Webtrees\Session;
50
use Fisharebest\Webtrees\Source;
51
use Fisharebest\Webtrees\Submitter;
52
use Fisharebest\Webtrees\Tree;
53
use Fisharebest\Webtrees\Validator;
54
use Illuminate\Support\Collection;
55
use Psr\Http\Message\ResponseInterface;
56
use Psr\Http\Message\ServerRequestInterface;
57
58
use function array_filter;
59
use function array_keys;
60
use function array_map;
61
use function array_search;
62
use function assert;
63
use function count;
64
use function date;
65
use function extension_loaded;
66
use function in_array;
67
use function is_array;
68
use function is_string;
69
use function preg_match_all;
70
use function redirect;
71
use function route;
72
use function str_replace;
73
use function uasort;
74
use function view;
75
76
use const PREG_SET_ORDER;
77
78
/**
79
 * Class ClippingsCartModule
80
 */
81
class ClippingsCartModule extends AbstractModule implements ModuleMenuInterface
82
{
83
    use ModuleMenuTrait;
84
85
    // What to add to the cart?
86
    private const ADD_RECORD_ONLY        = 'record';
87
    private const ADD_CHILDREN           = 'children';
88
    private const ADD_DESCENDANTS        = 'descendants';
89
    private const ADD_PARENT_FAMILIES    = 'parents';
90
    private const ADD_SPOUSE_FAMILIES    = 'spouses';
91
    private const ADD_ANCESTORS          = 'ancestors';
92
    private const ADD_ANCESTOR_FAMILIES  = 'families';
93
    private const ADD_LINKED_INDIVIDUALS = 'linked';
94
95
    // Routes that have a record which can be added to the clipboard
96
    private const ROUTES_WITH_RECORDS = [
97
        'Family'     => FamilyPage::class,
98
        'Individual' => IndividualPage::class,
99
        'Media'      => MediaPage::class,
100
        'Location'   => LocationPage::class,
101
        'Note'       => NotePage::class,
102
        'Repository' => RepositoryPage::class,
103
        'Source'     => SourcePage::class,
104
        'Submitter'  => SubmitterPage::class,
105
    ];
106
107
    /** @var int The default access level for this module.  It can be changed in the control panel. */
108
    protected int $access_level = Auth::PRIV_USER;
109
110
    private GedcomExportService $gedcom_export_service;
111
112
    private LinkedRecordService $linked_record_service;
113
114
    /**
115
     * @param GedcomExportService $gedcom_export_service
116
     * @param LinkedRecordService $linked_record_service
117
     */
118
    public function __construct(
119
        GedcomExportService $gedcom_export_service,
120
        LinkedRecordService $linked_record_service
121
    ) {
122
        $this->gedcom_export_service = $gedcom_export_service;
123
        $this->linked_record_service = $linked_record_service;
124
    }
125
126
    /**
127
     * A sentence describing what this module does.
128
     *
129
     * @return string
130
     */
131
    public function description(): string
132
    {
133
        /* I18N: Description of the “Clippings cart” module */
134
        return I18N::translate('Select records from your family tree and save them as a GEDCOM file.');
135
    }
136
137
    /**
138
     * The default position for this menu.  It can be changed in the control panel.
139
     *
140
     * @return int
141
     */
142
    public function defaultMenuOrder(): int
143
    {
144
        return 6;
145
    }
146
147
    /**
148
     * A menu, to be added to the main application menu.
149
     */
150
    public function getMenu(Tree $tree): Menu|null
151
    {
152
        $request = Registry::container()->get(ServerRequestInterface::class);
153
        $route   = Validator::attributes($request)->route();
154
        $cart    = Session::get('cart');
155
        $cart    = is_array($cart) ? $cart : [];
156
        $count   = count($cart[$tree->name()] ?? []);
157
        $badge   = view('components/badge', ['count' => $count]);
158
159
        $submenus = [
160
            new Menu($this->title() . ' ' . $badge, route('module', [
161
                'module' => $this->name(),
162
                'action' => 'Show',
163
                'tree'   => $tree->name(),
164
            ]), 'menu-clippings-cart', ['rel' => 'nofollow']),
165
        ];
166
167
        $action = array_search($route->name, self::ROUTES_WITH_RECORDS, true);
168
        if ($action !== false) {
169
            $xref = $route->attributes['xref'];
170
            assert(is_string($xref));
171
172
            $add_route = route('module', [
173
                'module' => $this->name(),
174
                'action' => 'Add' . $action,
175
                'xref'   => $xref,
176
                'tree'   => $tree->name(),
177
            ]);
178
179
            $submenus[] = new Menu(I18N::translate('Add to the clippings cart'), $add_route, 'menu-clippings-add', ['rel' => 'nofollow']);
180
        }
181
182
        if (!$this->isCartEmpty($tree)) {
183
            $submenus[] = new Menu(I18N::translate('Empty the clippings cart'), route('module', [
184
                'module' => $this->name(),
185
                'action' => 'Empty',
186
                'tree'   => $tree->name(),
187
            ]), 'menu-clippings-empty', ['rel' => 'nofollow']);
188
189
            $submenus[] = new Menu(I18N::translate('Download'), route('module', [
190
                'module' => $this->name(),
191
                'action' => 'DownloadForm',
192
                'tree'   => $tree->name(),
193
            ]), 'menu-clippings-download', ['rel' => 'nofollow']);
194
        }
195
196
        return new Menu($this->title(), '#', 'menu-clippings', ['rel' => 'nofollow'], $submenus);
197
    }
198
199
    /**
200
     * How should this module be identified in the control panel, etc.?
201
     *
202
     * @return string
203
     */
204
    public function title(): string
205
    {
206
        /* I18N: Name of a module */
207
        return I18N::translate('Clippings cart');
208
    }
209
210
    /**
211
     * @param Tree $tree
212
     *
213
     * @return bool
214
     */
215
    private function isCartEmpty(Tree $tree): bool
216
    {
217
        $cart     = Session::get('cart');
218
        $cart     = is_array($cart) ? $cart : [];
219
        $contents = $cart[$tree->name()] ?? [];
220
221
        return $contents === [];
222
    }
223
224
    /**
225
     * @param ServerRequestInterface $request
226
     *
227
     * @return ResponseInterface
228
     */
229
    public function getDownloadFormAction(ServerRequestInterface $request): ResponseInterface
230
    {
231
        $tree = Validator::attributes($request)->tree();
232
233
        $title = I18N::translate('Family tree clippings cart') . ' — ' . I18N::translate('Download');
234
235
        $download_filenames = [
236
            'clippings'                  => 'clippings',
237
            'clippings-' . date('Y-m-d') => 'clippings-' . date('Y-m-d'),
238
        ];
239
240
        return $this->viewResponse('modules/clippings/download', [
241
            'download_filenames' => $download_filenames,
242
            'module'             => $this->name(),
243
            'title'              => $title,
244
            'tree'               => $tree,
245
            'zip_available'      => extension_loaded('zip'),
246
        ]);
247
    }
248
249
    /**
250
     * @param ServerRequestInterface $request
251
     *
252
     * @return ResponseInterface
253
     */
254
    public function postDownloadAction(ServerRequestInterface $request): ResponseInterface
255
    {
256
        $tree = Validator::attributes($request)->tree();
257
258
        if (Auth::isAdmin()) {
259
            $privacy_options = ['none', 'gedadmin', 'user', 'visitor'];
260
        } elseif (Auth::isManager($tree)) {
261
            $privacy_options = ['gedadmin', 'user', 'visitor'];
262
        } elseif (Auth::isMember($tree)) {
263
            $privacy_options = ['user', 'visitor'];
264
        } else {
265
            $privacy_options = ['visitor'];
266
        }
267
268
        $filename     = Validator::parsedBody($request)->string('filename');
269
        $format       = Validator::parsedBody($request)->isInArray(['gedcom', 'zip', 'zipmedia', 'gedzip'])->string('format');
270
        $privacy      = Validator::parsedBody($request)->isInArray($privacy_options)->string('privacy');
271
        $encoding     = Validator::parsedBody($request)->isInArray([UTF8::NAME, UTF16BE::NAME, ANSEL::NAME, ASCII::NAME, Windows1252::NAME])->string('encoding');
272
        $line_endings = Validator::parsedBody($request)->isInArray(['CRLF', 'LF'])->string('line_endings');
273
274
        $cart = Session::get('cart');
275
        $cart = is_array($cart) ? $cart : [];
276
277
        $xrefs = array_keys($cart[$tree->name()] ?? []);
278
        $xrefs = array_map('strval', $xrefs); // PHP converts numeric keys to integers.
279
280
        $records = new Collection();
281
282
        switch ($privacy) {
283
            case 'gedadmin':
284
                $access_level = Auth::PRIV_NONE;
285
                break;
286
            case 'user':
287
                $access_level = Auth::PRIV_USER;
288
                break;
289
            case 'visitor':
290
                $access_level = Auth::PRIV_PRIVATE;
291
                break;
292
            case 'none':
293
            default:
294
                $access_level = Auth::PRIV_HIDE;
295
                break;
296
        }
297
298
        foreach ($xrefs as $xref) {
299
            $object = Registry::gedcomRecordFactory()->make($xref, $tree);
300
            // The object may have been deleted since we added it to the cart....
301
            if ($object instanceof GedcomRecord) {
302
                $record = $object->privatizeGedcom($access_level);
303
                // Remove links to objects that aren't in the cart
304
                preg_match_all('/\n1 ' . Gedcom::REGEX_TAG . ' @(' . Gedcom::REGEX_XREF . ')@(\n[2-9].*)*/', $record, $matches, PREG_SET_ORDER);
305
                foreach ($matches as $match) {
306
                    if (!in_array($match[1], $xrefs, true)) {
307
                        $record = str_replace($match[0], '', $record);
308
                    }
309
                }
310
                preg_match_all('/\n2 ' . Gedcom::REGEX_TAG . ' @(' . Gedcom::REGEX_XREF . ')@(\n[3-9].*)*/', $record, $matches, PREG_SET_ORDER);
311
                foreach ($matches as $match) {
312
                    if (!in_array($match[1], $xrefs, true)) {
313
                        $record = str_replace($match[0], '', $record);
314
                    }
315
                }
316
                preg_match_all('/\n3 ' . Gedcom::REGEX_TAG . ' @(' . Gedcom::REGEX_XREF . ')@(\n[4-9].*)*/', $record, $matches, PREG_SET_ORDER);
317
                foreach ($matches as $match) {
318
                    if (!in_array($match[1], $xrefs, true)) {
319
                        $record = str_replace($match[0], '', $record);
320
                    }
321
                }
322
323
                $records->add($record);
0 ignored issues
show
Bug introduced by
$record of type string is incompatible with the type Illuminate\Support\TValue expected by parameter $item of Illuminate\Support\Collection::add(). ( Ignorable by Annotation )

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

323
                $records->add(/** @scrutinizer ignore-type */ $record);
Loading history...
324
            }
325
        }
326
327
        // We have already applied privacy filtering, so do not do it again.
328
        return $this->gedcom_export_service->downloadResponse($tree, false, $encoding, 'none', $line_endings, $filename, $format, $records);
329
    }
330
331
    /**
332
     * @param ServerRequestInterface $request
333
     *
334
     * @return ResponseInterface
335
     */
336
    public function getEmptyAction(ServerRequestInterface $request): ResponseInterface
337
    {
338
        $tree = Validator::attributes($request)->tree();
339
340
        $cart = Session::get('cart');
341
        $cart = is_array($cart) ? $cart : [];
342
343
        $cart[$tree->name()] = [];
344
        Session::put('cart', $cart);
345
346
        $url = route('module', [
347
            'module' => $this->name(),
348
            'action' => 'Show',
349
            'tree'   => $tree->name(),
350
        ]);
351
352
        return redirect($url);
353
    }
354
355
    /**
356
     * @param ServerRequestInterface $request
357
     *
358
     * @return ResponseInterface
359
     */
360
    public function postRemoveAction(ServerRequestInterface $request): ResponseInterface
361
    {
362
        $tree = Validator::attributes($request)->tree();
363
        $xref = Validator::queryParams($request)->isXref()->string('xref');
364
        $cart = Session::get('cart');
365
        $cart = is_array($cart) ? $cart : [];
366
367
        unset($cart[$tree->name()][$xref]);
368
        Session::put('cart', $cart);
369
370
        $url = route('module', [
371
            'module' => $this->name(),
372
            'action' => 'Show',
373
            'tree'   => $tree->name(),
374
        ]);
375
376
        return redirect($url);
377
    }
378
379
    /**
380
     * @param ServerRequestInterface $request
381
     *
382
     * @return ResponseInterface
383
     */
384
    public function getShowAction(ServerRequestInterface $request): ResponseInterface
385
    {
386
        $tree = Validator::attributes($request)->tree();
387
388
        return $this->viewResponse('modules/clippings/show', [
389
            'module'  => $this->name(),
390
            'records' => $this->allRecordsInCart($tree),
391
            'title'   => I18N::translate('Family tree clippings cart'),
392
            'tree'    => $tree,
393
        ]);
394
    }
395
396
    /**
397
     * Get all the records in the cart.
398
     *
399
     * @param Tree $tree
400
     *
401
     * @return array<GedcomRecord>
402
     */
403
    private function allRecordsInCart(Tree $tree): array
404
    {
405
        $cart = Session::get('cart');
406
        $cart = is_array($cart) ? $cart : [];
407
408
        $xrefs = array_keys($cart[$tree->name()] ?? []);
409
        $xrefs = array_map('strval', $xrefs); // PHP converts numeric keys to integers.
410
411
        // Fetch all the records in the cart.
412
        $records = array_map(static fn (string $xref): GedcomRecord|null => Registry::gedcomRecordFactory()->make($xref, $tree), $xrefs);
413
414
        // Some records may have been deleted after they were added to the cart.
415
        $records = array_filter($records);
416
417
        // Group and sort.
418
        uasort($records, static fn (GedcomRecord $x, GedcomRecord $y): int => $x->tag() <=> $y->tag() ?: GedcomRecord::nameComparator()($x, $y));
419
420
        return $records;
421
    }
422
423
    /**
424
     * @param ServerRequestInterface $request
425
     *
426
     * @return ResponseInterface
427
     */
428
    public function getAddFamilyAction(ServerRequestInterface $request): ResponseInterface
429
    {
430
        $tree   = Validator::attributes($request)->tree();
431
        $xref   = Validator::queryParams($request)->isXref()->string('xref');
432
        $family = Registry::familyFactory()->make($xref, $tree);
433
        $family = Auth::checkFamilyAccess($family);
434
        $name   = $family->fullName();
435
436
        $options = [
437
            self::ADD_RECORD_ONLY => $name,
438
            /* I18N: %s is a family (husband + wife) */
439
            self::ADD_CHILDREN    => I18N::translate('%s and their children', $name),
440
            /* I18N: %s is a family (husband + wife) */
441
            self::ADD_DESCENDANTS => I18N::translate('%s and their descendants', $name),
442
        ];
443
444
        $title = I18N::translate('Add %s to the clippings cart', $name);
445
446
        return $this->viewResponse('modules/clippings/add-options', [
447
            'options' => $options,
448
            'record'  => $family,
449
            'title'   => $title,
450
            'tree'    => $tree,
451
        ]);
452
    }
453
454
    /**
455
     * @param ServerRequestInterface $request
456
     *
457
     * @return ResponseInterface
458
     */
459
    public function postAddFamilyAction(ServerRequestInterface $request): ResponseInterface
460
    {
461
        $tree   = Validator::attributes($request)->tree();
462
        $xref   = Validator::parsedBody($request)->isXref()->string('xref');
463
        $option = Validator::parsedBody($request)->string('option');
464
465
        $family = Registry::familyFactory()->make($xref, $tree);
466
        $family = Auth::checkFamilyAccess($family);
467
468
        switch ($option) {
469
            case self::ADD_RECORD_ONLY:
470
                $this->addFamilyToCart($family);
471
                break;
472
473
            case self::ADD_CHILDREN:
474
                $this->addFamilyAndChildrenToCart($family);
475
                break;
476
477
            case self::ADD_DESCENDANTS:
478
                $this->addFamilyAndDescendantsToCart($family);
479
                break;
480
        }
481
482
        return redirect($family->url());
483
    }
484
485
    /**
486
     * @param Family $family
487
     *
488
     * @return void
489
     */
490
    protected function addFamilyAndChildrenToCart(Family $family): void
491
    {
492
        $this->addFamilyToCart($family);
493
494
        foreach ($family->children() as $child) {
495
            $this->addIndividualToCart($child);
496
        }
497
    }
498
499
    /**
500
     * @param Family $family
501
     *
502
     * @return void
503
     */
504
    protected function addFamilyAndDescendantsToCart(Family $family): void
505
    {
506
        $this->addFamilyAndChildrenToCart($family);
507
508
        foreach ($family->children() as $child) {
509
            foreach ($child->spouseFamilies() as $child_family) {
510
                $this->addFamilyAndDescendantsToCart($child_family);
511
            }
512
        }
513
    }
514
515
    /**
516
     * @param ServerRequestInterface $request
517
     *
518
     * @return ResponseInterface
519
     */
520
    public function getAddIndividualAction(ServerRequestInterface $request): ResponseInterface
521
    {
522
        $tree       = Validator::attributes($request)->tree();
523
        $xref       = Validator::queryParams($request)->isXref()->string('xref');
524
        $individual = Registry::individualFactory()->make($xref, $tree);
525
        $individual = Auth::checkIndividualAccess($individual);
526
        $name       = $individual->fullName();
527
528
        if ($individual->sex() === 'F') {
529
            $options = [
530
                self::ADD_RECORD_ONLY       => $name,
531
                self::ADD_PARENT_FAMILIES   => I18N::translate('%s, her parents and siblings', $name),
532
                self::ADD_SPOUSE_FAMILIES   => I18N::translate('%s, her spouses and children', $name),
533
                self::ADD_ANCESTORS         => I18N::translate('%s and her ancestors', $name),
534
                self::ADD_ANCESTOR_FAMILIES => I18N::translate('%s, her ancestors and their families', $name),
535
                self::ADD_DESCENDANTS       => I18N::translate('%s, her spouses and descendants', $name),
536
            ];
537
        } else {
538
            $options = [
539
                self::ADD_RECORD_ONLY       => $name,
540
                self::ADD_PARENT_FAMILIES   => I18N::translate('%s, his parents and siblings', $name),
541
                self::ADD_SPOUSE_FAMILIES   => I18N::translate('%s, his spouses and children', $name),
542
                self::ADD_ANCESTORS         => I18N::translate('%s and his ancestors', $name),
543
                self::ADD_ANCESTOR_FAMILIES => I18N::translate('%s, his ancestors and their families', $name),
544
                self::ADD_DESCENDANTS       => I18N::translate('%s, his spouses and descendants', $name),
545
            ];
546
        }
547
548
        $title = I18N::translate('Add %s to the clippings cart', $name);
549
550
        return $this->viewResponse('modules/clippings/add-options', [
551
            'options' => $options,
552
            'record'  => $individual,
553
            'title'   => $title,
554
            'tree'    => $tree,
555
        ]);
556
    }
557
558
    /**
559
     * @param ServerRequestInterface $request
560
     *
561
     * @return ResponseInterface
562
     */
563
    public function postAddIndividualAction(ServerRequestInterface $request): ResponseInterface
564
    {
565
        $tree   = Validator::attributes($request)->tree();
566
        $xref   = Validator::parsedBody($request)->isXref()->string('xref');
567
        $option = Validator::parsedBody($request)->string('option');
568
569
        $individual = Registry::individualFactory()->make($xref, $tree);
570
        $individual = Auth::checkIndividualAccess($individual);
571
572
        switch ($option) {
573
            case self::ADD_RECORD_ONLY:
574
                $this->addIndividualToCart($individual);
575
                break;
576
577
            case self::ADD_PARENT_FAMILIES:
578
                foreach ($individual->childFamilies() as $family) {
579
                    $this->addFamilyAndChildrenToCart($family);
580
                }
581
                break;
582
583
            case self::ADD_SPOUSE_FAMILIES:
584
                foreach ($individual->spouseFamilies() as $family) {
585
                    $this->addFamilyAndChildrenToCart($family);
586
                }
587
                break;
588
589
            case self::ADD_ANCESTORS:
590
                $this->addAncestorsToCart($individual);
591
                break;
592
593
            case self::ADD_ANCESTOR_FAMILIES:
594
                $this->addAncestorFamiliesToCart($individual);
595
                break;
596
597
            case self::ADD_DESCENDANTS:
598
                foreach ($individual->spouseFamilies() as $family) {
599
                    $this->addFamilyAndDescendantsToCart($family);
600
                }
601
                break;
602
        }
603
604
        return redirect($individual->url());
605
    }
606
607
    /**
608
     * @param Individual $individual
609
     *
610
     * @return void
611
     */
612
    protected function addAncestorsToCart(Individual $individual): void
613
    {
614
        $this->addIndividualToCart($individual);
615
616
        foreach ($individual->childFamilies() as $family) {
617
            $this->addFamilyToCart($family);
618
619
            foreach ($family->spouses() as $parent) {
620
                $this->addAncestorsToCart($parent);
621
            }
622
        }
623
    }
624
625
    /**
626
     * @param Individual $individual
627
     *
628
     * @return void
629
     */
630
    protected function addAncestorFamiliesToCart(Individual $individual): void
631
    {
632
        foreach ($individual->childFamilies() as $family) {
633
            $this->addFamilyAndChildrenToCart($family);
634
635
            foreach ($family->spouses() as $parent) {
636
                $this->addAncestorFamiliesToCart($parent);
637
            }
638
        }
639
    }
640
641
    /**
642
     * @param ServerRequestInterface $request
643
     *
644
     * @return ResponseInterface
645
     */
646
    public function getAddLocationAction(ServerRequestInterface $request): ResponseInterface
647
    {
648
        $tree     = Validator::attributes($request)->tree();
649
        $xref     = Validator::queryParams($request)->isXref()->string('xref');
650
        $location = Registry::locationFactory()->make($xref, $tree);
651
        $location = Auth::checkLocationAccess($location);
652
        $name     = $location->fullName();
653
654
        $options = [
655
            self::ADD_RECORD_ONLY => $name,
656
        ];
657
658
        $title = I18N::translate('Add %s to the clippings cart', $name);
659
660
        return $this->viewResponse('modules/clippings/add-options', [
661
            'options' => $options,
662
            'record'  => $location,
663
            'title'   => $title,
664
            'tree'    => $tree,
665
        ]);
666
    }
667
668
    /**
669
     * @param ServerRequestInterface $request
670
     *
671
     * @return ResponseInterface
672
     */
673
    public function postAddLocationAction(ServerRequestInterface $request): ResponseInterface
674
    {
675
        $tree     = Validator::attributes($request)->tree();
676
        $xref     = Validator::queryParams($request)->isXref()->string('xref');
677
        $location = Registry::locationFactory()->make($xref, $tree);
678
        $location = Auth::checkLocationAccess($location);
679
680
        $this->addLocationToCart($location);
681
682
        return redirect($location->url());
683
    }
684
685
    /**
686
     * @param ServerRequestInterface $request
687
     *
688
     * @return ResponseInterface
689
     */
690
    public function getAddMediaAction(ServerRequestInterface $request): ResponseInterface
691
    {
692
        $tree  = Validator::attributes($request)->tree();
693
        $xref  = Validator::queryParams($request)->isXref()->string('xref');
694
        $media = Registry::mediaFactory()->make($xref, $tree);
695
        $media = Auth::checkMediaAccess($media);
696
        $name  = $media->fullName();
697
698
        $options = [
699
            self::ADD_RECORD_ONLY => $name,
700
        ];
701
702
        $title = I18N::translate('Add %s to the clippings cart', $name);
703
704
        return $this->viewResponse('modules/clippings/add-options', [
705
            'options' => $options,
706
            'record'  => $media,
707
            'title'   => $title,
708
            'tree'    => $tree,
709
        ]);
710
    }
711
712
    /**
713
     * @param ServerRequestInterface $request
714
     *
715
     * @return ResponseInterface
716
     */
717
    public function postAddMediaAction(ServerRequestInterface $request): ResponseInterface
718
    {
719
        $tree  = Validator::attributes($request)->tree();
720
        $xref  = Validator::queryParams($request)->isXref()->string('xref');
721
        $media = Registry::mediaFactory()->make($xref, $tree);
722
        $media = Auth::checkMediaAccess($media);
723
724
        $this->addMediaToCart($media);
725
726
        return redirect($media->url());
727
    }
728
729
    /**
730
     * @param ServerRequestInterface $request
731
     *
732
     * @return ResponseInterface
733
     */
734
    public function getAddNoteAction(ServerRequestInterface $request): ResponseInterface
735
    {
736
        $tree = Validator::attributes($request)->tree();
737
        $xref = Validator::queryParams($request)->isXref()->string('xref');
738
        $note = Registry::noteFactory()->make($xref, $tree);
739
        $note = Auth::checkNoteAccess($note);
740
        $name = $note->fullName();
741
742
        $options = [
743
            self::ADD_RECORD_ONLY => $name,
744
        ];
745
746
        $title = I18N::translate('Add %s to the clippings cart', $name);
747
748
        return $this->viewResponse('modules/clippings/add-options', [
749
            'options' => $options,
750
            'record'  => $note,
751
            'title'   => $title,
752
            'tree'    => $tree,
753
        ]);
754
    }
755
756
    /**
757
     * @param ServerRequestInterface $request
758
     *
759
     * @return ResponseInterface
760
     */
761
    public function postAddNoteAction(ServerRequestInterface $request): ResponseInterface
762
    {
763
        $tree = Validator::attributes($request)->tree();
764
        $xref = Validator::queryParams($request)->isXref()->string('xref');
765
        $note = Registry::noteFactory()->make($xref, $tree);
766
        $note = Auth::checkNoteAccess($note);
767
768
        $this->addNoteToCart($note);
769
770
        return redirect($note->url());
771
    }
772
773
    /**
774
     * @param ServerRequestInterface $request
775
     *
776
     * @return ResponseInterface
777
     */
778
    public function getAddRepositoryAction(ServerRequestInterface $request): ResponseInterface
779
    {
780
        $tree       = Validator::attributes($request)->tree();
781
        $xref       = Validator::queryParams($request)->isXref()->string('xref');
782
        $repository = Registry::repositoryFactory()->make($xref, $tree);
783
        $repository = Auth::checkRepositoryAccess($repository);
784
        $name       = $repository->fullName();
785
786
        $options = [
787
            self::ADD_RECORD_ONLY => $name,
788
        ];
789
790
        $title = I18N::translate('Add %s to the clippings cart', $name);
791
792
        return $this->viewResponse('modules/clippings/add-options', [
793
            'options' => $options,
794
            'record'  => $repository,
795
            'title'   => $title,
796
            'tree'    => $tree,
797
        ]);
798
    }
799
800
    /**
801
     * @param ServerRequestInterface $request
802
     *
803
     * @return ResponseInterface
804
     */
805
    public function postAddRepositoryAction(ServerRequestInterface $request): ResponseInterface
806
    {
807
        $tree       = Validator::attributes($request)->tree();
808
        $xref       = Validator::queryParams($request)->isXref()->string('xref');
809
        $repository = Registry::repositoryFactory()->make($xref, $tree);
810
        $repository = Auth::checkRepositoryAccess($repository);
811
812
        $this->addRepositoryToCart($repository);
813
814
        foreach ($this->linked_record_service->linkedSources($repository) as $source) {
815
            $this->addSourceToCart($source);
816
        }
817
818
        return redirect($repository->url());
819
    }
820
821
    /**
822
     * @param ServerRequestInterface $request
823
     *
824
     * @return ResponseInterface
825
     */
826
    public function getAddSourceAction(ServerRequestInterface $request): ResponseInterface
827
    {
828
        $tree   = Validator::attributes($request)->tree();
829
        $xref   = Validator::queryParams($request)->isXref()->string('xref');
830
        $source = Registry::sourceFactory()->make($xref, $tree);
831
        $source = Auth::checkSourceAccess($source);
832
        $name   = $source->fullName();
833
834
        $options = [
835
            self::ADD_RECORD_ONLY        => $name,
836
            self::ADD_LINKED_INDIVIDUALS => I18N::translate('%s and the individuals that reference it.', $name),
837
        ];
838
839
        $title = I18N::translate('Add %s to the clippings cart', $name);
840
841
        return $this->viewResponse('modules/clippings/add-options', [
842
            'options' => $options,
843
            'record'  => $source,
844
            'title'   => $title,
845
            'tree'    => $tree,
846
        ]);
847
    }
848
849
    /**
850
     * @param ServerRequestInterface $request
851
     *
852
     * @return ResponseInterface
853
     */
854
    public function postAddSourceAction(ServerRequestInterface $request): ResponseInterface
855
    {
856
        $tree   = Validator::attributes($request)->tree();
857
        $xref   = Validator::parsedBody($request)->isXref()->string('xref');
858
        $option = Validator::parsedBody($request)->string('option');
859
860
        $source = Registry::sourceFactory()->make($xref, $tree);
861
        $source = Auth::checkSourceAccess($source);
862
863
        $this->addSourceToCart($source);
864
865
        if ($option === self::ADD_LINKED_INDIVIDUALS) {
866
            foreach ($this->linked_record_service->linkedIndividuals($source) as $individual) {
867
                $this->addIndividualToCart($individual);
868
            }
869
            foreach ($this->linked_record_service->linkedFamilies($source) as $family) {
870
                $this->addFamilyToCart($family);
871
            }
872
        }
873
874
        return redirect($source->url());
875
    }
876
877
    /**
878
     * @param ServerRequestInterface $request
879
     *
880
     * @return ResponseInterface
881
     */
882
    public function getAddSubmitterAction(ServerRequestInterface $request): ResponseInterface
883
    {
884
        $tree      = Validator::attributes($request)->tree();
885
        $xref      = Validator::queryParams($request)->isXref()->string('xref');
886
        $submitter = Registry::submitterFactory()->make($xref, $tree);
887
        $submitter = Auth::checkSubmitterAccess($submitter);
888
        $name      = $submitter->fullName();
889
890
        $options = [
891
            self::ADD_RECORD_ONLY => $name,
892
        ];
893
894
        $title = I18N::translate('Add %s to the clippings cart', $name);
895
896
        return $this->viewResponse('modules/clippings/add-options', [
897
            'options' => $options,
898
            'record'  => $submitter,
899
            'title'   => $title,
900
            'tree'    => $tree,
901
        ]);
902
    }
903
904
    /**
905
     * @param ServerRequestInterface $request
906
     *
907
     * @return ResponseInterface
908
     */
909
    public function postAddSubmitterAction(ServerRequestInterface $request): ResponseInterface
910
    {
911
        $tree      = Validator::attributes($request)->tree();
912
        $xref      = Validator::queryParams($request)->isXref()->string('xref');
913
        $submitter = Registry::submitterFactory()->make($xref, $tree);
914
        $submitter = Auth::checkSubmitterAccess($submitter);
915
916
        $this->addSubmitterToCart($submitter);
917
918
        return redirect($submitter->url());
919
    }
920
921
    /**
922
     * @param Family $family
923
     */
924
    protected function addFamilyToCart(Family $family): void
925
    {
926
        $cart = Session::get('cart');
927
        $cart = is_array($cart) ? $cart : [];
928
929
        $tree = $family->tree()->name();
930
        $xref = $family->xref();
931
932
        if (($cart[$tree][$xref] ?? false) === false) {
933
            $cart[$tree][$xref] = true;
934
935
            Session::put('cart', $cart);
936
937
            foreach ($family->spouses() as $spouse) {
938
                $this->addIndividualToCart($spouse);
939
            }
940
941
            $this->addLocationLinksToCart($family);
942
            $this->addMediaLinksToCart($family);
943
            $this->addNoteLinksToCart($family);
944
            $this->addSourceLinksToCart($family);
945
            $this->addSubmitterLinksToCart($family);
946
        }
947
    }
948
949
    /**
950
     * @param Individual $individual
951
     */
952
    protected function addIndividualToCart(Individual $individual): void
953
    {
954
        $cart = Session::get('cart');
955
        $cart = is_array($cart) ? $cart : [];
956
957
        $tree = $individual->tree()->name();
958
        $xref = $individual->xref();
959
960
        if (($cart[$tree][$xref] ?? false) === false) {
961
            $cart[$tree][$xref] = true;
962
963
            Session::put('cart', $cart);
964
965
            $this->addLocationLinksToCart($individual);
966
            $this->addMediaLinksToCart($individual);
967
            $this->addNoteLinksToCart($individual);
968
            $this->addSourceLinksToCart($individual);
969
        }
970
    }
971
972
    /**
973
     * @param Location $location
974
     */
975
    protected function addLocationToCart(Location $location): void
976
    {
977
        $cart = Session::get('cart');
978
        $cart = is_array($cart) ? $cart : [];
979
980
        $tree = $location->tree()->name();
981
        $xref = $location->xref();
982
983
        if (($cart[$tree][$xref] ?? false) === false) {
984
            $cart[$tree][$xref] = true;
985
986
            Session::put('cart', $cart);
987
988
            $this->addLocationLinksToCart($location);
989
            $this->addMediaLinksToCart($location);
990
            $this->addNoteLinksToCart($location);
991
            $this->addSourceLinksToCart($location);
992
        }
993
    }
994
995
    /**
996
     * @param GedcomRecord $record
997
     */
998
    protected function addLocationLinksToCart(GedcomRecord $record): void
999
    {
1000
        preg_match_all('/\n\d _LOC @(' . Gedcom::REGEX_XREF . ')@/', $record->gedcom(), $matches);
1001
1002
        foreach ($matches[1] as $xref) {
1003
            $location = Registry::locationFactory()->make($xref, $record->tree());
1004
1005
            if ($location instanceof Location && $location->canShow()) {
1006
                $this->addLocationToCart($location);
1007
            }
1008
        }
1009
    }
1010
1011
    /**
1012
     * @param Media $media
1013
     */
1014
    protected function addMediaToCart(Media $media): void
1015
    {
1016
        $cart = Session::get('cart');
1017
        $cart = is_array($cart) ? $cart : [];
1018
1019
        $tree = $media->tree()->name();
1020
        $xref = $media->xref();
1021
1022
        if (($cart[$tree][$xref] ?? false) === false) {
1023
            $cart[$tree][$xref] = true;
1024
1025
            Session::put('cart', $cart);
1026
1027
            $this->addNoteLinksToCart($media);
1028
        }
1029
    }
1030
1031
    /**
1032
     * @param GedcomRecord $record
1033
     */
1034
    protected function addMediaLinksToCart(GedcomRecord $record): void
1035
    {
1036
        preg_match_all('/\n\d OBJE @(' . Gedcom::REGEX_XREF . ')@/', $record->gedcom(), $matches);
1037
1038
        foreach ($matches[1] as $xref) {
1039
            $media = Registry::mediaFactory()->make($xref, $record->tree());
1040
1041
            if ($media instanceof Media && $media->canShow()) {
1042
                $this->addMediaToCart($media);
1043
            }
1044
        }
1045
    }
1046
1047
    /**
1048
     * @param Note $note
1049
     */
1050
    protected function addNoteToCart(Note $note): void
1051
    {
1052
        $cart = Session::get('cart');
1053
        $cart = is_array($cart) ? $cart : [];
1054
1055
        $tree = $note->tree()->name();
1056
        $xref = $note->xref();
1057
1058
        if (($cart[$tree][$xref] ?? false) === false) {
1059
            $cart[$tree][$xref] = true;
1060
1061
            Session::put('cart', $cart);
1062
        }
1063
    }
1064
1065
    /**
1066
     * @param GedcomRecord $record
1067
     */
1068
    protected function addNoteLinksToCart(GedcomRecord $record): void
1069
    {
1070
        preg_match_all('/\n\d NOTE @(' . Gedcom::REGEX_XREF . ')@/', $record->gedcom(), $matches);
1071
1072
        foreach ($matches[1] as $xref) {
1073
            $note = Registry::noteFactory()->make($xref, $record->tree());
1074
1075
            if ($note instanceof Note && $note->canShow()) {
1076
                $this->addNoteToCart($note);
1077
            }
1078
        }
1079
    }
1080
1081
    /**
1082
     * @param Source $source
1083
     */
1084
    protected function addSourceToCart(Source $source): void
1085
    {
1086
        $cart = Session::get('cart');
1087
        $cart = is_array($cart) ? $cart : [];
1088
1089
        $tree = $source->tree()->name();
1090
        $xref = $source->xref();
1091
1092
        if (($cart[$tree][$xref] ?? false) === false) {
1093
            $cart[$tree][$xref] = true;
1094
1095
            Session::put('cart', $cart);
1096
1097
            $this->addNoteLinksToCart($source);
1098
            $this->addRepositoryLinksToCart($source);
1099
        }
1100
    }
1101
1102
    /**
1103
     * @param GedcomRecord $record
1104
     */
1105
    protected function addSourceLinksToCart(GedcomRecord $record): void
1106
    {
1107
        preg_match_all('/\n\d SOUR @(' . Gedcom::REGEX_XREF . ')@/', $record->gedcom(), $matches);
1108
1109
        foreach ($matches[1] as $xref) {
1110
            $source = Registry::sourceFactory()->make($xref, $record->tree());
1111
1112
            if ($source instanceof Source && $source->canShow()) {
1113
                $this->addSourceToCart($source);
1114
            }
1115
        }
1116
    }
1117
1118
    /**
1119
     * @param Repository $repository
1120
     */
1121
    protected function addRepositoryToCart(Repository $repository): void
1122
    {
1123
        $cart = Session::get('cart');
1124
        $cart = is_array($cart) ? $cart : [];
1125
1126
        $tree = $repository->tree()->name();
1127
        $xref = $repository->xref();
1128
1129
        if (($cart[$tree][$xref] ?? false) === false) {
1130
            $cart[$tree][$xref] = true;
1131
1132
            Session::put('cart', $cart);
1133
1134
            $this->addNoteLinksToCart($repository);
1135
        }
1136
    }
1137
1138
    /**
1139
     * @param GedcomRecord $record
1140
     */
1141
    protected function addRepositoryLinksToCart(GedcomRecord $record): void
1142
    {
1143
        preg_match_all('/\n\d REPO @(' . Gedcom::REGEX_XREF . '@)/', $record->gedcom(), $matches);
1144
1145
        foreach ($matches[1] as $xref) {
1146
            $repository = Registry::repositoryFactory()->make($xref, $record->tree());
1147
1148
            if ($repository instanceof Repository && $repository->canShow()) {
1149
                $this->addRepositoryToCart($repository);
1150
            }
1151
        }
1152
    }
1153
1154
    /**
1155
     * @param Submitter $submitter
1156
     */
1157
    protected function addSubmitterToCart(Submitter $submitter): void
1158
    {
1159
        $cart = Session::get('cart');
1160
        $cart = is_array($cart) ? $cart : [];
1161
        $tree = $submitter->tree()->name();
1162
        $xref = $submitter->xref();
1163
1164
        if (($cart[$tree][$xref] ?? false) === false) {
1165
            $cart[$tree][$xref] = true;
1166
1167
            Session::put('cart', $cart);
1168
1169
            $this->addNoteLinksToCart($submitter);
1170
        }
1171
    }
1172
1173
    /**
1174
     * @param GedcomRecord $record
1175
     */
1176
    protected function addSubmitterLinksToCart(GedcomRecord $record): void
1177
    {
1178
        preg_match_all('/\n\d SUBM @(' . Gedcom::REGEX_XREF . ')@/', $record->gedcom(), $matches);
1179
1180
        foreach ($matches[1] as $xref) {
1181
            $submitter = Registry::submitterFactory()->make($xref, $record->tree());
1182
1183
            if ($submitter instanceof Submitter && $submitter->canShow()) {
1184
                $this->addSubmitterToCart($submitter);
1185
            }
1186
        }
1187
    }
1188
}
1189