Issues (2559)

app/Module/ClippingsCartModule.php (1 issue)

Labels
Severity
1
<?php
2
3
/**
4
 * webtrees: online genealogy
5
 * Copyright (C) 2025 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\Services\PhpService;
50
use Fisharebest\Webtrees\Session;
51
use Fisharebest\Webtrees\Source;
52
use Fisharebest\Webtrees\Submitter;
53
use Fisharebest\Webtrees\Tree;
54
use Fisharebest\Webtrees\Validator;
55
use Illuminate\Support\Collection;
56
use Psr\Http\Message\ResponseInterface;
57
use Psr\Http\Message\ServerRequestInterface;
58
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 count;
65
use function date;
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
class ClippingsCartModule extends AbstractModule implements ModuleMenuInterface
79
{
80
    use ModuleMenuTrait;
81
82
    // What to add to the cart?
83
    private const string ADD_RECORD_ONLY        = 'record';
0 ignored issues
show
A parse error occurred: Syntax error, unexpected T_STRING, expecting '=' on line 83 at column 25
Loading history...
84
    private const string ADD_CHILDREN           = 'children';
85
    private const string ADD_DESCENDANTS        = 'descendants';
86
    private const string ADD_PARENT_FAMILIES    = 'parents';
87
    private const string ADD_SPOUSE_FAMILIES    = 'spouses';
88
    private const string ADD_ANCESTORS          = 'ancestors';
89
    private const string ADD_ANCESTOR_FAMILIES  = 'families';
90
    private const string ADD_LINKED_INDIVIDUALS = 'linked';
91
92
    // Routes that have a record which can be added to the clipboard
93
    private const array ROUTES_WITH_RECORDS = [
94
        'Family'     => FamilyPage::class,
95
        'Individual' => IndividualPage::class,
96
        'Media'      => MediaPage::class,
97
        'Location'   => LocationPage::class,
98
        'Note'       => NotePage::class,
99
        'Repository' => RepositoryPage::class,
100
        'Source'     => SourcePage::class,
101
        'Submitter'  => SubmitterPage::class,
102
    ];
103
104
    /** @var int The default access level for this module.  It can be changed in the control panel. */
105
    protected int $access_level = Auth::PRIV_USER;
106
107
    public function __construct(
108
        private GedcomExportService $gedcom_export_service,
109
        private LinkedRecordService $linked_record_service,
110
        private PhpService $php_service,
111
    ) {
112
    }
113
114
    public function description(): string
115
    {
116
        /* I18N: Description of the “Clippings cart” module */
117
        return I18N::translate('Select records from your family tree and save them as a GEDCOM file.');
118
    }
119
120
    public function defaultMenuOrder(): int
121
    {
122
        return 6;
123
    }
124
125
    public function getMenu(Tree $tree): Menu|null
126
    {
127
        $request = Registry::container()->get(ServerRequestInterface::class);
128
        $route   = Validator::attributes($request)->route();
129
        $cart    = Session::get('cart');
130
        $cart    = is_array($cart) ? $cart : [];
131
        $count   = count($cart[$tree->name()] ?? []);
132
        $badge   = view('components/badge', ['count' => $count]);
133
134
        $submenus = [
135
            new Menu($this->title() . ' ' . $badge, route('module', [
136
                'module' => $this->name(),
137
                'action' => 'Show',
138
                'tree'   => $tree->name(),
139
            ]), 'menu-clippings-cart', ['rel' => 'nofollow']),
140
        ];
141
142
        $action = array_search($route->name, self::ROUTES_WITH_RECORDS, true);
143
        if ($action !== false) {
144
            $xref = $route->attributes['xref'];
145
            assert(is_string($xref));
146
147
            $add_route = route('module', [
148
                'module' => $this->name(),
149
                'action' => 'Add' . $action,
150
                'xref'   => $xref,
151
                'tree'   => $tree->name(),
152
            ]);
153
154
            $submenus[] = new Menu(I18N::translate('Add to the clippings cart'), $add_route, 'menu-clippings-add', ['rel' => 'nofollow']);
155
        }
156
157
        if (!$this->isCartEmpty($tree)) {
158
            $submenus[] = new Menu(I18N::translate('Empty the clippings cart'), route('module', [
159
                'module' => $this->name(),
160
                'action' => 'Empty',
161
                'tree'   => $tree->name(),
162
            ]), 'menu-clippings-empty', ['rel' => 'nofollow']);
163
164
            $submenus[] = new Menu(I18N::translate('Download'), route('module', [
165
                'module' => $this->name(),
166
                'action' => 'DownloadForm',
167
                'tree'   => $tree->name(),
168
            ]), 'menu-clippings-download', ['rel' => 'nofollow']);
169
        }
170
171
        return new Menu($this->title(), '#', 'menu-clippings', ['rel' => 'nofollow'], $submenus);
172
    }
