xref: /webtrees/app/Module/ClippingsCartModule.php (revision 6830c5c1f08a8c0f37147c0884175ea7fd030dfe)
18c2e8227SGreg Roach<?php
23976b470SGreg Roach
38c2e8227SGreg Roach/**
48c2e8227SGreg Roach * webtrees: online genealogy
5d11be702SGreg Roach * Copyright (C) 2023 webtrees development team
68c2e8227SGreg Roach * This program is free software: you can redistribute it and/or modify
78c2e8227SGreg Roach * it under the terms of the GNU General Public License as published by
88c2e8227SGreg Roach * the Free Software Foundation, either version 3 of the License, or
98c2e8227SGreg Roach * (at your option) any later version.
108c2e8227SGreg Roach * This program is distributed in the hope that it will be useful,
118c2e8227SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of
128c2e8227SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
138c2e8227SGreg Roach * GNU General Public License for more details.
148c2e8227SGreg Roach * You should have received a copy of the GNU General Public License
1589f7189bSGreg Roach * along with this program. If not, see <https://www.gnu.org/licenses/>.
168c2e8227SGreg Roach */
17fcfa147eSGreg Roach
18e7f56f2aSGreg Roachdeclare(strict_types=1);
19e7f56f2aSGreg Roach
2076692c8bSGreg Roachnamespace Fisharebest\Webtrees\Module;
2176692c8bSGreg Roach
220e62c4b8SGreg Roachuse Fisharebest\Webtrees\Auth;
231c6adce8SGreg Roachuse Fisharebest\Webtrees\Encodings\ANSEL;
241c6adce8SGreg Roachuse Fisharebest\Webtrees\Encodings\ASCII;
251c6adce8SGreg Roachuse Fisharebest\Webtrees\Encodings\UTF16BE;
261c6adce8SGreg Roachuse Fisharebest\Webtrees\Encodings\UTF8;
271c6adce8SGreg Roachuse Fisharebest\Webtrees\Encodings\Windows1252;
280e62c4b8SGreg Roachuse Fisharebest\Webtrees\Family;
295a78cd34SGreg Roachuse Fisharebest\Webtrees\Gedcom;
300e62c4b8SGreg Roachuse Fisharebest\Webtrees\GedcomRecord;
31f95e0480SGreg Roachuse Fisharebest\Webtrees\Http\RequestHandlers\FamilyPage;
32f95e0480SGreg Roachuse Fisharebest\Webtrees\Http\RequestHandlers\IndividualPage;
33e8ded2caSGreg Roachuse Fisharebest\Webtrees\Http\RequestHandlers\LocationPage;
34f95e0480SGreg Roachuse Fisharebest\Webtrees\Http\RequestHandlers\MediaPage;
35f95e0480SGreg Roachuse Fisharebest\Webtrees\Http\RequestHandlers\NotePage;
36f95e0480SGreg Roachuse Fisharebest\Webtrees\Http\RequestHandlers\RepositoryPage;
37f95e0480SGreg Roachuse Fisharebest\Webtrees\Http\RequestHandlers\SourcePage;
38d45701ccSGreg Roachuse Fisharebest\Webtrees\Http\RequestHandlers\SubmitterPage;
390e62c4b8SGreg Roachuse Fisharebest\Webtrees\I18N;
400e62c4b8SGreg Roachuse Fisharebest\Webtrees\Individual;
41e8ded2caSGreg Roachuse Fisharebest\Webtrees\Location;
425a78cd34SGreg Roachuse Fisharebest\Webtrees\Media;
430e62c4b8SGreg Roachuse Fisharebest\Webtrees\Menu;
445a78cd34SGreg Roachuse Fisharebest\Webtrees\Note;
45d45701ccSGreg Roachuse Fisharebest\Webtrees\Registry;
465a78cd34SGreg Roachuse Fisharebest\Webtrees\Repository;
4769c05a6eSGreg Roachuse Fisharebest\Webtrees\Services\GedcomExportService;
484991f205SGreg Roachuse Fisharebest\Webtrees\Services\LinkedRecordService;
490e62c4b8SGreg Roachuse Fisharebest\Webtrees\Session;
505a78cd34SGreg Roachuse Fisharebest\Webtrees\Source;
51d45701ccSGreg Roachuse Fisharebest\Webtrees\Submitter;
52aee13b6dSGreg Roachuse Fisharebest\Webtrees\Tree;
531c6adce8SGreg Roachuse Fisharebest\Webtrees\Validator;
5469c05a6eSGreg Roachuse Illuminate\Support\Collection;
556ccdf4f0SGreg Roachuse Psr\Http\Message\ResponseInterface;
566ccdf4f0SGreg Roachuse Psr\Http\Message\ServerRequestInterface;
573976b470SGreg Roach
58bf80ec58SGreg Roachuse function array_filter;
59bf80ec58SGreg Roachuse function array_keys;
60bf80ec58SGreg Roachuse function array_map;
61fa695506SGreg Roachuse function array_search;
625229eadeSGreg Roachuse function assert;
6310e06497SGreg Roachuse function count;
6416ecfcafSGreg Roachuse function date;
6516ecfcafSGreg Roachuse function extension_loaded;
66bf80ec58SGreg Roachuse function in_array;
67d8809d62SGreg Roachuse function is_array;
68ddeb3354SGreg Roachuse function is_string;
69bf80ec58SGreg Roachuse function preg_match_all;
70bf80ec58SGreg Roachuse function redirect;
71bf80ec58SGreg Roachuse function route;
72e5a6b4d4SGreg Roachuse function str_replace;
73fa695506SGreg Roachuse function uasort;
74d45701ccSGreg Roachuse function view;
75fa695506SGreg Roach
76fa695506SGreg Roachuse const PREG_SET_ORDER;
778c2e8227SGreg Roach
788c2e8227SGreg Roach/**
798c2e8227SGreg Roach * Class ClippingsCartModule
808c2e8227SGreg Roach */
8137eb8894SGreg Roachclass ClippingsCartModule extends AbstractModule implements ModuleMenuInterface
82c1010edaSGreg Roach{
8349a243cbSGreg Roach    use ModuleMenuTrait;
8449a243cbSGreg Roach
8503f99a78SGreg Roach    // What to add to the cart?
8603f99a78SGreg Roach    private const ADD_RECORD_ONLY        = 'record';
8703f99a78SGreg Roach    private const ADD_CHILDREN           = 'children';
8803f99a78SGreg Roach    private const ADD_DESCENDANTS        = 'descendants';
8903f99a78SGreg Roach    private const ADD_PARENT_FAMILIES    = 'parents';
9003f99a78SGreg Roach    private const ADD_SPOUSE_FAMILIES    = 'spouses';
9103f99a78SGreg Roach    private const ADD_ANCESTORS          = 'ancestors';
9203f99a78SGreg Roach    private const ADD_ANCESTOR_FAMILIES  = 'families';
9303f99a78SGreg Roach    private const ADD_LINKED_INDIVIDUALS = 'linked';
9403f99a78SGreg Roach
955a78cd34SGreg Roach    // Routes that have a record which can be added to the clipboard
9616d6367aSGreg Roach    private const ROUTES_WITH_RECORDS = [
97f95e0480SGreg Roach        'Family'     => FamilyPage::class,
98f95e0480SGreg Roach        'Individual' => IndividualPage::class,
99f95e0480SGreg Roach        'Media'      => MediaPage::class,
100e8ded2caSGreg Roach        'Location'   => LocationPage::class,
101f95e0480SGreg Roach        'Note'       => NotePage::class,
102f95e0480SGreg Roach        'Repository' => RepositoryPage::class,
103f95e0480SGreg Roach        'Source'     => SourcePage::class,
104d45701ccSGreg Roach        'Submitter'  => SubmitterPage::class,
105c1010edaSGreg Roach    ];
1065a78cd34SGreg Roach
10749a243cbSGreg Roach    /** @var int The default access level for this module.  It can be changed in the control panel. */
10833c746f1SGreg Roach    protected int $access_level = Auth::PRIV_USER;
10949a243cbSGreg Roach
110ca47b2adSGreg Roach    private GedcomExportService $gedcom_export_service;
11169c05a6eSGreg Roach
1124991f205SGreg Roach    private LinkedRecordService $linked_record_service;
1134991f205SGreg Roach
114ca47b2adSGreg Roach    public function __construct(
115ca47b2adSGreg Roach        GedcomExportService $gedcom_export_service,
11616ecfcafSGreg Roach        LinkedRecordService $linked_record_service
117ca47b2adSGreg Roach    ) {
11869c05a6eSGreg Roach        $this->gedcom_export_service = $gedcom_export_service;
1194991f205SGreg Roach        $this->linked_record_service = $linked_record_service;
120e5a6b4d4SGreg Roach    }
121e5a6b4d4SGreg Roach
12249a243cbSGreg Roach    public function description(): string
123c1010edaSGreg Roach    {
124bbb76c12SGreg Roach        /* I18N: Description of the “Clippings cart” module */
125bbb76c12SGreg Roach        return I18N::translate('Select records from your family tree and save them as a GEDCOM file.');
1268c2e8227SGreg Roach    }
1278c2e8227SGreg Roach
1288f53f488SRico Sonntag    public function defaultMenuOrder(): int
129c1010edaSGreg Roach    {
130353b36abSGreg Roach        return 6;
1318c2e8227SGreg Roach    }
1328c2e8227SGreg Roach
1331ff45046SGreg Roach    public function getMenu(Tree $tree): Menu|null
134c1010edaSGreg Roach    {
135d35568b4SGreg Roach        $request = Registry::container()->get(ServerRequestInterface::class);
136b55cbc6bSGreg Roach        $route   = Validator::attributes($request)->route();
137d8809d62SGreg Roach        $cart    = Session::get('cart');
138d8809d62SGreg Roach        $cart    = is_array($cart) ? $cart : [];
139d45701ccSGreg Roach        $count   = count($cart[$tree->name()] ?? []);
140d45701ccSGreg Roach        $badge   = view('components/badge', ['count' => $count]);
141d45701ccSGreg Roach
1425a78cd34SGreg Roach        $submenus = [
143d45701ccSGreg Roach            new Menu($this->title() . ' ' . $badge, route('module', [
14426684e68SGreg Roach                'module' => $this->name(),
145c1010edaSGreg Roach                'action' => 'Show',
146d72b284aSGreg Roach                'tree'   => $tree->name(),
147c1010edaSGreg Roach            ]), 'menu-clippings-cart', ['rel' => 'nofollow']),
1485a78cd34SGreg Roach        ];
1495a78cd34SGreg Roach
1502b0d92b4SGreg Roach        $action = array_search($route->name, self::ROUTES_WITH_RECORDS, true);
151f95e0480SGreg Roach        if ($action !== false) {
1522b0d92b4SGreg Roach            $xref = $route->attributes['xref'];
153ddeb3354SGreg Roach            assert(is_string($xref));
154ddeb3354SGreg Roach
155c1010edaSGreg Roach            $add_route = route('module', [
15626684e68SGreg Roach                'module' => $this->name(),
157f95e0480SGreg Roach                'action' => 'Add' . $action,
158c1010edaSGreg Roach                'xref'   => $xref,
159d72b284aSGreg Roach                'tree'   => $tree->name(),
160c1010edaSGreg Roach            ]);
1615a78cd34SGreg Roach
16225b2dde3SGreg Roach            $submenus[] = new Menu(I18N::translate('Add to the clippings cart'), $add_route, 'menu-clippings-add', ['rel' => 'nofollow']);
1638c2e8227SGreg Roach        }
164cbc1590aSGreg Roach
1655a78cd34SGreg Roach        if (!$this->isCartEmpty($tree)) {
166c1010edaSGreg Roach            $submenus[] = new Menu(I18N::translate('Empty the clippings cart'), route('module', [
16726684e68SGreg Roach                'module' => $this->name(),
168c1010edaSGreg Roach                'action' => 'Empty',
169d72b284aSGreg Roach                'tree'   => $tree->name(),
170c1010edaSGreg Roach            ]), 'menu-clippings-empty', ['rel' => 'nofollow']);
171f95e0480SGreg Roach
172c1010edaSGreg Roach            $submenus[] = new Menu(I18N::translate('Download'), route('module', [
17326684e68SGreg Roach                'module' => $this->name(),
174c1010edaSGreg Roach                'action' => 'DownloadForm',
175d72b284aSGreg Roach                'tree'   => $tree->name(),
176c1010edaSGreg Roach            ]), 'menu-clippings-download', ['rel' => 'nofollow']);
1775a78cd34SGreg Roach        }
1785a78cd34SGreg Roach
17949a243cbSGreg Roach        return new Menu($this->title(), '#', 'menu-clippings', ['rel' => 'nofollow'], $submenus);
1808c2e8227SGreg Roach    }
1818c2e8227SGreg Roach
182d45701ccSGreg Roach    public function title(): string
183d45701ccSGreg Roach    {
184d45701ccSGreg Roach        /* I18N: Name of a module */
185d45701ccSGreg Roach        return I18N::translate('Clippings cart');
186d45701ccSGreg Roach    }
187d45701ccSGreg Roach
188d45701ccSGreg Roach    private function isCartEmpty(Tree $tree): bool
189d45701ccSGreg Roach    {
190d8809d62SGreg Roach        $cart     = Session::get('cart');
191d8809d62SGreg Roach        $cart     = is_array($cart) ? $cart : [];
192d45701ccSGreg Roach        $contents = $cart[$tree->name()] ?? [];
193d45701ccSGreg Roach
194d45701ccSGreg Roach        return $contents === [];
195d45701ccSGreg Roach    }
196d45701ccSGreg Roach
197d45701ccSGreg Roach    public function getDownloadFormAction(ServerRequestInterface $request): ResponseInterface
198d45701ccSGreg Roach    {
199b55cbc6bSGreg Roach        $tree = Validator::attributes($request)->tree();
200d45701ccSGreg Roach
201d45701ccSGreg Roach        $title = I18N::translate('Family tree clippings cart') . ' — ' . I18N::translate('Download');
202d45701ccSGreg Roach
20316ecfcafSGreg Roach        $download_filenames = [
20416ecfcafSGreg Roach            'clippings'                  => 'clippings',
20516ecfcafSGreg Roach            'clippings-' . date('Y-m-d') => 'clippings-' . date('Y-m-d'),
20616ecfcafSGreg Roach        ];
20716ecfcafSGreg Roach
208d45701ccSGreg Roach        return $this->viewResponse('modules/clippings/download', [
20916ecfcafSGreg Roach            'download_filenames' => $download_filenames,
210d45701ccSGreg Roach            'module'             => $this->name(),
211d45701ccSGreg Roach            'title'              => $title,
212d45701ccSGreg Roach            'tree'               => $tree,
21316ecfcafSGreg Roach            'zip_available'      => extension_loaded('zip'),
214d45701ccSGreg Roach        ]);
215d45701ccSGreg Roach    }
216d45701ccSGreg Roach
217f95e0480SGreg Roach    public function postDownloadAction(ServerRequestInterface $request): ResponseInterface
218c1010edaSGreg Roach    {
219b55cbc6bSGreg Roach        $tree = Validator::attributes($request)->tree();
2204ea62551SGreg Roach
22116ecfcafSGreg Roach        if (Auth::isAdmin()) {
22216ecfcafSGreg Roach            $privacy_options = ['none', 'gedadmin', 'user', 'visitor'];
22316ecfcafSGreg Roach        } elseif (Auth::isManager($tree)) {
22416ecfcafSGreg Roach            $privacy_options = ['gedadmin', 'user', 'visitor'];
22516ecfcafSGreg Roach        } elseif (Auth::isMember($tree)) {
22616ecfcafSGreg Roach            $privacy_options = ['user', 'visitor'];
22716ecfcafSGreg Roach        } else {
22816ecfcafSGreg Roach            $privacy_options = ['visitor'];
22916ecfcafSGreg Roach        }
230a04bb9a2SGreg Roach
23116ecfcafSGreg Roach        $filename     = Validator::parsedBody($request)->string('filename');
23216ecfcafSGreg Roach        $format       = Validator::parsedBody($request)->isInArray(['gedcom', 'zip', 'zipmedia', 'gedzip'])->string('format');
23316ecfcafSGreg Roach        $privacy      = Validator::parsedBody($request)->isInArray($privacy_options)->string('privacy');
234b55cbc6bSGreg Roach        $encoding     = Validator::parsedBody($request)->isInArray([UTF8::NAME, UTF16BE::NAME, ANSEL::NAME, ASCII::NAME, Windows1252::NAME])->string('encoding');
235b55cbc6bSGreg Roach        $line_endings = Validator::parsedBody($request)->isInArray(['CRLF', 'LF'])->string('line_endings');
236b46c87bdSGreg Roach
237d8809d62SGreg Roach        $cart = Session::get('cart');
238d8809d62SGreg Roach        $cart = is_array($cart) ? $cart : [];
2398c2e8227SGreg Roach
240aa6f03bbSGreg Roach        $xrefs = array_keys($cart[$tree->name()] ?? []);
241c8846facSGreg Roach        $xrefs = array_map('strval', $xrefs); // PHP converts numeric keys to integers.
2425a78cd34SGreg Roach
24369c05a6eSGreg Roach        $records = new Collection();
2445a78cd34SGreg Roach
2451c6adce8SGreg Roach        switch ($privacy) {
2465a78cd34SGreg Roach            case 'gedadmin':
2475a78cd34SGreg Roach                $access_level = Auth::PRIV_NONE;
2485a78cd34SGreg Roach                break;
2495a78cd34SGreg Roach            case 'user':
2505a78cd34SGreg Roach                $access_level = Auth::PRIV_USER;
2515a78cd34SGreg Roach                break;
2525a78cd34SGreg Roach            case 'visitor':
2535a78cd34SGreg Roach                $access_level = Auth::PRIV_PRIVATE;
2545a78cd34SGreg Roach                break;
2555a78cd34SGreg Roach            case 'none':
2565a78cd34SGreg Roach            default:
2575a78cd34SGreg Roach                $access_level = Auth::PRIV_HIDE;
2585a78cd34SGreg Roach                break;
2595a78cd34SGreg Roach        }
2605a78cd34SGreg Roach
2615a78cd34SGreg Roach        foreach ($xrefs as $xref) {
2626b9cb339SGreg Roach            $object = Registry::gedcomRecordFactory()->make($xref, $tree);
2635a78cd34SGreg Roach            // The object may have been deleted since we added it to the cart....
264*6830c5c1SGreg Roach            if ($object instanceof GedcomRecord && $object->canShow($access_level)) {
265*6830c5c1SGreg Roach                $gedcom = $object->privatizeGedcom($access_level);
266*6830c5c1SGreg Roach
2675a78cd34SGreg Roach                // Remove links to objects that aren't in the cart
268*6830c5c1SGreg Roach                $patterns = [
269*6830c5c1SGreg Roach                    '/\n1 ' . Gedcom::REGEX_TAG . ' @(' . Gedcom::REGEX_XREF . ')@(?:\n[2-9].*)*/',
270*6830c5c1SGreg Roach                    '/\n2 ' . Gedcom::REGEX_TAG . ' @(' . Gedcom::REGEX_XREF . ')@(?:\n[3-9].*)*/',
271*6830c5c1SGreg Roach                    '/\n3 ' . Gedcom::REGEX_TAG . ' @(' . Gedcom::REGEX_XREF . ')@(?:\n[4-9].*)*/',
272*6830c5c1SGreg Roach                    '/\n4 ' . Gedcom::REGEX_TAG . ' @(' . Gedcom::REGEX_XREF . ')@(?:\n[5-9].*)*/',
273*6830c5c1SGreg Roach                ];
274*6830c5c1SGreg Roach
275*6830c5c1SGreg Roach                foreach ($patterns as $pattern) {
276*6830c5c1SGreg Roach                    preg_match_all($pattern, $gedcom, $matches, PREG_SET_ORDER);
277*6830c5c1SGreg Roach
2785a78cd34SGreg Roach                    foreach ($matches as $match) {
279bf80ec58SGreg Roach                        if (!in_array($match[1], $xrefs, true)) {
280*6830c5c1SGreg Roach                            // Remove the reference to any object that isn't in the cart
281*6830c5c1SGreg Roach                            $gedcom = str_replace($match[0], '', $gedcom);
2825a78cd34SGreg Roach                        }
2835a78cd34SGreg Roach                    }
2845a78cd34SGreg Roach                }
2855a78cd34SGreg Roach
286*6830c5c1SGreg Roach                $records->add($gedcom);
2871c6adce8SGreg Roach            }
2881c6adce8SGreg Roach        }
289f566cb16SGreg Roach
2901c6adce8SGreg Roach        // We have already applied privacy filtering, so do not do it again.
29116ecfcafSGreg Roach        return $this->gedcom_export_service->downloadResponse($tree, false, $encoding, 'none', $line_endings, $filename, $format, $records);
2928c2e8227SGreg Roach    }
2938c2e8227SGreg Roach
29457ab2231SGreg Roach    public function getEmptyAction(ServerRequestInterface $request): ResponseInterface
295c1010edaSGreg Roach    {
296b55cbc6bSGreg Roach        $tree = Validator::attributes($request)->tree();
2974ea62551SGreg Roach
298d8809d62SGreg Roach        $cart = Session::get('cart');
299d8809d62SGreg Roach        $cart = is_array($cart) ? $cart : [];
300d8809d62SGreg Roach
301aa6f03bbSGreg Roach        $cart[$tree->name()] = [];
3025a78cd34SGreg Roach        Session::put('cart', $cart);
3038c2e8227SGreg Roach
304c1010edaSGreg Roach        $url = route('module', [
30526684e68SGreg Roach            'module' => $this->name(),
306c1010edaSGreg Roach            'action' => 'Show',
307d72b284aSGreg Roach            'tree'   => $tree->name(),
308c1010edaSGreg Roach        ]);
3095a78cd34SGreg Roach
3106ccdf4f0SGreg Roach        return redirect($url);
3115a78cd34SGreg Roach    }
3125a78cd34SGreg Roach
31357ab2231SGreg Roach    public function postRemoveAction(ServerRequestInterface $request): ResponseInterface
314c1010edaSGreg Roach    {
315b55cbc6bSGreg Roach        $tree = Validator::attributes($request)->tree();
316748dbe15SGreg Roach        $xref = Validator::queryParams($request)->isXref()->string('xref');
317d8809d62SGreg Roach        $cart = Session::get('cart');
318d8809d62SGreg Roach        $cart = is_array($cart) ? $cart : [];
319d8809d62SGreg Roach
320aa6f03bbSGreg Roach        unset($cart[$tree->name()][$xref]);
3215a78cd34SGreg Roach        Session::put('cart', $cart);
3225a78cd34SGreg Roach
323c1010edaSGreg Roach        $url = route('module', [
32426684e68SGreg Roach            'module' => $this->name(),
325c1010edaSGreg Roach            'action' => 'Show',
326d72b284aSGreg Roach            'tree'   => $tree->name(),
327c1010edaSGreg Roach        ]);
3285a78cd34SGreg Roach
3296ccdf4f0SGreg Roach        return redirect($url);
3305a78cd34SGreg Roach    }
3315a78cd34SGreg Roach
33257ab2231SGreg Roach    public function getShowAction(ServerRequestInterface $request): ResponseInterface
333c1010edaSGreg Roach    {
334b55cbc6bSGreg Roach        $tree = Validator::attributes($request)->tree();
33557ab2231SGreg Roach
3365a78cd34SGreg Roach        return $this->viewResponse('modules/clippings/show', [
33777b78a63SRichard Cissée            'module'  => $this->name(),
3385a78cd34SGreg Roach            'records' => $this->allRecordsInCart($tree),
3395a78cd34SGreg Roach            'title'   => I18N::translate('Family tree clippings cart'),
3405a78cd34SGreg Roach            'tree'    => $tree,
3415a78cd34SGreg Roach        ]);
3425a78cd34SGreg Roach    }
3435a78cd34SGreg Roach
3445a78cd34SGreg Roach    /**
34509482a55SGreg Roach     * @return array<GedcomRecord>
346d45701ccSGreg Roach     */
347d45701ccSGreg Roach    private function allRecordsInCart(Tree $tree): array
348d45701ccSGreg Roach    {
349d8809d62SGreg Roach        $cart = Session::get('cart');
350d8809d62SGreg Roach        $cart = is_array($cart) ? $cart : [];
351d45701ccSGreg Roach
352d45701ccSGreg Roach        $xrefs = array_keys($cart[$tree->name()] ?? []);
353d45701ccSGreg Roach        $xrefs = array_map('strval', $xrefs); // PHP converts numeric keys to integers.
354d45701ccSGreg Roach
355d45701ccSGreg Roach        // Fetch all the records in the cart.
3561ff45046SGreg Roach        $records = array_map(static fn (string $xref): GedcomRecord|null => Registry::gedcomRecordFactory()->make($xref, $tree), $xrefs);
357d45701ccSGreg Roach
358d45701ccSGreg Roach        // Some records may have been deleted after they were added to the cart.
359d45701ccSGreg Roach        $records = array_filter($records);
360d45701ccSGreg Roach
361d45701ccSGreg Roach        // Group and sort.
362f25fc0f9SGreg Roach        uasort($records, static fn (GedcomRecord $x, GedcomRecord $y): int => $x->tag() <=> $y->tag() ?: GedcomRecord::nameComparator()($x, $y));
363d45701ccSGreg Roach
364d45701ccSGreg Roach        return $records;
365d45701ccSGreg Roach    }
366d45701ccSGreg Roach
36757ab2231SGreg Roach    public function getAddFamilyAction(ServerRequestInterface $request): ResponseInterface
368c1010edaSGreg Roach    {
369b55cbc6bSGreg Roach        $tree   = Validator::attributes($request)->tree();
370748dbe15SGreg Roach        $xref   = Validator::queryParams($request)->isXref()->string('xref');
3716b9cb339SGreg Roach        $family = Registry::familyFactory()->make($xref, $tree);
372d45701ccSGreg Roach        $family = Auth::checkFamilyAccess($family);
373d45701ccSGreg Roach        $name   = $family->fullName();
3745a78cd34SGreg Roach
375d45701ccSGreg Roach        $options = [
37603f99a78SGreg Roach            self::ADD_RECORD_ONLY => $name,
377bbb76c12SGreg Roach            /* I18N: %s is a family (husband + wife) */
37803f99a78SGreg Roach            self::ADD_CHILDREN    => I18N::translate('%s and their children', $name),
379bbb76c12SGreg Roach            /* I18N: %s is a family (husband + wife) */
38003f99a78SGreg Roach            self::ADD_DESCENDANTS => I18N::translate('%s and their descendants', $name),
3815a78cd34SGreg Roach        ];
382d45701ccSGreg Roach
383d45701ccSGreg Roach        $title = I18N::translate('Add %s to the clippings cart', $name);
384d45701ccSGreg Roach
385d45701ccSGreg Roach        return $this->viewResponse('modules/clippings/add-options', [
386d45701ccSGreg Roach            'options' => $options,
387d45701ccSGreg Roach            'record'  => $family,
388d45701ccSGreg Roach            'title'   => $title,
389d45701ccSGreg Roach            'tree'    => $tree,
390d45701ccSGreg Roach        ]);
3915a78cd34SGreg Roach    }
3925a78cd34SGreg Roach
39357ab2231SGreg Roach    public function postAddFamilyAction(ServerRequestInterface $request): ResponseInterface
394c1010edaSGreg Roach    {
395b55cbc6bSGreg Roach        $tree   = Validator::attributes($request)->tree();
396748dbe15SGreg Roach        $xref   = Validator::parsedBody($request)->isXref()->string('xref');
397748dbe15SGreg Roach        $option = Validator::parsedBody($request)->string('option');
3985a78cd34SGreg Roach
3996b9cb339SGreg Roach        $family = Registry::familyFactory()->make($xref, $tree);
400d45701ccSGreg Roach        $family = Auth::checkFamilyAccess($family);
4015a78cd34SGreg Roach
4025a78cd34SGreg Roach        switch ($option) {
40303f99a78SGreg Roach            case self::ADD_RECORD_ONLY:
4045a78cd34SGreg Roach                $this->addFamilyToCart($family);
4055a78cd34SGreg Roach                break;
4065a78cd34SGreg Roach
40703f99a78SGreg Roach            case self::ADD_CHILDREN:
4085a78cd34SGreg Roach                $this->addFamilyAndChildrenToCart($family);
4095a78cd34SGreg Roach                break;
4105a78cd34SGreg Roach
41103f99a78SGreg Roach            case self::ADD_DESCENDANTS:
4125a78cd34SGreg Roach                $this->addFamilyAndDescendantsToCart($family);
4135a78cd34SGreg Roach                break;
4145a78cd34SGreg Roach        }
4155a78cd34SGreg Roach
4166ccdf4f0SGreg Roach        return redirect($family->url());
4175a78cd34SGreg Roach    }
4185a78cd34SGreg Roach
419d45701ccSGreg Roach    protected function addFamilyAndChildrenToCart(Family $family): void
420c1010edaSGreg Roach    {
421d45701ccSGreg Roach        $this->addFamilyToCart($family);
4225a78cd34SGreg Roach
42339ca88baSGreg Roach        foreach ($family->children() as $child) {
424d45701ccSGreg Roach            $this->addIndividualToCart($child);
4255a78cd34SGreg Roach        }
4265a78cd34SGreg Roach    }
4275a78cd34SGreg Roach
428d45701ccSGreg Roach    protected function addFamilyAndDescendantsToCart(Family $family): void
429c1010edaSGreg Roach    {
430d45701ccSGreg Roach        $this->addFamilyAndChildrenToCart($family);
4315a78cd34SGreg Roach
43239ca88baSGreg Roach        foreach ($family->children() as $child) {
43339ca88baSGreg Roach            foreach ($child->spouseFamilies() as $child_family) {
4345a78cd34SGreg Roach                $this->addFamilyAndDescendantsToCart($child_family);
4355a78cd34SGreg Roach            }
4365a78cd34SGreg Roach        }
4375a78cd34SGreg Roach    }
4385a78cd34SGreg Roach
43957ab2231SGreg Roach    public function getAddIndividualAction(ServerRequestInterface $request): ResponseInterface
440c1010edaSGreg Roach    {
441b55cbc6bSGreg Roach        $tree       = Validator::attributes($request)->tree();
442748dbe15SGreg Roach        $xref       = Validator::queryParams($request)->isXref()->string('xref');
4436b9cb339SGreg Roach        $individual = Registry::individualFactory()->make($xref, $tree);
444d45701ccSGreg Roach        $individual = Auth::checkIndividualAccess($individual);
445d45701ccSGreg Roach        $name       = $individual->fullName();
4465a78cd34SGreg Roach
44739ca88baSGreg Roach        if ($individual->sex() === 'F') {
448d45701ccSGreg Roach            $options = [
44903f99a78SGreg Roach                self::ADD_RECORD_ONLY       => $name,
45003f99a78SGreg Roach                self::ADD_PARENT_FAMILIES   => I18N::translate('%s, her parents and siblings', $name),
45103f99a78SGreg Roach                self::ADD_SPOUSE_FAMILIES   => I18N::translate('%s, her spouses and children', $name),
45203f99a78SGreg Roach                self::ADD_ANCESTORS         => I18N::translate('%s and her ancestors', $name),
45303f99a78SGreg Roach                self::ADD_ANCESTOR_FAMILIES => I18N::translate('%s, her ancestors and their families', $name),
45403f99a78SGreg Roach                self::ADD_DESCENDANTS       => I18N::translate('%s, her spouses and descendants', $name),
4555a78cd34SGreg Roach            ];
456d45701ccSGreg Roach        } else {
457d45701ccSGreg Roach            $options = [
45803f99a78SGreg Roach                self::ADD_RECORD_ONLY       => $name,
45903f99a78SGreg Roach                self::ADD_PARENT_FAMILIES   => I18N::translate('%s, his parents and siblings', $name),
46003f99a78SGreg Roach                self::ADD_SPOUSE_FAMILIES   => I18N::translate('%s, his spouses and children', $name),
46103f99a78SGreg Roach                self::ADD_ANCESTORS         => I18N::translate('%s and his ancestors', $name),
46203f99a78SGreg Roach                self::ADD_ANCESTOR_FAMILIES => I18N::translate('%s, his ancestors and their families', $name),
46303f99a78SGreg Roach                self::ADD_DESCENDANTS       => I18N::translate('%s, his spouses and descendants', $name),
4645a78cd34SGreg Roach            ];
4655a78cd34SGreg Roach        }
4665a78cd34SGreg Roach
467d45701ccSGreg Roach        $title = I18N::translate('Add %s to the clippings cart', $name);
468d45701ccSGreg Roach
469d45701ccSGreg Roach        return $this->viewResponse('modules/clippings/add-options', [
470d45701ccSGreg Roach            'options' => $options,
471d45701ccSGreg Roach            'record'  => $individual,
472d45701ccSGreg Roach            'title'   => $title,
473d45701ccSGreg Roach            'tree'    => $tree,
474d45701ccSGreg Roach        ]);
475d45701ccSGreg Roach    }
476d45701ccSGreg Roach
47757ab2231SGreg Roach    public function postAddIndividualAction(ServerRequestInterface $request): ResponseInterface
478c1010edaSGreg Roach    {
479b55cbc6bSGreg Roach        $tree   = Validator::attributes($request)->tree();
480748dbe15SGreg Roach        $xref   = Validator::parsedBody($request)->isXref()->string('xref');
481748dbe15SGreg Roach        $option = Validator::parsedBody($request)->string('option');
4825a78cd34SGreg Roach
4836b9cb339SGreg Roach        $individual = Registry::individualFactory()->make($xref, $tree);
484d45701ccSGreg Roach        $individual = Auth::checkIndividualAccess($individual);
4855a78cd34SGreg Roach
4865a78cd34SGreg Roach        switch ($option) {
48703f99a78SGreg Roach            case self::ADD_RECORD_ONLY:
488d45701ccSGreg Roach                $this->addIndividualToCart($individual);
4895a78cd34SGreg Roach                break;
4905a78cd34SGreg Roach
49103f99a78SGreg Roach            case self::ADD_PARENT_FAMILIES:
49239ca88baSGreg Roach                foreach ($individual->childFamilies() as $family) {
4935a78cd34SGreg Roach                    $this->addFamilyAndChildrenToCart($family);
4945a78cd34SGreg Roach                }
4955a78cd34SGreg Roach                break;
4965a78cd34SGreg Roach
49703f99a78SGreg Roach            case self::ADD_SPOUSE_FAMILIES:
49839ca88baSGreg Roach                foreach ($individual->spouseFamilies() as $family) {
4995a78cd34SGreg Roach                    $this->addFamilyAndChildrenToCart($family);
5005a78cd34SGreg Roach                }
5015a78cd34SGreg Roach                break;
5025a78cd34SGreg Roach
50303f99a78SGreg Roach            case self::ADD_ANCESTORS:
5045a78cd34SGreg Roach                $this->addAncestorsToCart($individual);
5055a78cd34SGreg Roach                break;
5065a78cd34SGreg Roach
50703f99a78SGreg Roach            case self::ADD_ANCESTOR_FAMILIES:
5085a78cd34SGreg Roach                $this->addAncestorFamiliesToCart($individual);
5095a78cd34SGreg Roach                break;
5105a78cd34SGreg Roach
51103f99a78SGreg Roach            case self::ADD_DESCENDANTS:
51239ca88baSGreg Roach                foreach ($individual->spouseFamilies() as $family) {
5135a78cd34SGreg Roach                    $this->addFamilyAndDescendantsToCart($family);
5145a78cd34SGreg Roach                }
5155a78cd34SGreg Roach                break;
5165a78cd34SGreg Roach        }
5175a78cd34SGreg Roach
5186ccdf4f0SGreg Roach        return redirect($individual->url());
5195a78cd34SGreg Roach    }
5205a78cd34SGreg Roach
521d45701ccSGreg Roach    protected function addAncestorsToCart(Individual $individual): void
522c1010edaSGreg Roach    {
523d45701ccSGreg Roach        $this->addIndividualToCart($individual);
5245a78cd34SGreg Roach
52539ca88baSGreg Roach        foreach ($individual->childFamilies() as $family) {
526d45701ccSGreg Roach            $this->addFamilyToCart($family);
5278df4c68dSGreg Roach
52839ca88baSGreg Roach            foreach ($family->spouses() as $parent) {
5295a78cd34SGreg Roach                $this->addAncestorsToCart($parent);
5305a78cd34SGreg Roach            }
5315a78cd34SGreg Roach        }
5325a78cd34SGreg Roach    }
5335a78cd34SGreg Roach
534d45701ccSGreg Roach    protected function addAncestorFamiliesToCart(Individual $individual): void
535c1010edaSGreg Roach    {
53639ca88baSGreg Roach        foreach ($individual->childFamilies() as $family) {
5375a78cd34SGreg Roach            $this->addFamilyAndChildrenToCart($family);
5388df4c68dSGreg Roach
53939ca88baSGreg Roach            foreach ($family->spouses() as $parent) {
540cad6d3f3SGreg Roach                $this->addAncestorFamiliesToCart($parent);
5415a78cd34SGreg Roach            }
5425a78cd34SGreg Roach        }
5435a78cd34SGreg Roach    }
5445a78cd34SGreg Roach
545e8ded2caSGreg Roach    public function getAddLocationAction(ServerRequestInterface $request): ResponseInterface
546e8ded2caSGreg Roach    {
547b55cbc6bSGreg Roach        $tree     = Validator::attributes($request)->tree();
548748dbe15SGreg Roach        $xref     = Validator::queryParams($request)->isXref()->string('xref');
549e8ded2caSGreg Roach        $location = Registry::locationFactory()->make($xref, $tree);
550e8ded2caSGreg Roach        $location = Auth::checkLocationAccess($location);
551e8ded2caSGreg Roach        $name     = $location->fullName();
552e8ded2caSGreg Roach
553e8ded2caSGreg Roach        $options = [
55403f99a78SGreg Roach            self::ADD_RECORD_ONLY => $name,
555e8ded2caSGreg Roach        ];
556e8ded2caSGreg Roach
557e8ded2caSGreg Roach        $title = I18N::translate('Add %s to the clippings cart', $name);
558e8ded2caSGreg Roach
559e8ded2caSGreg Roach        return $this->viewResponse('modules/clippings/add-options', [
560e8ded2caSGreg Roach            'options' => $options,
561e8ded2caSGreg Roach            'record'  => $location,
562e8ded2caSGreg Roach            'title'   => $title,
563e8ded2caSGreg Roach            'tree'    => $tree,
564e8ded2caSGreg Roach        ]);
565e8ded2caSGreg Roach    }
566e8ded2caSGreg Roach
567e8ded2caSGreg Roach    public function postAddLocationAction(ServerRequestInterface $request): ResponseInterface
568e8ded2caSGreg Roach    {
569b55cbc6bSGreg Roach        $tree     = Validator::attributes($request)->tree();
570748dbe15SGreg Roach        $xref     = Validator::queryParams($request)->isXref()->string('xref');
571e8ded2caSGreg Roach        $location = Registry::locationFactory()->make($xref, $tree);
572e8ded2caSGreg Roach        $location = Auth::checkLocationAccess($location);
573e8ded2caSGreg Roach
574e8ded2caSGreg Roach        $this->addLocationToCart($location);
575e8ded2caSGreg Roach
576e8ded2caSGreg Roach        return redirect($location->url());
577e8ded2caSGreg Roach    }
578e8ded2caSGreg Roach
57957ab2231SGreg Roach    public function getAddMediaAction(ServerRequestInterface $request): ResponseInterface
580c1010edaSGreg Roach    {
581b55cbc6bSGreg Roach        $tree  = Validator::attributes($request)->tree();
582748dbe15SGreg Roach        $xref  = Validator::queryParams($request)->isXref()->string('xref');
5836b9cb339SGreg Roach        $media = Registry::mediaFactory()->make($xref, $tree);
584d45701ccSGreg Roach        $media = Auth::checkMediaAccess($media);
585d45701ccSGreg Roach        $name  = $media->fullName();
5865a78cd34SGreg Roach
587d45701ccSGreg Roach        $options = [
58803f99a78SGreg Roach            self::ADD_RECORD_ONLY => $name,
589d45701ccSGreg Roach        ];
5905a78cd34SGreg Roach
591d45701ccSGreg Roach        $title = I18N::translate('Add %s to the clippings cart', $name);
5925a78cd34SGreg Roach
5935a78cd34SGreg Roach        return $this->viewResponse('modules/clippings/add-options', [
5945a78cd34SGreg Roach            'options' => $options,
5955a78cd34SGreg Roach            'record'  => $media,
5965a78cd34SGreg Roach            'title'   => $title,
5975a78cd34SGreg Roach            'tree'    => $tree,
5985a78cd34SGreg Roach        ]);
5995a78cd34SGreg Roach    }
6005a78cd34SGreg Roach
60157ab2231SGreg Roach    public function postAddMediaAction(ServerRequestInterface $request): ResponseInterface
602c1010edaSGreg Roach    {
603b55cbc6bSGreg Roach        $tree  = Validator::attributes($request)->tree();
604748dbe15SGreg Roach        $xref  = Validator::queryParams($request)->isXref()->string('xref');
6056b9cb339SGreg Roach        $media = Registry::mediaFactory()->make($xref, $tree);
606d45701ccSGreg Roach        $media = Auth::checkMediaAccess($media);
6075a78cd34SGreg Roach
608d45701ccSGreg Roach        $this->addMediaToCart($media);
6095a78cd34SGreg Roach
6106ccdf4f0SGreg Roach        return redirect($media->url());
6115a78cd34SGreg Roach    }
6125a78cd34SGreg Roach
61357ab2231SGreg Roach    public function getAddNoteAction(ServerRequestInterface $request): ResponseInterface
614c1010edaSGreg Roach    {
615b55cbc6bSGreg Roach        $tree = Validator::attributes($request)->tree();
616748dbe15SGreg Roach        $xref = Validator::queryParams($request)->isXref()->string('xref');
6176b9cb339SGreg Roach        $note = Registry::noteFactory()->make($xref, $tree);
618d45701ccSGreg Roach        $note = Auth::checkNoteAccess($note);
619d45701ccSGreg Roach        $name = $note->fullName();
6205a78cd34SGreg Roach
621d45701ccSGreg Roach        $options = [
62203f99a78SGreg Roach            self::ADD_RECORD_ONLY => $name,
623d45701ccSGreg Roach        ];
6245a78cd34SGreg Roach
625d45701ccSGreg Roach        $title = I18N::translate('Add %s to the clippings cart', $name);
6265a78cd34SGreg Roach
6275a78cd34SGreg Roach        return $this->viewResponse('modules/clippings/add-options', [
6285a78cd34SGreg Roach            'options' => $options,
6295a78cd34SGreg Roach            'record'  => $note,
6305a78cd34SGreg Roach            'title'   => $title,
6315a78cd34SGreg Roach            'tree'    => $tree,
6325a78cd34SGreg Roach        ]);
6335a78cd34SGreg Roach    }
6345a78cd34SGreg Roach
63557ab2231SGreg Roach    public function postAddNoteAction(ServerRequestInterface $request): ResponseInterface
636c1010edaSGreg Roach    {
637b55cbc6bSGreg Roach        $tree = Validator::attributes($request)->tree();
638748dbe15SGreg Roach        $xref = Validator::queryParams($request)->isXref()->string('xref');
6396b9cb339SGreg Roach        $note = Registry::noteFactory()->make($xref, $tree);
640d45701ccSGreg Roach        $note = Auth::checkNoteAccess($note);
6415a78cd34SGreg Roach
642d45701ccSGreg Roach        $this->addNoteToCart($note);
6435a78cd34SGreg Roach
6446ccdf4f0SGreg Roach        return redirect($note->url());
6455a78cd34SGreg Roach    }
6465a78cd34SGreg Roach
64757ab2231SGreg Roach    public function getAddRepositoryAction(ServerRequestInterface $request): ResponseInterface
648c1010edaSGreg Roach    {
649b55cbc6bSGreg Roach        $tree       = Validator::attributes($request)->tree();
650748dbe15SGreg Roach        $xref       = Validator::queryParams($request)->isXref()->string('xref');
6516b9cb339SGreg Roach        $repository = Registry::repositoryFactory()->make($xref, $tree);
652d45701ccSGreg Roach        $repository = Auth::checkRepositoryAccess($repository);
653d45701ccSGreg Roach        $name       = $repository->fullName();
6545a78cd34SGreg Roach
655d45701ccSGreg Roach        $options = [
65603f99a78SGreg Roach            self::ADD_RECORD_ONLY => $name,
657d45701ccSGreg Roach        ];
6585a78cd34SGreg Roach
659d45701ccSGreg Roach        $title = I18N::translate('Add %s to the clippings cart', $name);
6605a78cd34SGreg Roach
6615a78cd34SGreg Roach        return $this->viewResponse('modules/clippings/add-options', [
6625a78cd34SGreg Roach            'options' => $options,
6635a78cd34SGreg Roach            'record'  => $repository,
6645a78cd34SGreg Roach            'title'   => $title,
6655a78cd34SGreg Roach            'tree'    => $tree,
6665a78cd34SGreg Roach        ]);
6675a78cd34SGreg Roach    }
6685a78cd34SGreg Roach
66957ab2231SGreg Roach    public function postAddRepositoryAction(ServerRequestInterface $request): ResponseInterface
670c1010edaSGreg Roach    {
671b55cbc6bSGreg Roach        $tree       = Validator::attributes($request)->tree();
672748dbe15SGreg Roach        $xref       = Validator::queryParams($request)->isXref()->string('xref');
6736b9cb339SGreg Roach        $repository = Registry::repositoryFactory()->make($xref, $tree);
674d45701ccSGreg Roach        $repository = Auth::checkRepositoryAccess($repository);
6755a78cd34SGreg Roach
676d45701ccSGreg Roach        $this->addRepositoryToCart($repository);
677d45701ccSGreg Roach
6784991f205SGreg Roach        foreach ($this->linked_record_service->linkedSources($repository) as $source) {
679d45701ccSGreg Roach            $this->addSourceToCart($source);
6805a78cd34SGreg Roach        }
6815a78cd34SGreg Roach
6826ccdf4f0SGreg Roach        return redirect($repository->url());
6835a78cd34SGreg Roach    }
6845a78cd34SGreg Roach
68557ab2231SGreg Roach    public function getAddSourceAction(ServerRequestInterface $request): ResponseInterface
686c1010edaSGreg Roach    {
687b55cbc6bSGreg Roach        $tree   = Validator::attributes($request)->tree();
688748dbe15SGreg Roach        $xref   = Validator::queryParams($request)->isXref()->string('xref');
6896b9cb339SGreg Roach        $source = Registry::sourceFactory()->make($xref, $tree);
690d45701ccSGreg Roach        $source = Auth::checkSourceAccess($source);
691d45701ccSGreg Roach        $name   = $source->fullName();
6925a78cd34SGreg Roach
693d45701ccSGreg Roach        $options = [
69403f99a78SGreg Roach            self::ADD_RECORD_ONLY        => $name,
69503f99a78SGreg Roach            self::ADD_LINKED_INDIVIDUALS => I18N::translate('%s and the individuals that reference it.', $name),
696d45701ccSGreg Roach        ];
6975a78cd34SGreg Roach
698d45701ccSGreg Roach        $title = I18N::translate('Add %s to the clippings cart', $name);
6995a78cd34SGreg Roach
7005a78cd34SGreg Roach        return $this->viewResponse('modules/clippings/add-options', [
7015a78cd34SGreg Roach            'options' => $options,
7025a78cd34SGreg Roach            'record'  => $source,
7035a78cd34SGreg Roach            'title'   => $title,
7045a78cd34SGreg Roach            'tree'    => $tree,
7055a78cd34SGreg Roach        ]);
7065a78cd34SGreg Roach    }
7075a78cd34SGreg Roach
70857ab2231SGreg Roach    public function postAddSourceAction(ServerRequestInterface $request): ResponseInterface
709c1010edaSGreg Roach    {
710b55cbc6bSGreg Roach        $tree   = Validator::attributes($request)->tree();
711748dbe15SGreg Roach        $xref   = Validator::parsedBody($request)->isXref()->string('xref');
712748dbe15SGreg Roach        $option = Validator::parsedBody($request)->string('option');
7135a78cd34SGreg Roach
7146b9cb339SGreg Roach        $source = Registry::sourceFactory()->make($xref, $tree);
715d45701ccSGreg Roach        $source = Auth::checkSourceAccess($source);
7165a78cd34SGreg Roach
717d45701ccSGreg Roach        $this->addSourceToCart($source);
7185a78cd34SGreg Roach
71903f99a78SGreg Roach        if ($option === self::ADD_LINKED_INDIVIDUALS) {
7204991f205SGreg Roach            foreach ($this->linked_record_service->linkedIndividuals($source) as $individual) {
721d45701ccSGreg Roach                $this->addIndividualToCart($individual);
7225a78cd34SGreg Roach            }
7234991f205SGreg Roach            foreach ($this->linked_record_service->linkedFamilies($source) as $family) {
724d45701ccSGreg Roach                $this->addFamilyToCart($family);
7255a78cd34SGreg Roach            }
7265a78cd34SGreg Roach        }
7275a78cd34SGreg Roach
7286ccdf4f0SGreg Roach        return redirect($source->url());
7295a78cd34SGreg Roach    }
7305a78cd34SGreg Roach
731d45701ccSGreg Roach    public function getAddSubmitterAction(ServerRequestInterface $request): ResponseInterface
732c1010edaSGreg Roach    {
733b55cbc6bSGreg Roach        $tree      = Validator::attributes($request)->tree();
734748dbe15SGreg Roach        $xref      = Validator::queryParams($request)->isXref()->string('xref');
735d45701ccSGreg Roach        $submitter = Registry::submitterFactory()->make($xref, $tree);
736d45701ccSGreg Roach        $submitter = Auth::checkSubmitterAccess($submitter);
737d45701ccSGreg Roach        $name      = $submitter->fullName();
7385a78cd34SGreg Roach
739d45701ccSGreg Roach        $options = [
74003f99a78SGreg Roach            self::ADD_RECORD_ONLY => $name,
741d45701ccSGreg Roach        ];
7425a78cd34SGreg Roach
743d45701ccSGreg Roach        $title = I18N::translate('Add %s to the clippings cart', $name);
7445a78cd34SGreg Roach
745d45701ccSGreg Roach        return $this->viewResponse('modules/clippings/add-options', [
746d45701ccSGreg Roach            'options' => $options,
747d45701ccSGreg Roach            'record'  => $submitter,
748d45701ccSGreg Roach            'title'   => $title,
749d45701ccSGreg Roach            'tree'    => $tree,
750d45701ccSGreg Roach        ]);
7515a78cd34SGreg Roach    }
7525a78cd34SGreg Roach
753d45701ccSGreg Roach    public function postAddSubmitterAction(ServerRequestInterface $request): ResponseInterface
754d45701ccSGreg Roach    {
755b55cbc6bSGreg Roach        $tree      = Validator::attributes($request)->tree();
756748dbe15SGreg Roach        $xref      = Validator::queryParams($request)->isXref()->string('xref');
757d45701ccSGreg Roach        $submitter = Registry::submitterFactory()->make($xref, $tree);
758d45701ccSGreg Roach        $submitter = Auth::checkSubmitterAccess($submitter);
759d45701ccSGreg Roach
760d45701ccSGreg Roach        $this->addSubmitterToCart($submitter);
761d45701ccSGreg Roach
762d45701ccSGreg Roach        return redirect($submitter->url());
763d45701ccSGreg Roach    }
764d45701ccSGreg Roach
765d45701ccSGreg Roach    protected function addFamilyToCart(Family $family): void
766c1010edaSGreg Roach    {
767d8809d62SGreg Roach        $cart = Session::get('cart');
768d8809d62SGreg Roach        $cart = is_array($cart) ? $cart : [];
769d8809d62SGreg Roach
770d45701ccSGreg Roach        $tree = $family->tree()->name();
771d45701ccSGreg Roach        $xref = $family->xref();
7725a78cd34SGreg Roach
773d45701ccSGreg Roach        if (($cart[$tree][$xref] ?? false) === false) {
774d45701ccSGreg Roach            $cart[$tree][$xref] = true;
7755a78cd34SGreg Roach
776d45701ccSGreg Roach            Session::put('cart', $cart);
7775a78cd34SGreg Roach
778d45701ccSGreg Roach            foreach ($family->spouses() as $spouse) {
779d45701ccSGreg Roach                $this->addIndividualToCart($spouse);
7805a78cd34SGreg Roach            }
7815a78cd34SGreg Roach
782e8ded2caSGreg Roach            $this->addLocationLinksToCart($family);
783d45701ccSGreg Roach            $this->addMediaLinksToCart($family);
784d45701ccSGreg Roach            $this->addNoteLinksToCart($family);
785d45701ccSGreg Roach            $this->addSourceLinksToCart($family);
786d45701ccSGreg Roach            $this->addSubmitterLinksToCart($family);
787d45701ccSGreg Roach        }
788d45701ccSGreg Roach    }
789d45701ccSGreg Roach
790d45701ccSGreg Roach    protected function addIndividualToCart(Individual $individual): void
791d45701ccSGreg Roach    {
792d8809d62SGreg Roach        $cart = Session::get('cart');
793d8809d62SGreg Roach        $cart = is_array($cart) ? $cart : [];
794d8809d62SGreg Roach
795d45701ccSGreg Roach        $tree = $individual->tree()->name();
796d45701ccSGreg Roach        $xref = $individual->xref();
797d45701ccSGreg Roach
798d45701ccSGreg Roach        if (($cart[$tree][$xref] ?? false) === false) {
799d45701ccSGreg Roach            $cart[$tree][$xref] = true;
800d45701ccSGreg Roach
801d45701ccSGreg Roach            Session::put('cart', $cart);
802d45701ccSGreg Roach
803e8ded2caSGreg Roach            $this->addLocationLinksToCart($individual);
804d45701ccSGreg Roach            $this->addMediaLinksToCart($individual);
805d45701ccSGreg Roach            $this->addNoteLinksToCart($individual);
806d45701ccSGreg Roach            $this->addSourceLinksToCart($individual);
807d45701ccSGreg Roach        }
808d45701ccSGreg Roach    }
809d45701ccSGreg Roach
810e8ded2caSGreg Roach    protected function addLocationToCart(Location $location): void
811e8ded2caSGreg Roach    {
812d8809d62SGreg Roach        $cart = Session::get('cart');
813d8809d62SGreg Roach        $cart = is_array($cart) ? $cart : [];
814d8809d62SGreg Roach
815e8ded2caSGreg Roach        $tree = $location->tree()->name();
816e8ded2caSGreg Roach        $xref = $location->xref();
817e8ded2caSGreg Roach
818e8ded2caSGreg Roach        if (($cart[$tree][$xref] ?? false) === false) {
819e8ded2caSGreg Roach            $cart[$tree][$xref] = true;
820e8ded2caSGreg Roach
821e8ded2caSGreg Roach            Session::put('cart', $cart);
822e8ded2caSGreg Roach
823e8ded2caSGreg Roach            $this->addLocationLinksToCart($location);
824e8ded2caSGreg Roach            $this->addMediaLinksToCart($location);
825e8ded2caSGreg Roach            $this->addNoteLinksToCart($location);
826e8ded2caSGreg Roach            $this->addSourceLinksToCart($location);
827e8ded2caSGreg Roach        }
828e8ded2caSGreg Roach    }
829e8ded2caSGreg Roach
830e8ded2caSGreg Roach    protected function addLocationLinksToCart(GedcomRecord $record): void
831e8ded2caSGreg Roach    {
832e8ded2caSGreg Roach        preg_match_all('/\n\d _LOC @(' . Gedcom::REGEX_XREF . ')@/', $record->gedcom(), $matches);
833e8ded2caSGreg Roach
834e8ded2caSGreg Roach        foreach ($matches[1] as $xref) {
835e8ded2caSGreg Roach            $location = Registry::locationFactory()->make($xref, $record->tree());
836e8ded2caSGreg Roach
837e8ded2caSGreg Roach            if ($location instanceof Location && $location->canShow()) {
838e8ded2caSGreg Roach                $this->addLocationToCart($location);
839e8ded2caSGreg Roach            }
840e8ded2caSGreg Roach        }
841e8ded2caSGreg Roach    }
842e8ded2caSGreg Roach
843d45701ccSGreg Roach    protected function addMediaToCart(Media $media): void
844d45701ccSGreg Roach    {
845d8809d62SGreg Roach        $cart = Session::get('cart');
846d8809d62SGreg Roach        $cart = is_array($cart) ? $cart : [];
847d8809d62SGreg Roach
848d45701ccSGreg Roach        $tree = $media->tree()->name();
849d45701ccSGreg Roach        $xref = $media->xref();
850d45701ccSGreg Roach
851d45701ccSGreg Roach        if (($cart[$tree][$xref] ?? false) === false) {
852d45701ccSGreg Roach            $cart[$tree][$xref] = true;
853d45701ccSGreg Roach
854d45701ccSGreg Roach            Session::put('cart', $cart);
855d45701ccSGreg Roach
856d45701ccSGreg Roach            $this->addNoteLinksToCart($media);
857d45701ccSGreg Roach        }
858d45701ccSGreg Roach    }
859d45701ccSGreg Roach
860d45701ccSGreg Roach    protected function addMediaLinksToCart(GedcomRecord $record): void
861d45701ccSGreg Roach    {
862d45701ccSGreg Roach        preg_match_all('/\n\d OBJE @(' . Gedcom::REGEX_XREF . ')@/', $record->gedcom(), $matches);
863d45701ccSGreg Roach
864d45701ccSGreg Roach        foreach ($matches[1] as $xref) {
865d45701ccSGreg Roach            $media = Registry::mediaFactory()->make($xref, $record->tree());
866d45701ccSGreg Roach
867d45701ccSGreg Roach            if ($media instanceof Media && $media->canShow()) {
868d45701ccSGreg Roach                $this->addMediaToCart($media);
869d45701ccSGreg Roach            }
870d45701ccSGreg Roach        }
871d45701ccSGreg Roach    }
872d45701ccSGreg Roach
873d45701ccSGreg Roach    protected function addNoteToCart(Note $note): void
874d45701ccSGreg Roach    {
875d8809d62SGreg Roach        $cart = Session::get('cart');
876d8809d62SGreg Roach        $cart = is_array($cart) ? $cart : [];
877d8809d62SGreg Roach
878d45701ccSGreg Roach        $tree = $note->tree()->name();
879d45701ccSGreg Roach        $xref = $note->xref();
880d45701ccSGreg Roach
881d45701ccSGreg Roach        if (($cart[$tree][$xref] ?? false) === false) {
882d45701ccSGreg Roach            $cart[$tree][$xref] = true;
883d45701ccSGreg Roach
8845a78cd34SGreg Roach            Session::put('cart', $cart);
8855a78cd34SGreg Roach        }
886d45701ccSGreg Roach    }
8875a78cd34SGreg Roach
888d45701ccSGreg Roach    protected function addNoteLinksToCart(GedcomRecord $record): void
889d45701ccSGreg Roach    {
890d45701ccSGreg Roach        preg_match_all('/\n\d NOTE @(' . Gedcom::REGEX_XREF . ')@/', $record->gedcom(), $matches);
891d45701ccSGreg Roach
892d45701ccSGreg Roach        foreach ($matches[1] as $xref) {
893d45701ccSGreg Roach            $note = Registry::noteFactory()->make($xref, $record->tree());
894d45701ccSGreg Roach
895d45701ccSGreg Roach            if ($note instanceof Note && $note->canShow()) {
896d45701ccSGreg Roach                $this->addNoteToCart($note);
897d45701ccSGreg Roach            }
898d45701ccSGreg Roach        }
899d45701ccSGreg Roach    }
900d45701ccSGreg Roach
901d45701ccSGreg Roach    protected function addSourceToCart(Source $source): void
902c1010edaSGreg Roach    {
903d8809d62SGreg Roach        $cart = Session::get('cart');
904d8809d62SGreg Roach        $cart = is_array($cart) ? $cart : [];
905d8809d62SGreg Roach
906d45701ccSGreg Roach        $tree = $source->tree()->name();
907d45701ccSGreg Roach        $xref = $source->xref();
9085a78cd34SGreg Roach
909d45701ccSGreg Roach        if (($cart[$tree][$xref] ?? false) === false) {
910d45701ccSGreg Roach            $cart[$tree][$xref] = true;
911d45701ccSGreg Roach
912d45701ccSGreg Roach            Session::put('cart', $cart);
913d45701ccSGreg Roach
914d45701ccSGreg Roach            $this->addNoteLinksToCart($source);
915d45701ccSGreg Roach            $this->addRepositoryLinksToCart($source);
916d45701ccSGreg Roach        }
917d45701ccSGreg Roach    }
918d45701ccSGreg Roach
919d45701ccSGreg Roach    protected function addSourceLinksToCart(GedcomRecord $record): void
920d45701ccSGreg Roach    {
921d45701ccSGreg Roach        preg_match_all('/\n\d SOUR @(' . Gedcom::REGEX_XREF . ')@/', $record->gedcom(), $matches);
922d45701ccSGreg Roach
923d45701ccSGreg Roach        foreach ($matches[1] as $xref) {
924d45701ccSGreg Roach            $source = Registry::sourceFactory()->make($xref, $record->tree());
925d45701ccSGreg Roach
926d45701ccSGreg Roach            if ($source instanceof Source && $source->canShow()) {
927d45701ccSGreg Roach                $this->addSourceToCart($source);
928d45701ccSGreg Roach            }
929d45701ccSGreg Roach        }
930d45701ccSGreg Roach    }
931d45701ccSGreg Roach
932d45701ccSGreg Roach    protected function addRepositoryToCart(Repository $repository): void
933d45701ccSGreg Roach    {
934d8809d62SGreg Roach        $cart = Session::get('cart');
935d8809d62SGreg Roach        $cart = is_array($cart) ? $cart : [];
936d8809d62SGreg Roach
937d45701ccSGreg Roach        $tree = $repository->tree()->name();
938d45701ccSGreg Roach        $xref = $repository->xref();
939d45701ccSGreg Roach
940d45701ccSGreg Roach        if (($cart[$tree][$xref] ?? false) === false) {
941d45701ccSGreg Roach            $cart[$tree][$xref] = true;
942d45701ccSGreg Roach
943d45701ccSGreg Roach            Session::put('cart', $cart);
944d45701ccSGreg Roach
945d45701ccSGreg Roach            $this->addNoteLinksToCart($repository);
946d45701ccSGreg Roach        }
947d45701ccSGreg Roach    }
948d45701ccSGreg Roach
949d45701ccSGreg Roach    protected function addRepositoryLinksToCart(GedcomRecord $record): void
950d45701ccSGreg Roach    {
9516e8564a3SGreg Roach        preg_match_all('/\n\d REPO @(' . Gedcom::REGEX_XREF . ')@/', $record->gedcom(), $matches);
952d45701ccSGreg Roach
953d45701ccSGreg Roach        foreach ($matches[1] as $xref) {
954d45701ccSGreg Roach            $repository = Registry::repositoryFactory()->make($xref, $record->tree());
955d45701ccSGreg Roach
956d45701ccSGreg Roach            if ($repository instanceof Repository && $repository->canShow()) {
957d45701ccSGreg Roach                $this->addRepositoryToCart($repository);
958d45701ccSGreg Roach            }
959d45701ccSGreg Roach        }
960d45701ccSGreg Roach    }
961d45701ccSGreg Roach
962d45701ccSGreg Roach    protected function addSubmitterToCart(Submitter $submitter): void
963d45701ccSGreg Roach    {
964d8809d62SGreg Roach        $cart = Session::get('cart');
965d8809d62SGreg Roach        $cart = is_array($cart) ? $cart : [];
966d45701ccSGreg Roach        $tree = $submitter->tree()->name();
967d45701ccSGreg Roach        $xref = $submitter->xref();
968d45701ccSGreg Roach
969d45701ccSGreg Roach        if (($cart[$tree][$xref] ?? false) === false) {
970d45701ccSGreg Roach            $cart[$tree][$xref] = true;
971d45701ccSGreg Roach
972d45701ccSGreg Roach            Session::put('cart', $cart);
973d45701ccSGreg Roach
974d45701ccSGreg Roach            $this->addNoteLinksToCart($submitter);
975d45701ccSGreg Roach        }
976d45701ccSGreg Roach    }
977d45701ccSGreg Roach
978d45701ccSGreg Roach    protected function addSubmitterLinksToCart(GedcomRecord $record): void
979d45701ccSGreg Roach    {
980d45701ccSGreg Roach        preg_match_all('/\n\d SUBM @(' . Gedcom::REGEX_XREF . ')@/', $record->gedcom(), $matches);
981d45701ccSGreg Roach
982d45701ccSGreg Roach        foreach ($matches[1] as $xref) {
983d45701ccSGreg Roach            $submitter = Registry::submitterFactory()->make($xref, $record->tree());
984d45701ccSGreg Roach
985d45701ccSGreg Roach            if ($submitter instanceof Submitter && $submitter->canShow()) {
986d45701ccSGreg Roach                $this->addSubmitterToCart($submitter);
987d45701ccSGreg Roach            }
988d45701ccSGreg Roach        }
9895a78cd34SGreg Roach    }
9908c2e8227SGreg Roach}
991