Completed
Push — master ( ac6993...e8ded2 )
by Greg
05:38
created

ClippingsCartModule::postDownloadAction()   F

Complexity

Conditions 30
Paths > 20000

Size

Total Lines 141
Code Lines 84

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 30
eloc 84
c 1
b 0
f 0
nc 52480
nop 1
dl 0
loc 141
rs 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * webtrees: online genealogy
5
 * Copyright (C) 2021 webtrees development team
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU General Public License for more details.
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
 */
17
18
declare(strict_types=1);
19
20
namespace Fisharebest\Webtrees\Module;
21
22
use Aura\Router\Route;
23
use Fisharebest\Webtrees\Auth;
24
use Fisharebest\Webtrees\Family;
25
use Fisharebest\Webtrees\Gedcom;
26
use Fisharebest\Webtrees\GedcomRecord;
27
use Fisharebest\Webtrees\Http\RequestHandlers\FamilyPage;
28
use Fisharebest\Webtrees\Http\RequestHandlers\IndividualPage;
29
use Fisharebest\Webtrees\Http\RequestHandlers\LocationPage;
30
use Fisharebest\Webtrees\Http\RequestHandlers\MediaPage;
31
use Fisharebest\Webtrees\Http\RequestHandlers\NotePage;
32
use Fisharebest\Webtrees\Http\RequestHandlers\RepositoryPage;
33
use Fisharebest\Webtrees\Http\RequestHandlers\SourcePage;
34
use Fisharebest\Webtrees\Http\RequestHandlers\SubmitterPage;
35
use Fisharebest\Webtrees\I18N;
36
use Fisharebest\Webtrees\Individual;
37
use Fisharebest\Webtrees\Location;
38
use Fisharebest\Webtrees\Media;
39
use Fisharebest\Webtrees\Menu;
40
use Fisharebest\Webtrees\Note;
41
use Fisharebest\Webtrees\Registry;
42
use Fisharebest\Webtrees\Repository;
43
use Fisharebest\Webtrees\Services\GedcomExportService;
44
use Fisharebest\Webtrees\Services\UserService;
45
use Fisharebest\Webtrees\Session;
46
use Fisharebest\Webtrees\Source;
47
use Fisharebest\Webtrees\Submitter;
48
use Fisharebest\Webtrees\Tree;
49
use Illuminate\Support\Collection;
50
use League\Flysystem\Filesystem;
51
use League\Flysystem\ZipArchive\ZipArchiveAdapter;
52
use Psr\Http\Message\ResponseFactoryInterface;
53
use Psr\Http\Message\ResponseInterface;
54
use Psr\Http\Message\ServerRequestInterface;
55
use Psr\Http\Message\StreamFactoryInterface;
56
use RuntimeException;
57
58
use function app;
59
use function array_filter;
60
use function array_keys;
61
use function array_map;
62
use function array_search;
63
use function assert;
64
use function fopen;
65
use function in_array;
66
use function is_string;
67
use function preg_match_all;
68
use function redirect;
69
use function rewind;
70
use function route;
71
use function str_replace;
72
use function stream_get_meta_data;
73
use function tmpfile;
74
use function uasort;
75
use function view;
76
77
use const PREG_SET_ORDER;
78
79
/**
80
 * Class ClippingsCartModule
81
 */