173
174
    public function title(): string
175
    {
176
        /* I18N: Name of a module */
177
        return I18N::translate('Clippings cart');
178
    }
179
180
    private function isCartEmpty(Tree $tree): bool
181
    {
182
        $cart     = Session::get('cart');
183
        $cart     = is_array($cart) ? $cart : [];
184
        $contents = $cart[$tree->name()] ?? [];
185
186
        return $contents === [];
187
    }
188
189
    public function getDownloadFormAction(ServerRequestInterface $request): ResponseInterface
190
    {
191
        $tree = Validator::attributes($request)->tree();
192
193
        $title = I18N::translate('Family tree clippings cart') . ' — ' . I18N::translate('Download');
194
195
        $download_filenames = [
196
            'clippings'                  => 'clippings',
197
            'clippings-' . date('Y-m-d') => 'clippings-' . date('Y-m-d'),
198
        ];
199
200
        return $this->viewResponse('modules/clippings/download', [
201
            'download_filenames' => $download_filenames,
202
            'module'             => $this->name(),
203
            'title'              => $title,
204
            'tree'               => $tree,
205
            'zip_available'      => $this->php_service->extensionLoaded(extension: 'zip'),
206
        ]);
207
    }
208
209
    public function postDownloadAction(ServerRequestInterface $request): ResponseInterface
210
    {
211
        $tree = Validator::attributes($request)->tree();
212
213
        if (Auth::isAdmin()) {
214
            $privacy_options = ['none', 'gedadmin', 'user', 'visitor'];
215
        } elseif (Auth::isManager($tree)) {
216
            $privacy_options = ['gedadmin', 'user', 'visitor'];
217
        } elseif (Auth::isMember($tree)) {
218
            $privacy_options = ['user', 'visitor'];
219
        } else {
220
            $privacy_options = ['visitor'];
221
        }
222
223
        $filename     = Validator::parsedBody($request)->string('filename');
224
        $format       = Validator::parsedBody($request)->isInArray(['gedcom', 'zip', 'zipmedia', 'gedzip'])->string('format');
225
        $privacy      = Validator::parsedBody($request)->isInArray($privacy_options)->string('privacy');
226
        $encoding     = Validator::parsedBody($request)->isInArray([UTF8::NAME, UTF16BE::NAME, ANSEL::NAME, ASCII::NAME, Windows1252::NAME])->string('encoding');
227
        $line_endings = Validator::parsedBody($request)->isInArray(['CRLF', 'LF'])->string('line_endings');
228
229
        $cart = Session::get('cart');
230
        $cart = is_array($cart) ? $cart : [];
231
232
        $xrefs = array_keys($cart[$tree->name()] ?? []);
233
        $xrefs = array_map('strval', $xrefs); // PHP converts numeric keys to integers.
234
235
        $records = new Collection();
236
237
        switch ($privacy) {
238
            case 'gedadmin':
239
                $access_level = Auth::PRIV_NONE;
240
                break;
241
            case 'user':
242
                $access_level = Auth::PRIV_USER;
243
                break;
244
            case 'visitor':
245
                $access_level = Auth::PRIV_PRIVATE;
246
                break;
247
            case 'none':
248
            default:
249
                $access_level = Auth::PRIV_HIDE;
250
                break;
251
        }
252
253
        foreach ($xrefs as $xref) {
254
            $object = Registry::gedcomRecordFactory()->make($xref, $tree);
255
            // The object may have been deleted since we added it to the cart....
256
            if ($object instanceof GedcomRecord && $object->canShow($access_level)) {
257
                $gedcom = $object->privatizeGedcom($access_level);
258
259
                // Remove links to objects that aren't in the cart
260
                $patterns = [
261
                    '/\n1 ' . Gedcom::REGEX_TAG . ' @(' . Gedcom::REGEX_XREF . ')@(?:\n[2-9].*)*/',
262
                    '/\n2 ' . Gedcom::REGEX_TAG . ' @(' . Gedcom::REGEX_XREF . ')@(?:\n[3-9].*)*/',
263
                    '/\n3 ' . Gedcom::REGEX_TAG . ' @(' . Gedcom::REGEX_XREF . ')@(?:\n[4-9].*)*/',
264
                    '/\n4 ' . Gedcom::REGEX_TAG . ' @(' . Gedcom::REGEX_XREF . ')@(?:\n[5-9].*)*/',
265
                ];
266
267
                foreach ($patterns as $pattern) {
268
                    preg_match_all($pattern, $gedcom, $matches, PREG_SET_ORDER);
269
270
                    foreach ($matches as $match) {
271
                        if (!in_array($match[1], $xrefs, true)) {
272
                            // Remove the reference to any object that isn't in the cart
273
                            $gedcom = str_replace($match[0], '', $gedcom);
274
                        }
275
                    }
276
                }
277
278
                $records->add($gedcom);
279
            }
280
        }
281
282
        // We have already applied privacy filtering, so do not do it again.
283
        return $this->gedcom_export_service->downloadResponse($tree, false, $encoding, 'none', $line_endings, $filename, $format, $records);
284
    }
285
286
    public function getEmptyAction(ServerRequestInterface $request): ResponseInterface
287
    {
288
        $tree = Validator::attributes($request)->tree();
289
290
        $cart = Session::get('cart');
291
        $cart = is_array($cart) ? $cart : [];
292
293
        $cart[$tree->name()] = [];
294
        Session::put('cart', $cart);
295
296
        $url = route('module', [
297
            'module' => $this->name(),
298
            'action' => 'Show',
299
            'tree'   => $tree->name(),
300
        ]);
301
302
        return redirect($url);
303
    }
304
305
    public function postRemoveAction(ServerRequestInterface $request): ResponseInterface
306
    {
307
        $tree = Validator::attributes($request)->tree();
308
        $xref = Validator::queryParams($request)->isXref()->string('xref');
309
        $cart = Session::get('cart');
310
        $cart = is_array($cart) ? $cart : [];
311
312
        unset($cart[$tree->name()][$xref]);
313
        Session::put('cart', $cart);
314
315
        $url = route('module', [
316
            'module' => $this->name(),
317
            'action' => 'Show',
318
            'tree'   => $tree->name(),
319
        ]);
320
321
        return redirect($url);
322
    }
323
324
    public function getShowAction(ServerRequestInterface $request): ResponseInterface
325
    {
326
        $tree = Validator::attributes($request)->tree();
327
328
        return $this->viewResponse('modules/clippings/show', [
329
            'module'  => $this->name(),
330
            'records' => $this->allRecordsInCart($tree),
331
            'title'   => I18N::translate('Family tree clippings cart'),
332
            'tree'    => $tree,
333
        ]);
334
    }
335
336
    /**
337
     * @return array<GedcomRecord>
338
     */
339
    private function allRecordsInCart(Tree $tree): array
340
    {
341
        $cart = Session::get('cart');
342
        $cart = is_array($cart) ? $cart : [];
343
344
        $xrefs = array_keys($cart[$tree->name()] ?? []);
345
        $xrefs = array_map('strval', $xrefs); // PHP converts numeric keys to integers.
346
347
        // Fetch all the records in the cart.
348
        $records = array_map(static fn (string $xref): GedcomRecord|null => Registry::gedcomRecordFactory()->make($xref, $tree), $xrefs);
349
350
        // Some records may have been deleted after they were added to the cart.
351
        $records = array_filter($records);
352
353
        // Group and sort.
354
        uasort($records, static fn (GedcomRecord $x, GedcomRecord $y): int => $x->tag() <=> $y->tag() ?: GedcomRecord::nameComparator()($x, $y));
355
356
        return $records;
357
    }
358
359
    public function getAddFamilyAction(ServerRequestInterface $request): ResponseInterface
360
    {
361
        $tree   = Validator::attributes($request)->tree();
362
        $xref   = Validator::queryParams($request)->isXref()->string('xref');
363
        $family = Registry::familyFactory()->make($xref, $tree);
364
        $family = Auth::checkFamilyAccess($family);
365
        $name   = $family->fullName();
366
367
        $options = [
368
            self::ADD_RECORD_ONLY => $name,
369
            /* I18N: %s is a family (husband + wife) */
370
            self::ADD_CHILDREN    => I18N::translate('%s and their children', $name),
371
            /* I18N: %s is a family (husband + wife) */
372
            self::ADD_DESCENDANTS => I18N::translate('%s and their descendants', $name),
373
        ];
374
375
        $title = I18N::translate('Add %s to the clippings cart', $name);
376
377
        return $this->viewResponse('modules/clippings/add-options', [
378
            'options' => $options,
379
            'record'  => $family,
380
            'title'   => $title,
381
            'tree'    => $tree,
382
        ]);
383
    }