82
class ClippingsCartModule extends AbstractModule implements ModuleMenuInterface
83
{
84
    use ModuleMenuTrait;
85
86
    // Routes that have a record which can be added to the clipboard
87
    private const ROUTES_WITH_RECORDS = [
88
        'Family'     => FamilyPage::class,
89
        'Individual' => IndividualPage::class,
90
        'Media'      => MediaPage::class,
91
        'Location'   => LocationPage::class,
92
        'Note'       => NotePage::class,
93
        'Repository' => RepositoryPage::class,
94
        'Source'     => SourcePage::class,
95
        'Submitter'  => SubmitterPage::class,
96
    ];
97
98
    /** @var int The default access level for this module.  It can be changed in the control panel. */
99
    protected $access_level = Auth::PRIV_USER;
100
101
    /** @var GedcomExportService */
102
    private $gedcom_export_service;
103
104
    /** @var UserService */
105
    private $user_service;
106
107
    /**
108
     * ClippingsCartModule constructor.
109
     *
110
     * @param GedcomExportService $gedcom_export_service
111
     * @param UserService         $user_service
112
     */
113
    public function __construct(GedcomExportService $gedcom_export_service, UserService $user_service)
114
    {
115
        $this->gedcom_export_service = $gedcom_export_service;
116
        $this->user_service          = $user_service;
117
    }
118
119
    /**
120
     * A sentence describing what this module does.
121
     *
122
     * @return string
123
     */
124
    public function description(): string
125
    {
126
        /* I18N: Description of the “Clippings cart” module */
127
        return I18N::translate('Select records from your family tree and save them as a GEDCOM file.');
128
    }
129
130
    /**
131
     * The default position for this menu.  It can be changed in the control panel.
132
     *
133
     * @return int
134
     */
135
    public function defaultMenuOrder(): int
136
    {
137
        return 6;
138
    }
139
140
    /**
141
     * A menu, to be added to the main application menu.
142
     *
143
     * @param Tree $tree
144
     *
145
     * @return Menu|null
146
     */
147
    public function getMenu(Tree $tree): ?Menu
148
    {
149
        /** @var ServerRequestInterface $request */
150
        $request = app(ServerRequestInterface::class);
151
152
        $route = $request->getAttribute('route');
153
        assert($route instanceof Route);
154
155
        $cart  = Session::get('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
        $contents = $cart[$tree->name()] ?? [];
219
220
        return $contents === [];
221
    }
222
223
    /**
224
     * @param ServerRequestInterface $request
225
     *
226
     * @return ResponseInterface
227
     */
228
    public function getDownloadFormAction(ServerRequestInterface $request): ResponseInterface
229
    {
230
        $tree = $request->getAttribute('tree');
231
        assert($tree instanceof Tree);
232
233
        $user  = $request->getAttribute('user');
234
        $title = I18N::translate('Family tree clippings cart') . ' — ' . I18N::translate('Download');
235
236
        return $this->viewResponse('modules/clippings/download', [
237
            'is_manager' => Auth::isManager($tree, $user),
238
            'is_member'  => Auth::isMember($tree, $user),
239
            'module'     => $this->name(),
240
            'title'      => $title,
241
            'tree'       => $tree,
242
        ]);
243
    }
244
245
    /**
246
     * @param ServerRequestInterface $request
247
     *
248
     * @return ResponseInterface
249
     */
250
    public function postDownloadAction(ServerRequestInterface $request): ResponseInterface
251
    {
252
        $tree = $request->getAttribute('tree');
253
        assert($tree instanceof Tree);
254
255
        $data_filesystem = Registry::filesystem()->data();
256
257
        $params = (array) $request->getParsedBody();
258
259
        $privatize_export = $params['privatize_export'] ?? 'none';
260
261
        if ($privatize_export === 'none' && !Auth::isManager($tree)) {
262
            $privatize_export = 'member';
263
        }
264
265
        if ($privatize_export === 'gedadmin' && !Auth::isManager($tree)) {
266
            $privatize_export = 'member';
267
        }
268
269
        if ($privatize_export === 'user' && !Auth::isMember($tree)) {
270
            $privatize_export = 'visitor';
271
        }
272
273
        $convert = (bool) ($params['convert'] ?? false);
274
275
        $cart = Session::get('cart', []);
276
277
        $xrefs = array_keys($cart[$tree->name()] ?? []);
278
        $xrefs = array_map('strval', $xrefs); // PHP converts numeric keys to integers.
279
280
        // Create a new/empty .ZIP file
281
        $temp_zip_file  = stream_get_meta_data(tmpfile())['uri'];
282
        $zip_adapter    = new ZipArchiveAdapter($temp_zip_file);
283
        $zip_filesystem = new Filesystem($zip_adapter);
284
285
        $media_filesystem = $tree->mediaFilesystem($data_filesystem);
286
287
        // Media file prefix
288
        $path = $tree->getPreference('MEDIA_DIRECTORY');
289
290
        $encoding = $convert ? 'ANSI' : 'UTF-8';
291
292
        $records = new Collection();
293
294
        switch ($privatize_export) {
295
            case 'gedadmin':
296
                $access_level = Auth::PRIV_NONE;
297
                break;
298
            case 'user':
299
                $access_level = Auth::PRIV_USER;
300
                break;
301
            case 'visitor':
302
                $access_level = Auth::PRIV_PRIVATE;
303
                break;
304
            case 'none':
305
            default:
306
                $access_level = Auth::PRIV_HIDE;
307
                break;
308
        }
309
310
        foreach ($xrefs as $xref) {
311
            $object = Registry::gedcomRecordFactory()->make($xref, $tree);
312
            // The object may have been deleted since we added it to the cart....
313
            if ($object instanceof GedcomRecord) {
314
                $record = $object->privatizeGedcom($access_level);
315
                // Remove links to objects that aren't in the cart
316
                preg_match_all('/\n1 ' . Gedcom::REGEX_TAG . ' @(' . Gedcom::REGEX_XREF . ')@(\n[2-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
                preg_match_all('/\n2 ' . Gedcom::REGEX_TAG . ' @(' . Gedcom::REGEX_XREF . ')@(\n[3-9].*)*/', $record, $matches, PREG_SET_ORDER);
323
                foreach ($matches as $match) {
324
                    if (!in_array($match[1], $xrefs, true)) {
325
                        $record = str_replace($match[0], '', $record);
326
                    }
327
                }
328
                preg_match_all('/\n3 ' . Gedcom::REGEX_TAG . ' @(' . Gedcom::REGEX_XREF . ')@(\n[4-9].*)*/', $record, $matches, PREG_SET_ORDER);
329
                foreach ($matches as $match) {
330
                    if (!in_array($match[1], $xrefs, true)) {
331
                        $record = str_replace($match[0], '', $record);
332
                    }
333
                }
334
335
                if ($object instanceof Individual || $object instanceof Family) {
336
                    $records->add($record . "\n1 SOUR @WEBTREES@\n2 PAGE " . $object->url());
337
                } elseif ($object instanceof Source) {
338
                    $records->add($record . "\n1 NOTE " . $object->url());
339
                } elseif ($object instanceof Media) {
340
                    // Add the media files to the archive
341
                    foreach ($object->mediaFiles() as $media_file) {
342
                        $from = $media_file->filename();
343
                        $to   = $path . $media_file->filename();
344
                        if (!$media_file->isExternal() && $media_filesystem->has($from) && !$zip_filesystem->has($to)) {
345
                            $zip_filesystem->writeStream($to, $media_filesystem->readStream($from));
346
                        }
347
                    }
348
                    $records->add($record);
349
                } else {
350
                    $records->add($record);
351
                }
352
            }
353
        }
354
355
        $base_url = $request->getAttribute('base_url');
356
357
        // Create a source, to indicate the source of the data.
358
        $record = "0 @WEBTREES@ SOUR\n1 TITL " . $base_url;
359
        $author = $this->user_service->find((int) $tree->getPreference('CONTACT_USER_ID'));
360
        if ($author !== null) {
361
            $record .= "\n1 AUTH " . $author->realName();
362
        }
363
        $records->add($record);
364
365
        $stream = fopen('php://temp', 'wb+');
366
367
        if ($stream === false) {
368
            throw new RuntimeException('Failed to create temporary stream');
369
        }
370
371
        // We have already applied privacy filtering, so do not do it again.
372
        $this->gedcom_export_service->export($tree, $stream, false, $encoding, Auth::PRIV_HIDE, $path, $records);
373
        rewind($stream);
374
375
        // Finally add the GEDCOM file to the .ZIP file.
376
        $zip_filesystem->writeStream('clippings.ged', $stream);
377
378
        // Need to force-close ZipArchive filesystems.
379
        $zip_adapter->getArchive()->close();
380
381
        // Use a stream, so that we do not have to load the entire file into memory.
382
        $stream = app(StreamFactoryInterface::class)->createStreamFromFile($temp_zip_file);
383
384
        /** @var ResponseFactoryInterface $response_factory */
385
        $response_factory = app(ResponseFactoryInterface::class);
386
387
        return $response_factory->createResponse()
388
            ->withBody($stream)
389
            ->withHeader('Content-Type', 'application/zip')
390
            ->withHeader('Content-Disposition', 'attachment; filename="clippings.zip');
391
    }
392
393
    /**
394
     * @param ServerRequestInterface $request
395
     *
396
     * @return ResponseInterface
397
     */
398
    public function getEmptyAction(ServerRequestInterface $request): ResponseInterface
399
    {
400
        $tree = $request->getAttribute('tree');
401
        assert($tree instanceof Tree);
402
403
        $cart                = Session::get('cart', []);
404
        $cart[$tree->name()] = [];
405
        Session::put('cart', $cart);
406
407
        $url = route('module', [
408
            'module' => $this->name(),
409
            'action' => 'Show',
410
            'tree'   => $tree->name(),
411
        ]);
412
413
        return redirect($url);
414
    }
415
416
    /**
417
     * @param ServerRequestInterface $request
418
     *
419
     * @return ResponseInterface
420
     */
421
    public function postRemoveAction(ServerRequestInterface $request): ResponseInterface
422
    {
423
        $tree = $request->getAttribute('tree');
424
        assert($tree instanceof Tree);
425
426
        $xref = $request->getQueryParams()['xref'] ?? '';
427
428
        $cart = Session::get('cart', []);
429
        unset($cart[$tree->name()][$xref]);
430
        Session::put('cart', $cart);
431
432
        $url = route('module', [
433
            'module' => $this->name(),
434
            'action' => 'Show',
435
            'tree'   => $tree->name(),
436
        ]);
437
438
        return redirect($url);
439
    }
440
441
    /**
442
     * @param ServerRequestInterface $request
443
     *
444
     * @return ResponseInterface
445
     */
446
    public function getShowAction(ServerRequestInterface $request): ResponseInterface
447
    {
448
        $tree = $request->getAttribute('tree');
449
        assert($tree instanceof Tree);
450
451
        return $this->viewResponse('modules/clippings/show', [
452
            'module'  => $this->name(),
453
            'records' => $this->allRecordsInCart($tree),
454
            'title'   => I18N::translate('Family tree clippings cart'),
455
            'tree'    => $tree,
456
        ]);
457
    }
458
459
    /**
460
     * Get all the records in the cart.
461
     *
462
     * @param Tree $tree
463
     *
464
     * @return GedcomRecord[]
465
     */
466
    private function allRecordsInCart(Tree $tree): array
467
    {
468
        $cart = Session::get('cart', []);
469
470
        $xrefs = array_keys($cart[$tree->name()] ?? []);
471
        $xrefs = array_map('strval', $xrefs); // PHP converts numeric keys to integers.
472
473
        // Fetch all the records in the cart.
474
        $records = array_map(static function (string $xref) use ($tree): ?GedcomRecord {
475
            return Registry::gedcomRecordFactory()->make($xref, $tree);
476
        }, $xrefs);
477
478
        // Some records may have been deleted after they were added to the cart.
479
        $records = array_filter($records);
480
481
        // Group and sort.
482
        uasort($records, static function (GedcomRecord $x, GedcomRecord $y): int {
483
            return $x->tag() <=> $y->tag() ?: GedcomRecord::nameComparator()($x, $y);
484
        });
485
486
        return $records;
487
    }
488
489
    /**
490
     * @param ServerRequestInterface $request
491
     *
492
     * @return ResponseInterface
493
     */
494
    public function getAddFamilyAction(ServerRequestInterface $request): ResponseInterface
495
    {
496
        $tree = $request->getAttribute('tree');
497
        assert($tree instanceof Tree);
498
499
        $xref = $request->getQueryParams()['xref'] ?? '';
500
501
        $family = Registry::familyFactory()->make($xref, $tree);
502
        $family = Auth::checkFamilyAccess($family);
503
        $name   = $family->fullName();
504
505
        $options = [
506
            'record'      => $name,
507
            /* I18N: %s is a family (husband + wife) */
508
            'members'     => I18N::translate('%s and their children', $name),
509
            /* I18N: %s is a family (husband + wife) */
510
            'descendants' => I18N::translate('%s and their descendants', $name),
511
        ];
512
513
        $title = I18N::translate('Add %s to the clippings cart', $name);
514
515
        return $this->viewResponse('modules/clippings/add-options', [
516
            'options' => $options,
517
            'record'  => $family,
518
            'title'   => $title,
519
            'tree'    => $tree,
520
        ]);
521
    }
522
523
    /**
524
     * @param ServerRequestInterface $request
525
     *
526
     * @return ResponseInterface
527
     */
528
    public function postAddFamilyAction(ServerRequestInterface $request): ResponseInterface
529
    {
530
        $tree = $request->getAttribute('tree');
531
        assert($tree instanceof Tree);
532
533
        $params = (array) $request->getParsedBody();
534
535
        $xref   = $params['xref'] ?? '';
536
        $option = $params['option'] ?? '';
537
538
        $family = Registry::familyFactory()->make($xref, $tree);
539
        $family = Auth::checkFamilyAccess($family);
540
541
        switch ($option) {
542
            case 'self':
543
                $this->addFamilyToCart($family);
544
                break;
545
546
            case 'members':
547
                $this->addFamilyAndChildrenToCart($family);
548
                break;
549
550
            case 'descendants':
551
                $this->addFamilyAndDescendantsToCart($family);
552
                break;
553
        }
554
555
        return redirect($family->url());
556
    }
557
558
559
    /**
560
     * @param Family $family
561
     *
562
     * @return void
563
     */
564
    protected function addFamilyAndChildrenToCart(Family $family): void
565
    {
566
        $this->addFamilyToCart($family);
567
568
        foreach ($family->children() as $child) {
569
            $this->addIndividualToCart($child);
570
        }
571
    }
572
573
    /**
574
     * @param Family $family
575
     *
576
     * @return void
577
     */
578
    protected function addFamilyAndDescendantsToCart(Family $family): void
579
    {
580
        $this->addFamilyAndChildrenToCart($family);
581
582
        foreach ($family->children() as $child) {
583
            foreach ($child->spouseFamilies() as $child_family) {
584
                $this->addFamilyAndDescendantsToCart($child_family);
585
            }
586
        }
587
    }
588
589
    /**
590
     * @param ServerRequestInterface $request
591
     *
592
     * @return ResponseInterface
593
     */
594
    public function getAddIndividualAction(ServerRequestInterface $request): ResponseInterface
595
    {
596
        $tree = $request->getAttribute('tree');
597
        assert($tree instanceof Tree);
598
599
        $xref = $request->getQueryParams()['xref'] ?? '';
600
601
        $individual = Registry::individualFactory()->make($xref, $tree);
602
        $individual = Auth::checkIndividualAccess($individual);
603
        $name       = $individual->fullName();
604
605
        if ($individual->sex() === 'F') {
606
            $options = [
607
                'record'            => $name,
608
                'parents'           => I18N::translate('%s, her parents and siblings', $name),
609
                'spouses'           => I18N::translate('%s, her spouses and children', $name),
610
                'ancestors'         => I18N::translate('%s and her ancestors', $name),
611
                'ancestor_families' => I18N::translate('%s, her ancestors and their families', $name),
612
                'descendants'       => I18N::translate('%s, her spouses and descendants', $name),
613
            ];
614
        } else {
615
            $options = [
616
                'record'            => $name,
617
                'parents'           => I18N::translate('%s, his parents and siblings', $name),
618
                'spouses'           => I18N::translate('%s, his spouses and children', $name),
619
                'ancestors'         => I18N::translate('%s and his ancestors', $name),
620
                'ancestor_families' => I18N::translate('%s, his ancestors and their families', $name),
621
                'descendants'       => I18N::translate('%s, his spouses and descendants', $name),
622
            ];
623
        }
624
625
        $title = I18N::translate('Add %s to the clippings cart', $name);
626
627
        return $this->viewResponse('modules/clippings/add-options', [
628
            'options' => $options,
629
            'record'  => $individual,
630
            'title'   => $title,
631
            'tree'    => $tree,
632
        ]);
633
    }
634
635
    /**
636
     * @param ServerRequestInterface $request
637
     *
638
     * @return ResponseInterface
639
     */
640
    public function postAddIndividualAction(ServerRequestInterface $request): ResponseInterface
641
    {
642
        $tree = $request->getAttribute('tree');
643
        assert($tree instanceof Tree);
644
645
        $params = (array) $request->getParsedBody();
646
647
        $xref   = $params['xref'] ?? '';
648
        $option = $params['option'] ?? '';
649
650
        $individual = Registry::individualFactory()->make($xref, $tree);
651
        $individual = Auth::checkIndividualAccess($individual);
652
653
        switch ($option) {
654
            case 'self':
655
                $this->addIndividualToCart($individual);
656
                break;
657
658
            case 'parents':
659
                foreach ($individual->childFamilies() as $family) {
660
                    $this->addFamilyAndChildrenToCart($family);
661
                }
662
                break;
663
664
            case 'spouses':
665
                foreach ($individual->spouseFamilies() as $family) {
666
                    $this->addFamilyAndChildrenToCart($family);
667
                }
668
                break;
669
670
            case 'ancestors':
671
                $this->addAncestorsToCart($individual);
672
                break;
673
674
            case 'ancestor_families':
675
                $this->addAncestorFamiliesToCart($individual);
676
                break;
677
678
            case 'descendants':
679
                foreach ($individual->spouseFamilies() as $family) {
680
                    $this->addFamilyAndDescendantsToCart($family);
681
                }
682
                break;
683
        }
684
685
        return redirect($individual->url());
686
    }
687
688
    /**
689
     * @param Individual $individual
690
     *
691
     * @return void
692
     */
693
    protected function addAncestorsToCart(Individual $individual): void
694
    {
695
        $this->addIndividualToCart($individual);
696
697
        foreach ($individual->childFamilies() as $family) {
698
            $this->addFamilyToCart($family);
699
700
            foreach ($family->spouses() as $parent) {
701
                $this->addAncestorsToCart($parent);
702
            }
703
        }
704
    }
705
706
    /**
707
     * @param Individual $individual
708
     *
709
     * @return void
710
     */
711
    protected function addAncestorFamiliesToCart(Individual $individual): void
712
    {
713
        foreach ($individual->childFamilies() as $family) {
714
            $this->addFamilyAndChildrenToCart($family);
715
716
            foreach ($family->spouses() as $parent) {
717
                $this->addAncestorFamiliesToCart($parent);
718
            }
719
        }
720
    }
721
722
    /**
723
     * @param ServerRequestInterface $request
724
     *
725
     * @return ResponseInterface
726
     */
727
    public function getAddLocationAction(ServerRequestInterface $request): ResponseInterface
728
    {
729
        $tree = $request->getAttribute('tree');
730
        assert($tree instanceof Tree);
731
732
        $xref = $request->getQueryParams()['xref'] ?? '';
733
734
        $location = Registry::locationFactory()->make($xref, $tree);
735
        $location = Auth::checkLocationAccess($location);
736
        $name     = $location->fullName();
737
738
        $options = [
739
            'record' => $name,
740
        ];
741
742
        $title = I18N::translate('Add %s to the clippings cart', $name);
743
744
        return $this->viewResponse('modules/clippings/add-options', [
745
            'options' => $options,
746
            'record'  => $location,
747
            'title'   => $title,
748
            'tree'    => $tree,
749
        ]);
750
    }
751
752
    /**
753
     * @param ServerRequestInterface $request
754
     *
755
     * @return ResponseInterface
756
     */
757
    public function postAddLocationAction(ServerRequestInterface $request): ResponseInterface
758
    {
759
        $tree = $request->getAttribute('tree');
760
        assert($tree instanceof Tree);
761
762
        $xref = $request->getQueryParams()['xref'] ?? '';
763
764
        $location = Registry::locationFactory()->make($xref, $tree);
765
        $location = Auth::checkLocationAccess($location);
766
767
        $this->addLocationToCart($location);
768
769
        return redirect($location->url());
770
    }
771
772
    /**
773
     * @param ServerRequestInterface $request
774
     *
775
     * @return ResponseInterface
776
     */
777
    public function getAddMediaAction(ServerRequestInterface $request): ResponseInterface
778
    {
779
        $tree = $request->getAttribute('tree');
780
        assert($tree instanceof Tree);
781
782
        $xref = $request->getQueryParams()['xref'] ?? '';
783
784
        $media = Registry::mediaFactory()->make($xref, $tree);
785
        $media = Auth::checkMediaAccess($media);
786
        $name  = $media->fullName();
787
788
        $options = [
789
            'record' => $name,
790
        ];
791
792
        $title = I18N::translate('Add %s to the clippings cart', $name);
793
794
        return $this->viewResponse('modules/clippings/add-options', [
795
            'options' => $options,
796
            'record'  => $media,
797
            'title'   => $title,
798
            'tree'    => $tree,
799
        ]);
800
    }
801
802
    /**
803
     * @param ServerRequestInterface $request
804
     *
805
     * @return ResponseInterface
806
     */
807
    public function postAddMediaAction(ServerRequestInterface $request): ResponseInterface
808
    {
809
        $tree = $request->getAttribute('tree');
810
        assert($tree instanceof Tree);
811
812
        $xref = $request->getQueryParams()['xref'] ?? '';
813
814
        $media = Registry::mediaFactory()->make($xref, $tree);
815
        $media = Auth::checkMediaAccess($media);
816
817
        $this->addMediaToCart($media);
818
819
        return redirect($media->url());
820
    }
821
822
    /**
823
     * @param ServerRequestInterface $request
824
     *
825
     * @return ResponseInterface
826
     */
827
    public function getAddNoteAction(ServerRequestInterface $request): ResponseInterface
828
    {
829
        $tree = $request->getAttribute('tree');
830
        assert($tree instanceof Tree);
831
832
        $xref = $request->getQueryParams()['xref'] ?? '';
833
834
        $note = Registry::noteFactory()->make($xref, $tree);
835
        $note = Auth::checkNoteAccess($note);
836
        $name = $note->fullName();
837
838
        $options = [
839
            'record' => $name,
840
        ];
841
842
        $title = I18N::translate('Add %s to the clippings cart', $name);
843
844
        return $this->viewResponse('modules/clippings/add-options', [
845
            'options' => $options,
846
            'record'  => $note,
847
            'title'   => $title,
848
            'tree'    => $tree,
849
        ]);
850
    }
851
852
    /**
853
     * @param ServerRequestInterface $request
854
     *
855
     * @return ResponseInterface
856
     */
857
    public function postAddNoteAction(ServerRequestInterface $request): ResponseInterface
858
    {
859
        $tree = $request->getAttribute('tree');
860
        assert($tree instanceof Tree);
861
862
        $xref = $request->getQueryParams()['xref'] ?? '';
863
864
        $note = Registry::noteFactory()->make($xref, $tree);
865
        $note = Auth::checkNoteAccess($note);
866
867
        $this->addNoteToCart($note);
868
869
        return redirect($note->url());
870
    }
871
872
    /**
873
     * @param ServerRequestInterface $request
874
     *
875
     * @return ResponseInterface
876
     */
877
    public function getAddRepositoryAction(ServerRequestInterface $request): ResponseInterface
878
    {
879
        $tree = $request->getAttribute('tree');
880
        assert($tree instanceof Tree);
881
882
        $xref = $request->getQueryParams()['xref'] ?? '';
883
884
        $repository = Registry::repositoryFactory()->make($xref, $tree);
885
        $repository = Auth::checkRepositoryAccess($repository);
886
        $name       = $repository->fullName();
887
888
        $options = [
889
            'record' => $name,
890
        ];
891
892
        $title = I18N::translate('Add %s to the clippings cart', $name);
893
894
        return $this->viewResponse('modules/clippings/add-options', [
895
            'options' => $options,
896
            'record'  => $repository,
897
            'title'   => $title,
898
            'tree'    => $tree,
899
        ]);
900
    }
901
902
    /**
903
     * @param ServerRequestInterface $request
904
     *
905
     * @return ResponseInterface
906
     */
907
    public function postAddRepositoryAction(ServerRequestInterface $request): ResponseInterface
908
    {
909
        $tree = $request->getAttribute('tree');
910
        assert($tree instanceof Tree);
911
912
        $xref = $request->getQueryParams()['xref'] ?? '';
913
914
        $repository = Registry::repositoryFactory()->make($xref, $tree);
915
        $repository = Auth::checkRepositoryAccess($repository);
916
917
        $this->addRepositoryToCart($repository);
918
919
        foreach ($repository->linkedSources('REPO') as $source) {
920
            $this->addSourceToCart($source);
921
        }
922
923
        return redirect($repository->url());
924
    }
925
926
    /**
927
     * @param ServerRequestInterface $request
928
     *
929
     * @return ResponseInterface
930
     */
931
    public function getAddSourceAction(ServerRequestInterface $request): ResponseInterface
932
    {
933
        $tree = $request->getAttribute('tree');
934
        assert($tree instanceof Tree);
935
936
        $xref = $request->getQueryParams()['xref'] ?? '';
937
938
        $source = Registry::sourceFactory()->make($xref, $tree);
939
        $source = Auth::checkSourceAccess($source);
940
        $name   = $source->fullName();
941
942
        $options = [
943
            'record' => $name,
944
            'linked' => I18N::translate('%s and the individuals that reference it.', $name),
945
        ];
946
947
        $title = I18N::translate('Add %s to the clippings cart', $name);
948
949
        return $this->viewResponse('modules/clippings/add-options', [
950
            'options' => $options,
951
            'record'  => $source,
952
            'title'   => $title,
953
            'tree'    => $tree,
954
        ]);
955
    }
956
957
    /**
958
     * @param ServerRequestInterface $request
959
     *
960
     * @return ResponseInterface
961
     */
962
    public function postAddSourceAction(ServerRequestInterface $request): ResponseInterface
963
    {
964
        $tree = $request->getAttribute('tree');
965
        assert($tree instanceof Tree);
966
967
        $params = (array) $request->getParsedBody();
968
969
        $xref   = $params['xref'] ?? '';
970
        $option = $params['option'] ?? '';
971
972
        $source = Registry::sourceFactory()->make($xref, $tree);
973
        $source = Auth::checkSourceAccess($source);
974
975
        $this->addSourceToCart($source);
976
977
        if ($option === 'linked') {
978
            foreach ($source->linkedIndividuals('SOUR') as $individual) {
979
                $this->addIndividualToCart($individual);
980
            }
981
            foreach ($source->linkedFamilies('SOUR') as $family) {
982
                $this->addFamilyToCart($family);
983
            }
984
        }
985
986
        return redirect($source->url());
987
    }
988
989
    /**
990
     * @param ServerRequestInterface $request
991
     *
992
     * @return ResponseInterface
993
     */
994
    public function getAddSubmitterAction(ServerRequestInterface $request): ResponseInterface
995
    {
996
        $tree = $request->getAttribute('tree');
997
        assert($tree instanceof Tree);
998
999
        $xref = $request->getQueryParams()['xref'] ?? '';
1000
1001
        $submitter = Registry::submitterFactory()->make($xref, $tree);
1002
        $submitter = Auth::checkSubmitterAccess($submitter);
1003
        $name      = $submitter->fullName();
1004
1005
        $options = [
1006
            'record' => $name,
1007
        ];
1008
1009
        $title = I18N::translate('Add %s to the clippings cart', $name);
1010
1011
        return $this->viewResponse('modules/clippings/add-options', [
1012
            'options' => $options,
1013
            'record'  => $submitter,
1014
            'title'   => $title,
1015
            'tree'    => $tree,
1016
        ]);
1017
    }
1018
1019
    /**
1020
     * @param ServerRequestInterface $request
1021
     *
1022
     * @return ResponseInterface
1023
     */
1024
    public function postAddSubmitterAction(ServerRequestInterface $request): ResponseInterface
1025
    {
1026
        $tree = $request->getAttribute('tree');
1027
        assert($tree instanceof Tree);
1028
1029
        $xref = $request->getQueryParams()['xref'] ?? '';
1030
1031
        $submitter = Registry::submitterFactory()->make($xref, $tree);
1032
        $submitter = Auth::checkSubmitterAccess($submitter);
1033
1034
        $this->addSubmitterToCart($submitter);
1035
1036
        return redirect($submitter->url());
1037
    }
1038
1039
    /**
1040
     * @param Family $family
1041
     */
1042
    protected function addFamilyToCart(Family $family): void
1043
    {
1044
        $cart = Session::get('cart', []);
1045
        $tree = $family->tree()->name();
1046
        $xref = $family->xref();
1047
1048
        if (($cart[$tree][$xref] ?? false) === false) {
1049
            $cart[$tree][$xref] = true;
1050
1051
            Session::put('cart', $cart);
1052
1053
            foreach ($family->spouses() as $spouse) {
1054
                $this->addIndividualToCart($spouse);
1055
            }
1056
1057
            $this->addLocationLinksToCart($family);
1058
            $this->addMediaLinksToCart($family);
1059
            $this->addNoteLinksToCart($family);
1060
            $this->addSourceLinksToCart($family);
1061
            $this->addSubmitterLinksToCart($family);
1062
        }
1063
    }
1064
1065
    /**
1066
     * @param Individual $individual
1067
     */
1068
    protected function addIndividualToCart(Individual $individual): void
1069
    {
1070
        $cart = Session::get('cart', []);
1071
        $tree = $individual->tree()->name();
1072
        $xref = $individual->xref();
1073
1074
        if (($cart[$tree][$xref] ?? false) === false) {
1075
            $cart[$tree][$xref] = true;
1076
1077
            Session::put('cart', $cart);
1078
1079
            $this->addLocationLinksToCart($individual);
1080
            $this->addMediaLinksToCart($individual);
1081
            $this->addNoteLinksToCart($individual);
1082
            $this->addSourceLinksToCart($individual);
1083
        }
1084
    }
1085
1086
    /**
1087
     * @param Location $location
1088
     */
1089
    protected function addLocationToCart(Location $location): void
1090
    {
1091
        $cart = Session::get('cart', []);
1092
        $tree = $location->tree()->name();
1093
        $xref = $location->xref();
1094
1095
        if (($cart[$tree][$xref] ?? false) === false) {
1096
            $cart[$tree][$xref] = true;
1097
1098
            Session::put('cart', $cart);
1099
1100
            $this->addLocationLinksToCart($location);
1101
            $this->addMediaLinksToCart($location);
1102
            $this->addNoteLinksToCart($location);
1103
            $this->addSourceLinksToCart($location);
1104
        }
1105
    }
1106
1107
    /**
1108
     * @param GedcomRecord $record
1109
     */
1110
    protected function addLocationLinksToCart(GedcomRecord $record): void
1111
    {
1112
        preg_match_all('/\n\d _LOC @(' . Gedcom::REGEX_XREF . ')@/', $record->gedcom(), $matches);
1113
1114
        foreach ($matches[1] as $xref) {
1115
            $location = Registry::locationFactory()->make($xref, $record->tree());
1116
1117
            if ($location instanceof Location && $location->canShow()) {
1118
                $this->addLocationToCart($location);
1119
            }
1120
        }
1121
    }
1122
1123
    /**
1124
     * @param Media $media
1125
     */
1126
    protected function addMediaToCart(Media $media): void
1127
    {
1128
        $cart = Session::get('cart', []);
1129
        $tree = $media->tree()->name();
1130
        $xref = $media->xref();
1131
1132
        if (($cart[$tree][$xref] ?? false) === false) {
1133
            $cart[$tree][$xref] = true;
1134
1135
            Session::put('cart', $cart);
1136
1137
            $this->addNoteLinksToCart($media);
1138
        }
1139
    }
1140
1141
    /**
1142
     * @param GedcomRecord $record
1143
     */
1144
    protected function addMediaLinksToCart(GedcomRecord $record): void
1145
    {
1146
        preg_match_all('/\n\d OBJE @(' . Gedcom::REGEX_XREF . ')@/', $record->gedcom(), $matches);
1147
1148
        foreach ($matches[1] as $xref) {
1149
            $media = Registry::mediaFactory()->make($xref, $record->tree());
1150
1151
            if ($media instanceof Media && $media->canShow()) {
1152
                $this->addMediaToCart($media);
1153
            }
1154
        }
1155
    }
1156
1157
    /**
1158
     * @param Note $note
1159
     */
1160
    protected function addNoteToCart(Note $note): void
1161
    {
1162
        $cart = Session::get('cart', []);
1163
        $tree = $note->tree()->name();
1164
        $xref = $note->xref();
1165
1166
        if (($cart[$tree][$xref] ?? false) === false) {
1167
            $cart[$tree][$xref] = true;
1168
1169
            Session::put('cart', $cart);
1170
        }
1171
    }
1172
1173
    /**
1174
     * @param GedcomRecord $record
1175
     */
1176
    protected function addNoteLinksToCart(GedcomRecord $record): void
1177
    {
1178
        preg_match_all('/\n\d NOTE @(' . Gedcom::REGEX_XREF . ')@/', $record->gedcom(), $matches);
1179
1180
        foreach ($matches[1] as $xref) {
1181
            $note = Registry::noteFactory()->make($xref, $record->tree());
1182
1183
            if ($note instanceof Note && $note->canShow()) {
1184
                $this->addNoteToCart($note);
1185
            }
1186
        }
1187
    }
1188
1189
    /**
1190
     * @param Source $source
1191
     */
1192
    protected function addSourceToCart(Source $source): void
1193
    {
1194
        $cart = Session::get('cart', []);
1195
        $tree = $source->tree()->name();
1196
        $xref = $source->xref();
1197
1198
        if (($cart[$tree][$xref] ?? false) === false) {
1199
            $cart[$tree][$xref] = true;
1200
1201
            Session::put('cart', $cart);
1202
1203
            $this->addNoteLinksToCart($source);
1204
            $this->addRepositoryLinksToCart($source);
1205
        }
1206
    }
1207
1208
    /**
1209
     * @param GedcomRecord $record
1210
     */
1211
    protected function addSourceLinksToCart(GedcomRecord $record): void
1212
    {
1213
        preg_match_all('/\n\d SOUR @(' . Gedcom::REGEX_XREF . ')@/', $record->gedcom(), $matches);
1214
1215
        foreach ($matches[1] as $xref) {
1216
            $source = Registry::sourceFactory()->make($xref, $record->tree());
1217
1218
            if ($source instanceof Source && $source->canShow()) {
1219
                $this->addSourceToCart($source);
1220
            }
1221
        }
1222
    }
1223
1224
    /**
1225
     * @param Repository $repository
1226
     */
1227
    protected function addRepositoryToCart(Repository $repository): void
1228
    {
1229
        $cart = Session::get('cart', []);
1230
        $tree = $repository->tree()->name();
1231
        $xref = $repository->xref();
1232
1233
        if (($cart[$tree][$xref] ?? false) === false) {
1234
            $cart[$tree][$xref] = true;
1235
1236
            Session::put('cart', $cart);
1237
1238
            $this->addNoteLinksToCart($repository);
1239
        }
1240
    }
1241
1242
    /**
1243
     * @param GedcomRecord $record
1244
     */
1245
    protected function addRepositoryLinksToCart(GedcomRecord $record): void
1246
    {
1247
        preg_match_all('/\n\d REPO @(' . Gedcom::REGEX_XREF . '@)/', $record->gedcom(), $matches);
1248
1249
        foreach ($matches[1] as $xref) {
1250
            $repository = Registry::repositoryFactory()->make($xref, $record->tree());
1251
1252
            if ($repository instanceof Repository && $repository->canShow()) {
1253
                $this->addRepositoryToCart($repository);
1254
            }
1255
        }
1256
    }
1257
1258
    /**
1259
     * @param Submitter $submitter
1260
     */
1261
    protected function addSubmitterToCart(Submitter $submitter): void
1262
    {
1263
        $cart = Session::get('cart', []);
1264
        $tree = $submitter->tree()->name();
1265
        $xref = $submitter->xref();
1266
1267
        if (($cart[$tree][$xref] ?? false) === false) {
1268
            $cart[$tree][$xref] = true;
1269
1270
            Session::put('cart', $cart);
1271
1272
            $this->addNoteLinksToCart($submitter);
1273
        }
1274
    }
1275
1276
    /**
1277
     * @param GedcomRecord $record
1278
     */
1279
    protected function addSubmitterLinksToCart(GedcomRecord $record): void
1280
    {
1281
        preg_match_all('/\n\d SUBM @(' . Gedcom::REGEX_XREF . ')@/', $record->gedcom(), $matches);
1282
1283
        foreach ($matches[1] as $xref) {
1284
            $submitter = Registry::submitterFactory()->make($xref, $record->tree());
1285
1286
            if ($submitter instanceof Submitter && $submitter->canShow()) {
1287
                $this->addSubmitterToCart($submitter);
1288
            }
1289
        }
1290
    }
1291
}
1292