384
385
    public function postAddFamilyAction(ServerRequestInterface $request): ResponseInterface
386
    {
387
        $tree   = Validator::attributes($request)->tree();
388
        $xref   = Validator::parsedBody($request)->isXref()->string('xref');
389
        $option = Validator::parsedBody($request)->string('option');
390
391
        $family = Registry::familyFactory()->make($xref, $tree);
392
        $family = Auth::checkFamilyAccess($family);
393
394
        switch ($option) {
395
            case self::ADD_RECORD_ONLY:
396
                $this->addFamilyToCart($family);
397
                break;
398
399
            case self::ADD_CHILDREN:
400
                $this->addFamilyAndChildrenToCart($family);
401
                break;
402
403
            case self::ADD_DESCENDANTS:
404
                $this->addFamilyAndDescendantsToCart($family);
405
                break;
406
        }
407
408
        return redirect($family->url());
409
    }
410
411
    protected function addFamilyAndChildrenToCart(Family $family): void
412
    {
413
        $this->addFamilyToCart($family);
414
415
        foreach ($family->children() as $child) {
416
            $this->addIndividualToCart($child);
417
        }
418
    }
419
420
    protected function addFamilyAndDescendantsToCart(Family $family): void
421
    {
422
        $this->addFamilyAndChildrenToCart($family);
423
424
        foreach ($family->children() as $child) {
425
            foreach ($child->spouseFamilies() as $child_family) {
426
                $this->addFamilyAndDescendantsToCart($child_family);
427
            }
428
        }
429
    }
430
431
    public function getAddIndividualAction(ServerRequestInterface $request): ResponseInterface
432
    {
433
        $tree       = Validator::attributes($request)->tree();
434
        $xref       = Validator::queryParams($request)->isXref()->string('xref');
435
        $individual = Registry::individualFactory()->make($xref, $tree);
436
        $individual = Auth::checkIndividualAccess($individual);
437
        $name       = $individual->fullName();
438
439
        if ($individual->sex() === 'F') {
440
            $options = [
441
                self::ADD_RECORD_ONLY       => $name,
442
                self::ADD_PARENT_FAMILIES   => I18N::translate('%s, her parents and siblings', $name),
443
                self::ADD_SPOUSE_FAMILIES   => I18N::translate('%s, her spouses and children', $name),
444
                self::ADD_ANCESTORS         => I18N::translate('%s and her ancestors', $name),
445
                self::ADD_ANCESTOR_FAMILIES => I18N::translate('%s, her ancestors and their families', $name),
446
                self::ADD_DESCENDANTS       => I18N::translate('%s, her spouses and descendants', $name),
447
            ];
448
        } else {
449
            $options = [
450
                self::ADD_RECORD_ONLY       => $name,
451
                self::ADD_PARENT_FAMILIES   => I18N::translate('%s, his parents and siblings', $name),
452
                self::ADD_SPOUSE_FAMILIES   => I18N::translate('%s, his spouses and children', $name),
453
                self::ADD_ANCESTORS         => I18N::translate('%s and his ancestors', $name),
454
                self::ADD_ANCESTOR_FAMILIES => I18N::translate('%s, his ancestors and their families', $name),
455
                self::ADD_DESCENDANTS       => I18N::translate('%s, his spouses and descendants', $name),
456
            ];
457
        }
458
459
        $title = I18N::translate('Add %s to the clippings cart', $name);
460
461
        return $this->viewResponse('modules/clippings/add-options', [
462
            'options' => $options,
463
            'record'  => $individual,
464
            'title'   => $title,
465
            'tree'    => $tree,
466
        ]);
467
    }
468
469
    public function postAddIndividualAction(ServerRequestInterface $request): ResponseInterface
470
    {
471
        $tree   = Validator::attributes($request)->tree();
472
        $xref   = Validator::parsedBody($request)->isXref()->string('xref');
473
        $option = Validator::parsedBody($request)->string('option');
474
475
        $individual = Registry::individualFactory()->make($xref, $tree);
476
        $individual = Auth::checkIndividualAccess($individual);
477
478
        switch ($option) {
479
            case self::ADD_RECORD_ONLY:
480
                $this->addIndividualToCart($individual);
481
                break;
482
483
            case self::ADD_PARENT_FAMILIES:
484
                foreach ($individual->childFamilies() as $family) {
485
                    $this->addFamilyAndChildrenToCart($family);
486
                }
487
                break;
488
489
            case self::ADD_SPOUSE_FAMILIES:
490
                foreach ($individual->spouseFamilies() as $family) {
491
                    $this->addFamilyAndChildrenToCart($family);
492
                }
493
                break;
494
495
            case self::ADD_ANCESTORS:
496
                $this->addAncestorsToCart($individual);
497
                break;
498
499
            case self::ADD_ANCESTOR_FAMILIES:
500
                $this->addAncestorFamiliesToCart($individual);
501
                break;
502
503
            case self::ADD_DESCENDANTS:
504
                foreach ($individual->spouseFamilies() as $family) {
505
                    $this->addFamilyAndDescendantsToCart($family);
506
                }
507
                break;
508
        }
509
510
        return redirect($individual->url());
511
    }
512
513
    protected function addAncestorsToCart(Individual $individual): void
514
    {
515
        $this->addIndividualToCart($individual);
516
517
        foreach ($individual->childFamilies() as $family) {
518
            $this->addFamilyToCart($family);
519
520
            foreach ($family->spouses() as $parent) {
521
                $this->addAncestorsToCart($parent);
522
            }
523
        }
524
    }
525
526
    protected function addAncestorFamiliesToCart(Individual $individual): void
527
    {
528
        foreach ($individual->childFamilies() as $family) {
529
            $this->addFamilyAndChildrenToCart($family);
530
531
            foreach ($family->spouses() as $parent) {
532
                $this->addAncestorFamiliesToCart($parent);
533
            }
534
        }
535
    }
536
537
    public function getAddLocationAction(ServerRequestInterface $request): ResponseInterface
538
    {
539
        $tree     = Validator::attributes($request)->tree();
540
        $xref     = Validator::queryParams($request)->isXref()->string('xref');
541
        $location = Registry::locationFactory()->make($xref, $tree);
542
        $location = Auth::checkLocationAccess($location);
543
        $name     = $location->fullName();
544
545
        $options = [
546
            self::ADD_RECORD_ONLY => $name,
547
        ];
548
549
        $title = I18N::translate('Add %s to the clippings cart', $name);
550
551
        return $this->viewResponse('modules/clippings/add-options', [
552
            'options' => $options,
553
            'record'  => $location,
554
            'title'   => $title,
555
            'tree'    => $tree,
556
        ]);
557
    }
558
559
    public function postAddLocationAction(ServerRequestInterface $request): ResponseInterface
560
    {
561
        $tree     = Validator::attributes($request)->tree();
562
        $xref     = Validator::queryParams($request)->isXref()->string('xref');
563
        $location = Registry::locationFactory()->make($xref, $tree);
564
        $location = Auth::checkLocationAccess($location);
565
566
        $this->addLocationToCart($location);
567
568
        return redirect($location->url());
569
    }
570
571
    public function getAddMediaAction(ServerRequestInterface $request): ResponseInterface
572
    {
573
        $tree  = Validator::attributes($request)->tree();
574
        $xref  = Validator::queryParams($request)->isXref()->string('xref');
575
        $media = Registry::mediaFactory()->make($xref, $tree);
576
        $media = Auth::checkMediaAccess($media);
577
        $name  = $media->fullName();
578
579
        $options = [
580
            self::ADD_RECORD_ONLY => $name,
581
        ];
582
583
        $title = I18N::translate('Add %s to the clippings cart', $name);
584
585
        return $this->viewResponse('modules/clippings/add-options', [
586
            'options' => $options,
587
            'record'  => $media,
588
            'title'   => $title,
589
            'tree'    => $tree,
590
        ]);
591
    }
592
593
    public function postAddMediaAction(ServerRequestInterface $request): ResponseInterface
594
    {
595
        $tree  = Validator::attributes($request)->tree();
596
        $xref  = Validator::queryParams($request)->isXref()->string('xref');
597
        $media = Registry::mediaFactory()->make($xref, $tree);
598
        $media = Auth::checkMediaAccess($media);
599
600
        $this->addMediaToCart($media);
601
602
        return redirect($media->url());
603
    }
604
605
    public function getAddNoteAction(ServerRequestInterface $request): ResponseInterface
606
    {
607
        $tree = Validator::attributes($request)->tree();
608
        $xref = Validator::queryParams($request)->isXref()->string('xref');
609
        $note = Registry::noteFactory()->make($xref, $tree);
610
        $note = Auth::checkNoteAccess($note);
611
        $name = $note->fullName();
612
613
        $options = [
614
            self::ADD_RECORD_ONLY => $name,
615
        ];
616
617
        $title = I18N::translate('Add %s to the clippings cart', $name);
618
619
        return $this->viewResponse('modules/clippings/add-options', [
620
            'options' => $options,
621
            'record'  => $note,
622
            'title'   => $title,
623
            'tree'    => $tree,
624
        ]);
625
    }
626
627
    public function postAddNoteAction(ServerRequestInterface $request): ResponseInterface
628
    {
629
        $tree = Validator::attributes($request)->tree();
630
        $xref = Validator::queryParams($request)->isXref()->string('xref');
631
        $note = Registry::noteFactory()->make($xref, $tree);
632
        $note = Auth::checkNoteAccess($note);
633
634
        $this->addNoteToCart($note);
635
636
        return redirect($note->url());
637
    }
638
639
    public function getAddRepositoryAction(ServerRequestInterface $request): ResponseInterface
640
    {
641
        $tree       = Validator::attributes($request)->tree();
642
        $xref       = Validator::queryParams($request)->isXref()->string('xref');
643
        $repository = Registry::repositoryFactory()->make($xref, $tree);
644
        $repository = Auth::checkRepositoryAccess($repository);
645
        $name       = $repository->fullName();
646
647
        $options = [
648
            self::ADD_RECORD_ONLY => $name,
649
        ];
650
651
        $title = I18N::translate('Add %s to the clippings cart', $name);
652
653
        return $this->viewResponse('modules/clippings/add-options', [
654
            'options' => $options,
655
            'record'  => $repository,
656
            'title'   => $title,
657
            'tree'    => $tree,
658
        ]);
659
    }
660
661
    public function postAddRepositoryAction(ServerRequestInterface $request): ResponseInterface
662
    {
663
        $tree       = Validator::attributes($request)->tree();
664
        $xref       = Validator::queryParams($request)->isXref()->string('xref');
665
        $repository = Registry::repositoryFactory()->make($xref, $tree);
666
        $repository = Auth::checkRepositoryAccess($repository);
667
668
        $this->addRepositoryToCart($repository);
669
670
        foreach ($this->linked_record_service->linkedSources($repository) as $source) {
671
            $this->addSourceToCart($source);
672
        }
673
674
        return redirect($repository->url());
675
    }
676
677
    public function getAddSourceAction(ServerRequestInterface $request): ResponseInterface
678
    {
679
        $tree   = Validator::attributes($request)->tree();
680
        $xref   = Validator::queryParams($request)->isXref()->string('xref');
681
        $source = Registry::sourceFactory()->make($xref, $tree);
682
        $source = Auth::checkSourceAccess($source);
683
        $name   = $source->fullName();
684
685
        $options = [
686
            self::ADD_RECORD_ONLY        => $name,
687
            self::ADD_LINKED_INDIVIDUALS => I18N::translate('%s and the individuals that reference it.', $name),
688
        ];
689
690
        $title = I18N::translate('Add %s to the clippings cart', $name);
691
692
        return $this->viewResponse('modules/clippings/add-options', [
693
            'options' => $options,
694
            'record'  => $source,
695
            'title'   => $title,
696
            'tree'    => $tree,
697
        ]);
698
    }
699
700
    public function postAddSourceAction(ServerRequestInterface $request): ResponseInterface
701
    {
702
        $tree   = Validator::attributes($request)->tree();
703
        $xref   = Validator::parsedBody($request)->isXref()->string('xref');
704
        $option = Validator::parsedBody($request)->string('option');
705
706
        $source = Registry::sourceFactory()->make($xref, $tree);
707
        $source = Auth::checkSourceAccess($source);
708
709
        $this->addSourceToCart($source);
710
711
        if ($option === self::ADD_LINKED_INDIVIDUALS) {
712
            foreach ($this->linked_record_service->linkedIndividuals($source) as $individual) {
713
                $this->addIndividualToCart($individual);
714
            }
715
            foreach ($this->linked_record_service->linkedFamilies($source) as $family) {
716
                $this->addFamilyToCart($family);
717
            }
718
        }
719
720
        return redirect($source->url());
721
    }
722
723
    public function getAddSubmitterAction(ServerRequestInterface $request): ResponseInterface
724
    {
725
        $tree      = Validator::attributes($request)->tree();
726
        $xref      = Validator::queryParams($request)->isXref()->string('xref');
727
        $submitter = Registry::submitterFactory()->make($xref, $tree);
728
        $submitter = Auth::checkSubmitterAccess($submitter);
729
        $name      = $submitter->fullName();
730
731
        $options = [
732
            self::ADD_RECORD_ONLY => $name,
733
        ];
734
735
        $title = I18N::translate('Add %s to the clippings cart', $name);
736
737
        return $this->viewResponse('modules/clippings/add-options', [
738
            'options' => $options,
739
            'record'  => $submitter,
740
            'title'   => $title,
741
            'tree'    => $tree,
742
        ]);
743
    }
744
745
    public function postAddSubmitterAction(ServerRequestInterface $request): ResponseInterface
746
    {
747
        $tree      = Validator::attributes($request)->tree();
748
        $xref      = Validator::queryParams($request)->isXref()->string('xref');
749
        $submitter = Registry::submitterFactory()->make($xref, $tree);
750
        $submitter = Auth::checkSubmitterAccess($submitter);
751
752
        $this->addSubmitterToCart($submitter);
753
754
        return redirect($submitter->url());
755
    }
756
757
    protected function addFamilyToCart(Family $family): void
758
    {
759
        $cart = Session::get('cart');
760
        $cart = is_array($cart) ? $cart : [];
761
762
        $tree = $family->tree()->name();
763
        $xref = $family->xref();
764
765
        if (($cart[$tree][$xref] ?? false) === false) {
766
            $cart[$tree][$xref] = true;
767
768
            Session::put('cart', $cart);
769
770
            foreach ($family->spouses() as $spouse) {
771
                $this->addIndividualToCart($spouse);
772
            }
773
774
            $this->addLocationLinksToCart($family);
775
            $this->addMediaLinksToCart($family);
776
            $this->addNoteLinksToCart($family);
777
            $this->addSourceLinksToCart($family);
778
            $this->addSubmitterLinksToCart($family);
779
        }
780
    }
781
782
    protected function addIndividualToCart(Individual $individual): void
783
    {
784
        $cart = Session::get('cart');
785
        $cart = is_array($cart) ? $cart : [];
786
787
        $tree = $individual->tree()->name();
788
        $xref = $individual->xref();
789
790
        if (($cart[$tree][$xref] ?? false) === false) {
791
            $cart[$tree][$xref] = true;
792
793
            Session::put('cart', $cart);
794
795
            $this->addLocationLinksToCart($individual);
796
            $this->addMediaLinksToCart($individual);
797
            $this->addNoteLinksToCart($individual);
798
            $this->addSourceLinksToCart($individual);
799
        }
800
    }
801
802
    protected function addLocationToCart(Location $location): void
803
    {
804
        $cart = Session::get('cart');
805
        $cart = is_array($cart) ? $cart : [];
806
807
        $tree = $location->tree()->name();
808
        $xref = $location->xref();
809
810
        if (($cart[$tree][$xref] ?? false) === false) {
811
            $cart[$tree][$xref] = true;
812
813
            Session::put('cart', $cart);
814
815
            $this->addLocationLinksToCart($location);
816
            $this->addMediaLinksToCart($location);
817
            $this->addNoteLinksToCart($location);
818
            $this->addSourceLinksToCart($location);
819
        }
820
    }
821
822
    protected function addLocationLinksToCart(GedcomRecord $record): void
823
    {
824
        preg_match_all('/\n\d _LOC @(' . Gedcom::REGEX_XREF . ')@/', $record->gedcom(), $matches);
825
826
        foreach ($matches[1] as $xref) {
827
            $location = Registry::locationFactory()->make($xref, $record->tree());
828
829
            if ($location instanceof Location && $location->canShow()) {
830
                $this->addLocationToCart($location);
831
            }
832
        }
833
    }
834
835
    protected function addMediaToCart(Media $media): void
836
    {
837
        $cart = Session::get('cart');
838
        $cart = is_array($cart) ? $cart : [];
839
840
        $tree = $media->tree()->name();
841
        $xref = $media->xref();
842
843
        if (($cart[$tree][$xref] ?? false) === false) {
844
            $cart[$tree][$xref] = true;
845
846
            Session::put('cart', $cart);
847
848
            $this->addNoteLinksToCart($media);
849
        }
850
    }
851
852
    protected function addMediaLinksToCart(GedcomRecord $record): void
853
    {
854
        preg_match_all('/\n\d OBJE @(' . Gedcom::REGEX_XREF . ')@/', $record->gedcom(), $matches);
855
856
        foreach ($matches[1] as $xref) {
857
            $media = Registry::mediaFactory()->make($xref, $record->tree());
858
859
            if ($media instanceof Media && $media->canShow()) {
860
                $this->addMediaToCart($media);
861
            }
862
        }
863
    }
864
865
    protected function addNoteToCart(Note $note): void
866
    {
867
        $cart = Session::get('cart');
868
        $cart = is_array($cart) ? $cart : [];
869
870
        $tree = $note->tree()->name();
871
        $xref = $note->xref();
872
873
        if (($cart[$tree][$xref] ?? false) === false) {
874
            $cart[$tree][$xref] = true;
875
876
            Session::put('cart', $cart);
877
        }
878
    }
879
880
    protected function addNoteLinksToCart(GedcomRecord $record): void
881
    {
882
        preg_match_all('/\n\d NOTE @(' . Gedcom::REGEX_XREF . ')@/', $record->gedcom(), $matches);
883
884
        foreach ($matches[1] as $xref) {
885
            $note = Registry::noteFactory()->make($xref, $record->tree());
886
887
            if ($note instanceof Note && $note->canShow()) {
888
                $this->addNoteToCart($note);
889
            }
890
        }
891
    }
892
893
    protected function addSourceToCart(Source $source): void
894
    {
895
        $cart = Session::get('cart');
896
        $cart = is_array($cart) ? $cart : [];
897
898
        $tree = $source->tree()->name();
899
        $xref = $source->xref();
900
901
        if (($cart[$tree][$xref] ?? false) === false) {
902
            $cart[$tree][$xref] = true;
903
904
            Session::put('cart', $cart);
905
906
            $this->addNoteLinksToCart($source);
907
            $this->addRepositoryLinksToCart($source);
908
        }
909
    }
910
911
    protected function addSourceLinksToCart(GedcomRecord $record): void
912
    {
913
        preg_match_all('/\n\d SOUR @(' . Gedcom::REGEX_XREF . ')@/', $record->gedcom(), $matches);
914
915
        foreach ($matches[1] as $xref) {
916
            $source = Registry::sourceFactory()->make($xref, $record->tree());
917
918
            if ($source instanceof Source && $source->canShow()) {
919
                $this->addSourceToCart($source);
920
            }
921
        }
922
    }
923
924
    protected function addRepositoryToCart(Repository $repository): void
925
    {
926
        $cart = Session::get('cart');
927
        $cart = is_array($cart) ? $cart : [];
928
929
        $tree = $repository->tree()->name();
930
        $xref = $repository->xref();
931
932
        if (($cart[$tree][$xref] ?? false) === false) {
933
            $cart[$tree][$xref] = true;
934
935
            Session::put('cart', $cart);
936
937
            $this->addNoteLinksToCart($repository);
938
        }
939
    }
940
941
    protected function addRepositoryLinksToCart(GedcomRecord $record): void
942
    {
943
        preg_match_all('/\n\d REPO @(' . Gedcom::REGEX_XREF . ')@/', $record->gedcom(), $matches);
944
945
        foreach ($matches[1] as $xref) {
946
            $repository = Registry::repositoryFactory()->make($xref, $record->tree());
947
948
            if ($repository instanceof Repository && $repository->canShow()) {
949
                $this->addRepositoryToCart($repository);
950
            }
951
        }
952
    }
953
954
    protected function addSubmitterToCart(Submitter $submitter): void
955
    {
956
        $cart = Session::get('cart');
957
        $cart = is_array($cart) ? $cart : [];
958
        $tree = $submitter->tree()->name();
959
        $xref = $submitter->xref();
960
961
        if (($cart[$tree][$xref] ?? false) === false) {
962
            $cart[$tree][$xref] = true;
963
964
            Session::put('cart', $cart);
965
966
            $this->addNoteLinksToCart($submitter);
967
        }
968
    }
969
970
    protected function addSubmitterLinksToCart(GedcomRecord $record): void
971
    {
972
        preg_match_all('/\n\d SUBM @(' . Gedcom::REGEX_XREF . ')@/', $record->gedcom(), $matches);
973
974
        foreach ($matches[1] as $xref) {
975
            $submitter = Registry::submitterFactory()->make($xref, $record->tree());
976
977
            if ($submitter instanceof Submitter && $submitter->canShow()) {
978
                $this->addSubmitterToCart($submitter);
979
            }
980
        }
981
    }
982
}
983