xref: /webtrees/app/Module/ClippingsCartModule.php (revision 00c45d2300a5375d4779bf9dbe157d4ef56b9443)
18c2e8227SGreg Roach<?php
23976b470SGreg Roach
38c2e8227SGreg Roach/**
48c2e8227SGreg Roach * webtrees: online genealogy
589f7189bSGreg Roach * Copyright (C) 2021 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
22de2aa325SGreg Roachuse Aura\Router\Route;
230e62c4b8SGreg Roachuse Fisharebest\Webtrees\Auth;
240e62c4b8SGreg Roachuse Fisharebest\Webtrees\Family;
255a78cd34SGreg Roachuse Fisharebest\Webtrees\Gedcom;
260e62c4b8SGreg Roachuse Fisharebest\Webtrees\GedcomRecord;
27f95e0480SGreg Roachuse Fisharebest\Webtrees\Http\RequestHandlers\FamilyPage;
28f95e0480SGreg Roachuse Fisharebest\Webtrees\Http\RequestHandlers\IndividualPage;
29e8ded2caSGreg Roachuse Fisharebest\Webtrees\Http\RequestHandlers\LocationPage;
30f95e0480SGreg Roachuse Fisharebest\Webtrees\Http\RequestHandlers\MediaPage;
31f95e0480SGreg Roachuse Fisharebest\Webtrees\Http\RequestHandlers\NotePage;
32f95e0480SGreg Roachuse Fisharebest\Webtrees\Http\RequestHandlers\RepositoryPage;
33f95e0480SGreg Roachuse Fisharebest\Webtrees\Http\RequestHandlers\SourcePage;
34d45701ccSGreg Roachuse Fisharebest\Webtrees\Http\RequestHandlers\SubmitterPage;
350e62c4b8SGreg Roachuse Fisharebest\Webtrees\I18N;
360e62c4b8SGreg Roachuse Fisharebest\Webtrees\Individual;
37e8ded2caSGreg Roachuse Fisharebest\Webtrees\Location;
385a78cd34SGreg Roachuse Fisharebest\Webtrees\Media;
390e62c4b8SGreg Roachuse Fisharebest\Webtrees\Menu;
405a78cd34SGreg Roachuse Fisharebest\Webtrees\Note;
41d45701ccSGreg Roachuse Fisharebest\Webtrees\Registry;
425a78cd34SGreg Roachuse Fisharebest\Webtrees\Repository;
4369c05a6eSGreg Roachuse Fisharebest\Webtrees\Services\GedcomExportService;
44e5a6b4d4SGreg Roachuse Fisharebest\Webtrees\Services\UserService;
450e62c4b8SGreg Roachuse Fisharebest\Webtrees\Session;
465a78cd34SGreg Roachuse Fisharebest\Webtrees\Source;
47d45701ccSGreg Roachuse Fisharebest\Webtrees\Submitter;
48aee13b6dSGreg Roachuse Fisharebest\Webtrees\Tree;
4969c05a6eSGreg Roachuse Illuminate\Support\Collection;
505a78cd34SGreg Roachuse League\Flysystem\Filesystem;
51f0448b68SGreg Roachuse League\Flysystem\FilesystemException;
52f32d77e6SGreg Roachuse League\Flysystem\ZipArchive\FilesystemZipArchiveProvider;
535a78cd34SGreg Roachuse League\Flysystem\ZipArchive\ZipArchiveAdapter;
54*00c45d23SGreg Roachuse Psr\Http\Message\ResponseFactoryInterface;
556ccdf4f0SGreg Roachuse Psr\Http\Message\ResponseInterface;
566ccdf4f0SGreg Roachuse Psr\Http\Message\ServerRequestInterface;
57*00c45d23SGreg Roachuse Psr\Http\Message\StreamFactoryInterface;
5869c05a6eSGreg Roachuse RuntimeException;
593976b470SGreg Roach
60eb235819SGreg Roachuse function app;
61bf80ec58SGreg Roachuse function array_filter;
62bf80ec58SGreg Roachuse function array_keys;
63bf80ec58SGreg Roachuse function array_map;
64fa695506SGreg Roachuse function array_search;
655229eadeSGreg Roachuse function assert;
6669c05a6eSGreg Roachuse function fopen;
67bf80ec58SGreg Roachuse function in_array;
68ddeb3354SGreg Roachuse function is_string;
69bf80ec58SGreg Roachuse function preg_match_all;
70bf80ec58SGreg Roachuse function redirect;
7169c05a6eSGreg Roachuse function rewind;
72bf80ec58SGreg Roachuse function route;
73e5a6b4d4SGreg Roachuse function str_replace;
74fa695506SGreg Roachuse function stream_get_meta_data;
75fa695506SGreg Roachuse function tmpfile;
76fa695506SGreg Roachuse function uasort;
77d45701ccSGreg Roachuse function view;
78fa695506SGreg Roach
79fa695506SGreg Roachuse const PREG_SET_ORDER;
808c2e8227SGreg Roach
818c2e8227SGreg Roach/**
828c2e8227SGreg Roach * Class ClippingsCartModule
838c2e8227SGreg Roach */
8437eb8894SGreg Roachclass ClippingsCartModule extends AbstractModule implements ModuleMenuInterface
85c1010edaSGreg Roach{
8649a243cbSGreg Roach    use ModuleMenuTrait;
8749a243cbSGreg Roach
8803f99a78SGreg Roach    // What to add to the cart?
8903f99a78SGreg Roach    private const ADD_RECORD_ONLY        = 'record';
9003f99a78SGreg Roach    private const ADD_CHILDREN           = 'children';
9103f99a78SGreg Roach    private const ADD_DESCENDANTS        = 'descendants';
9203f99a78SGreg Roach    private const ADD_PARENT_FAMILIES    = 'parents';
9303f99a78SGreg Roach    private const ADD_SPOUSE_FAMILIES    = 'spouses';
9403f99a78SGreg Roach    private const ADD_ANCESTORS          = 'ancestors';
9503f99a78SGreg Roach    private const ADD_ANCESTOR_FAMILIES  = 'families';
9603f99a78SGreg Roach    private const ADD_LINKED_INDIVIDUALS = 'linked';
9703f99a78SGreg Roach
985a78cd34SGreg Roach    // Routes that have a record which can be added to the clipboard
9916d6367aSGreg Roach    private const ROUTES_WITH_RECORDS = [
100f95e0480SGreg Roach        'Family'     => FamilyPage::class,
101f95e0480SGreg Roach        'Individual' => IndividualPage::class,
102f95e0480SGreg Roach        'Media'      => MediaPage::class,
103e8ded2caSGreg Roach        'Location'   => LocationPage::class,
104f95e0480SGreg Roach        'Note'       => NotePage::class,
105f95e0480SGreg Roach        'Repository' => RepositoryPage::class,
106f95e0480SGreg Roach        'Source'     => SourcePage::class,
107d45701ccSGreg Roach        'Submitter'  => SubmitterPage::class,
108c1010edaSGreg Roach    ];
1095a78cd34SGreg Roach
11049a243cbSGreg Roach    /** @var int The default access level for this module.  It can be changed in the control panel. */
11149a243cbSGreg Roach    protected $access_level = Auth::PRIV_USER;
11249a243cbSGreg Roach
11369c05a6eSGreg Roach    /** @var GedcomExportService */
11469c05a6eSGreg Roach    private $gedcom_export_service;
11569c05a6eSGreg Roach
11669c05a6eSGreg Roach    /** @var UserService */
117e5a6b4d4SGreg Roach    private $user_service;
118e5a6b4d4SGreg Roach
119e5a6b4d4SGreg Roach    /**
120e5a6b4d4SGreg Roach     * ClippingsCartModule constructor.
121e5a6b4d4SGreg Roach     *
12269c05a6eSGreg Roach     * @param GedcomExportService $gedcom_export_service
123e5a6b4d4SGreg Roach     * @param UserService         $user_service
124e5a6b4d4SGreg Roach     */
12569c05a6eSGreg Roach    public function __construct(GedcomExportService $gedcom_export_service, UserService $user_service)
126e5a6b4d4SGreg Roach    {
12769c05a6eSGreg Roach        $this->gedcom_export_service = $gedcom_export_service;
128e5a6b4d4SGreg Roach        $this->user_service          = $user_service;
129e5a6b4d4SGreg Roach    }
130e5a6b4d4SGreg Roach
131e5a6b4d4SGreg Roach    /**
132961ec755SGreg Roach     * A sentence describing what this module does.
133961ec755SGreg Roach     *
134961ec755SGreg Roach     * @return string
135961ec755SGreg Roach     */
13649a243cbSGreg Roach    public function description(): string
137c1010edaSGreg Roach    {
138bbb76c12SGreg Roach        /* I18N: Description of the “Clippings cart” module */
139bbb76c12SGreg Roach        return I18N::translate('Select records from your family tree and save them as a GEDCOM file.');
1408c2e8227SGreg Roach    }
1418c2e8227SGreg Roach
1420ee13198SGreg Roach    /**
14349a243cbSGreg Roach     * The default position for this menu.  It can be changed in the control panel.
1440ee13198SGreg Roach     *
1450ee13198SGreg Roach     * @return int
1460ee13198SGreg Roach     */
1478f53f488SRico Sonntag    public function defaultMenuOrder(): int
148c1010edaSGreg Roach    {
149353b36abSGreg Roach        return 6;
1508c2e8227SGreg Roach    }
1518c2e8227SGreg Roach
1520ee13198SGreg Roach    /**
1530ee13198SGreg Roach     * A menu, to be added to the main application menu.
1540ee13198SGreg Roach     *
155aee13b6dSGreg Roach     * @param Tree $tree
156aee13b6dSGreg Roach     *
1570ee13198SGreg Roach     * @return Menu|null
1580ee13198SGreg Roach     */
15946295629SGreg Roach    public function getMenu(Tree $tree): ?Menu
160c1010edaSGreg Roach    {
161eb235819SGreg Roach        /** @var ServerRequestInterface $request */
1626ccdf4f0SGreg Roach        $request = app(ServerRequestInterface::class);
1638c2e8227SGreg Roach
164f7ab47b1SGreg Roach        $route = $request->getAttribute('route');
165de2aa325SGreg Roach        assert($route instanceof Route);
1665a78cd34SGreg Roach
167d45701ccSGreg Roach        $cart  = Session::get('cart', []);
168d45701ccSGreg Roach        $count = count($cart[$tree->name()] ?? []);
169d45701ccSGreg Roach        $badge = view('components/badge', ['count' => $count]);
170d45701ccSGreg Roach
1715a78cd34SGreg Roach        $submenus = [
172d45701ccSGreg Roach            new Menu($this->title() . ' ' . $badge, route('module', [
17326684e68SGreg Roach                'module' => $this->name(),
174c1010edaSGreg Roach                'action' => 'Show',
175d72b284aSGreg Roach                'tree'   => $tree->name(),
176c1010edaSGreg Roach            ]), 'menu-clippings-cart', ['rel' => 'nofollow']),
1775a78cd34SGreg Roach        ];
1785a78cd34SGreg Roach
1792b0d92b4SGreg Roach        $action = array_search($route->name, self::ROUTES_WITH_RECORDS, true);
180f95e0480SGreg Roach        if ($action !== false) {
1812b0d92b4SGreg Roach            $xref = $route->attributes['xref'];
182ddeb3354SGreg Roach            assert(is_string($xref));
183ddeb3354SGreg Roach
184c1010edaSGreg Roach            $add_route = route('module', [
18526684e68SGreg Roach                'module' => $this->name(),
186f95e0480SGreg Roach                'action' => 'Add' . $action,
187c1010edaSGreg Roach                'xref'   => $xref,
188d72b284aSGreg Roach                'tree'   => $tree->name(),
189c1010edaSGreg Roach            ]);
1905a78cd34SGreg Roach
19125b2dde3SGreg Roach            $submenus[] = new Menu(I18N::translate('Add to the clippings cart'), $add_route, 'menu-clippings-add', ['rel' => 'nofollow']);
1928c2e8227SGreg Roach        }
193cbc1590aSGreg Roach
1945a78cd34SGreg Roach        if (!$this->isCartEmpty($tree)) {
195c1010edaSGreg Roach            $submenus[] = new Menu(I18N::translate('Empty the clippings cart'), route('module', [
19626684e68SGreg Roach                'module' => $this->name(),
197c1010edaSGreg Roach                'action' => 'Empty',
198d72b284aSGreg Roach                'tree'   => $tree->name(),
199c1010edaSGreg Roach            ]), 'menu-clippings-empty', ['rel' => 'nofollow']);
200f95e0480SGreg Roach
201c1010edaSGreg Roach            $submenus[] = new Menu(I18N::translate('Download'), route('module', [
20226684e68SGreg Roach                'module' => $this->name(),
203c1010edaSGreg Roach                'action' => 'DownloadForm',
204d72b284aSGreg Roach                'tree'   => $tree->name(),
205c1010edaSGreg Roach            ]), 'menu-clippings-download', ['rel' => 'nofollow']);
2065a78cd34SGreg Roach        }
2075a78cd34SGreg Roach
20849a243cbSGreg Roach        return new Menu($this->title(), '#', 'menu-clippings', ['rel' => 'nofollow'], $submenus);
2098c2e8227SGreg Roach    }
2108c2e8227SGreg Roach
21176692c8bSGreg Roach    /**
212d45701ccSGreg Roach     * How should this module be identified in the control panel, etc.?
213d45701ccSGreg Roach     *
214d45701ccSGreg Roach     * @return string
215d45701ccSGreg Roach     */
216d45701ccSGreg Roach    public function title(): string
217d45701ccSGreg Roach    {
218d45701ccSGreg Roach        /* I18N: Name of a module */
219d45701ccSGreg Roach        return I18N::translate('Clippings cart');
220d45701ccSGreg Roach    }
221d45701ccSGreg Roach
222d45701ccSGreg Roach    /**
223d45701ccSGreg Roach     * @param Tree $tree
224d45701ccSGreg Roach     *
225d45701ccSGreg Roach     * @return bool
226d45701ccSGreg Roach     */
227d45701ccSGreg Roach    private function isCartEmpty(Tree $tree): bool
228d45701ccSGreg Roach    {
229d45701ccSGreg Roach        $cart     = Session::get('cart', []);
230d45701ccSGreg Roach        $contents = $cart[$tree->name()] ?? [];
231d45701ccSGreg Roach
232d45701ccSGreg Roach        return $contents === [];
233d45701ccSGreg Roach    }
234d45701ccSGreg Roach
235d45701ccSGreg Roach    /**
236d45701ccSGreg Roach     * @param ServerRequestInterface $request
237d45701ccSGreg Roach     *
238d45701ccSGreg Roach     * @return ResponseInterface
239d45701ccSGreg Roach     */
240d45701ccSGreg Roach    public function getDownloadFormAction(ServerRequestInterface $request): ResponseInterface
241d45701ccSGreg Roach    {
242d45701ccSGreg Roach        $tree = $request->getAttribute('tree');
243d45701ccSGreg Roach        assert($tree instanceof Tree);
244d45701ccSGreg Roach
245d45701ccSGreg Roach        $user  = $request->getAttribute('user');
246d45701ccSGreg Roach        $title = I18N::translate('Family tree clippings cart') . ' — ' . I18N::translate('Download');
247d45701ccSGreg Roach
248d45701ccSGreg Roach        return $this->viewResponse('modules/clippings/download', [
249d45701ccSGreg Roach            'is_manager' => Auth::isManager($tree, $user),
250d45701ccSGreg Roach            'is_member'  => Auth::isMember($tree, $user),
251d45701ccSGreg Roach            'module'     => $this->name(),
252d45701ccSGreg Roach            'title'      => $title,
253d45701ccSGreg Roach            'tree'       => $tree,
254d45701ccSGreg Roach        ]);
255d45701ccSGreg Roach    }
256d45701ccSGreg Roach
257d45701ccSGreg Roach    /**
2586ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
25976692c8bSGreg Roach     *
2606ccdf4f0SGreg Roach     * @return ResponseInterface
261f0448b68SGreg Roach     * @throws FilesystemException
26276692c8bSGreg Roach     */
263f95e0480SGreg Roach    public function postDownloadAction(ServerRequestInterface $request): ResponseInterface
264c1010edaSGreg Roach    {
26557ab2231SGreg Roach        $tree = $request->getAttribute('tree');
2664ea62551SGreg Roach        assert($tree instanceof Tree);
2674ea62551SGreg Roach
2686b9cb339SGreg Roach        $data_filesystem = Registry::filesystem()->data();
269a04bb9a2SGreg Roach
270b46c87bdSGreg Roach        $params = (array) $request->getParsedBody();
271b46c87bdSGreg Roach
272d45701ccSGreg Roach        $privatize_export = $params['privatize_export'] ?? 'none';
273e2ed7c79SGreg Roach
274e2ed7c79SGreg Roach        if ($privatize_export === 'none' && !Auth::isManager($tree)) {
275e2ed7c79SGreg Roach            $privatize_export = 'member';
276e2ed7c79SGreg Roach        }
277e2ed7c79SGreg Roach
278e2ed7c79SGreg Roach        if ($privatize_export === 'gedadmin' && !Auth::isManager($tree)) {
279e2ed7c79SGreg Roach            $privatize_export = 'member';
280e2ed7c79SGreg Roach        }
281e2ed7c79SGreg Roach
282e2ed7c79SGreg Roach        if ($privatize_export === 'user' && !Auth::isMember($tree)) {
283e2ed7c79SGreg Roach            $privatize_export = 'visitor';
284e2ed7c79SGreg Roach        }
285e2ed7c79SGreg Roach
286b46c87bdSGreg Roach        $convert = (bool) ($params['convert'] ?? false);
2878c2e8227SGreg Roach
28813abd6f3SGreg Roach        $cart = Session::get('cart', []);
2898c2e8227SGreg Roach
290aa6f03bbSGreg Roach        $xrefs = array_keys($cart[$tree->name()] ?? []);
291c8846facSGreg Roach        $xrefs = array_map('strval', $xrefs); // PHP converts numeric keys to integers.
2925a78cd34SGreg Roach
2935a78cd34SGreg Roach        // Create a new/empty .ZIP file
294a00baf47SGreg Roach        $temp_zip_file  = stream_get_meta_data(tmpfile())['uri'];
295f32d77e6SGreg Roach        $zip_provider   = new FilesystemZipArchiveProvider($temp_zip_file, 0755);
296f32d77e6SGreg Roach        $zip_adapter    = new ZipArchiveAdapter($zip_provider);
2977f996f6eSGreg Roach        $zip_filesystem = new Filesystem($zip_adapter);
2985a78cd34SGreg Roach
299fa695506SGreg Roach        $media_filesystem = $tree->mediaFilesystem($data_filesystem);
30061bf91b2SGreg Roach
3015a78cd34SGreg Roach        // Media file prefix
3025a78cd34SGreg Roach        $path = $tree->getPreference('MEDIA_DIRECTORY');
3035a78cd34SGreg Roach
30469c05a6eSGreg Roach        $encoding = $convert ? 'ANSI' : 'UTF-8';
30569c05a6eSGreg Roach
30669c05a6eSGreg Roach        $records = new Collection();
3075a78cd34SGreg Roach
3085a78cd34SGreg Roach        switch ($privatize_export) {
3095a78cd34SGreg Roach            case 'gedadmin':
3105a78cd34SGreg Roach                $access_level = Auth::PRIV_NONE;
3115a78cd34SGreg Roach                break;
3125a78cd34SGreg Roach            case 'user':
3135a78cd34SGreg Roach                $access_level = Auth::PRIV_USER;
3145a78cd34SGreg Roach                break;
3155a78cd34SGreg Roach            case 'visitor':
3165a78cd34SGreg Roach                $access_level = Auth::PRIV_PRIVATE;
3175a78cd34SGreg Roach                break;
3185a78cd34SGreg Roach            case 'none':
3195a78cd34SGreg Roach            default:
3205a78cd34SGreg Roach                $access_level = Auth::PRIV_HIDE;
3215a78cd34SGreg Roach                break;
3225a78cd34SGreg Roach        }
3235a78cd34SGreg Roach
3245a78cd34SGreg Roach        foreach ($xrefs as $xref) {
3256b9cb339SGreg Roach            $object = Registry::gedcomRecordFactory()->make($xref, $tree);
3265a78cd34SGreg Roach            // The object may have been deleted since we added it to the cart....
327bed27cedSGreg Roach            if ($object instanceof GedcomRecord) {
3285a78cd34SGreg Roach                $record = $object->privatizeGedcom($access_level);
3295a78cd34SGreg Roach                // Remove links to objects that aren't in the cart
3308d0ebef0SGreg Roach                preg_match_all('/\n1 ' . Gedcom::REGEX_TAG . ' @(' . Gedcom::REGEX_XREF . ')@(\n[2-9].*)*/', $record, $matches, PREG_SET_ORDER);
3315a78cd34SGreg Roach                foreach ($matches as $match) {
332bf80ec58SGreg Roach                    if (!in_array($match[1], $xrefs, true)) {
3335a78cd34SGreg Roach                        $record = str_replace($match[0], '', $record);
3345a78cd34SGreg Roach                    }
3355a78cd34SGreg Roach                }
3368d0ebef0SGreg Roach                preg_match_all('/\n2 ' . Gedcom::REGEX_TAG . ' @(' . Gedcom::REGEX_XREF . ')@(\n[3-9].*)*/', $record, $matches, PREG_SET_ORDER);
3375a78cd34SGreg Roach                foreach ($matches as $match) {
338bf80ec58SGreg Roach                    if (!in_array($match[1], $xrefs, true)) {
3395a78cd34SGreg Roach                        $record = str_replace($match[0], '', $record);
3405a78cd34SGreg Roach                    }
3415a78cd34SGreg Roach                }
3428d0ebef0SGreg Roach                preg_match_all('/\n3 ' . Gedcom::REGEX_TAG . ' @(' . Gedcom::REGEX_XREF . ')@(\n[4-9].*)*/', $record, $matches, PREG_SET_ORDER);
3435a78cd34SGreg Roach                foreach ($matches as $match) {
344bf80ec58SGreg Roach                    if (!in_array($match[1], $xrefs, true)) {
3455a78cd34SGreg Roach                        $record = str_replace($match[0], '', $record);
3465a78cd34SGreg Roach                    }
3475a78cd34SGreg Roach                }
3485a78cd34SGreg Roach
34955167344SGreg Roach                if ($object instanceof Individual || $object instanceof Family) {
35069c05a6eSGreg Roach                    $records->add($record . "\n1 SOUR @WEBTREES@\n2 PAGE " . $object->url());
35155167344SGreg Roach                } elseif ($object instanceof Source) {
35269c05a6eSGreg Roach                    $records->add($record . "\n1 NOTE " . $object->url());
35355167344SGreg Roach                } elseif ($object instanceof Media) {
35455167344SGreg Roach                    // Add the media files to the archive
3555a78cd34SGreg Roach                    foreach ($object->mediaFiles() as $media_file) {
356fa695506SGreg Roach                        $from = $media_file->filename();
357fa695506SGreg Roach                        $to   = $path . $media_file->filename();
358f0448b68SGreg Roach                        if (!$media_file->isExternal() && $media_filesystem->fileExists($from)) {
359fa695506SGreg Roach                            $zip_filesystem->writeStream($to, $media_filesystem->readStream($from));
3605a78cd34SGreg Roach                        }
3615a78cd34SGreg Roach                    }
36269c05a6eSGreg Roach                    $records->add($record);
36355167344SGreg Roach                } else {
36469c05a6eSGreg Roach                    $records->add($record);
3658c2e8227SGreg Roach                }
3668c2e8227SGreg Roach            }
3678c2e8227SGreg Roach        }
3688c2e8227SGreg Roach
3699b93b7c3SGreg Roach        $base_url = $request->getAttribute('base_url');
3709b93b7c3SGreg Roach
3715a78cd34SGreg Roach        // Create a source, to indicate the source of the data.
37269c05a6eSGreg Roach        $record = "0 @WEBTREES@ SOUR\n1 TITL " . $base_url;
373e5a6b4d4SGreg Roach        $author = $this->user_service->find((int) $tree->getPreference('CONTACT_USER_ID'));
3745a78cd34SGreg Roach        if ($author !== null) {
37569c05a6eSGreg Roach            $record .= "\n1 AUTH " . $author->realName();
3765a78cd34SGreg Roach        }
37769c05a6eSGreg Roach        $records->add($record);
3785a78cd34SGreg Roach
37969c05a6eSGreg Roach        $stream = fopen('php://temp', 'wb+');
3805a78cd34SGreg Roach
38169c05a6eSGreg Roach        if ($stream === false) {
38269c05a6eSGreg Roach            throw new RuntimeException('Failed to create temporary stream');
3838c2e8227SGreg Roach        }
384cbc1590aSGreg Roach
38569c05a6eSGreg Roach        // We have already applied privacy filtering, so do not do it again.
38669c05a6eSGreg Roach        $this->gedcom_export_service->export($tree, $stream, false, $encoding, Auth::PRIV_HIDE, $path, $records);
38769c05a6eSGreg Roach        rewind($stream);
38869c05a6eSGreg Roach
3895a78cd34SGreg Roach        // Finally add the GEDCOM file to the .ZIP file.
39069c05a6eSGreg Roach        $zip_filesystem->writeStream('clippings.ged', $stream);
3915a78cd34SGreg Roach
3926ccdf4f0SGreg Roach        // Use a stream, so that we do not have to load the entire file into memory.
393*00c45d23SGreg Roach        $stream = app(StreamFactoryInterface::class)->createStreamFromFile($temp_zip_file);
3945a78cd34SGreg Roach
395*00c45d23SGreg Roach        /** @var ResponseFactoryInterface $response_factory */
396*00c45d23SGreg Roach        $response_factory = app(ResponseFactoryInterface::class);
397*00c45d23SGreg Roach
398*00c45d23SGreg Roach        return $response_factory->createResponse()
3996ccdf4f0SGreg Roach            ->withBody($stream)
4001b3d4731SGreg Roach            ->withHeader('Content-Type', 'application/zip')
401bed27cedSGreg Roach            ->withHeader('Content-Disposition', 'attachment; filename="clippings.zip');
4028c2e8227SGreg Roach    }
4038c2e8227SGreg Roach
4048c2e8227SGreg Roach    /**
40557ab2231SGreg Roach     * @param ServerRequestInterface $request
40676692c8bSGreg Roach     *
4076ccdf4f0SGreg Roach     * @return ResponseInterface
4088c2e8227SGreg Roach     */
40957ab2231SGreg Roach    public function getEmptyAction(ServerRequestInterface $request): ResponseInterface
410c1010edaSGreg Roach    {
41157ab2231SGreg Roach        $tree = $request->getAttribute('tree');
4124ea62551SGreg Roach        assert($tree instanceof Tree);
4134ea62551SGreg Roach
4145a78cd34SGreg Roach        $cart                = Session::get('cart', []);
415aa6f03bbSGreg Roach        $cart[$tree->name()] = [];
4165a78cd34SGreg Roach        Session::put('cart', $cart);
4178c2e8227SGreg Roach
418c1010edaSGreg Roach        $url = route('module', [
41926684e68SGreg Roach            'module' => $this->name(),
420c1010edaSGreg Roach            'action' => 'Show',
421d72b284aSGreg Roach            'tree'   => $tree->name(),
422c1010edaSGreg Roach        ]);
4235a78cd34SGreg Roach
4246ccdf4f0SGreg Roach        return redirect($url);
4255a78cd34SGreg Roach    }
4265a78cd34SGreg Roach
4275a78cd34SGreg Roach    /**
4286ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
4295a78cd34SGreg Roach     *
4306ccdf4f0SGreg Roach     * @return ResponseInterface
4315a78cd34SGreg Roach     */
43257ab2231SGreg Roach    public function postRemoveAction(ServerRequestInterface $request): ResponseInterface
433c1010edaSGreg Roach    {
43457ab2231SGreg Roach        $tree = $request->getAttribute('tree');
43575964c75SGreg Roach        assert($tree instanceof Tree);
4365229eadeSGreg Roach
437d45701ccSGreg Roach        $xref = $request->getQueryParams()['xref'] ?? '';
4385a78cd34SGreg Roach
4395a78cd34SGreg Roach        $cart = Session::get('cart', []);
440aa6f03bbSGreg Roach        unset($cart[$tree->name()][$xref]);
4415a78cd34SGreg Roach        Session::put('cart', $cart);
4425a78cd34SGreg Roach
443c1010edaSGreg Roach        $url = route('module', [
44426684e68SGreg Roach            'module' => $this->name(),
445c1010edaSGreg Roach            'action' => 'Show',
446d72b284aSGreg Roach            'tree'   => $tree->name(),
447c1010edaSGreg Roach        ]);
4485a78cd34SGreg Roach
4496ccdf4f0SGreg Roach        return redirect($url);
4505a78cd34SGreg Roach    }
4515a78cd34SGreg Roach
4525a78cd34SGreg Roach    /**
45357ab2231SGreg Roach     * @param ServerRequestInterface $request
4545a78cd34SGreg Roach     *
4556ccdf4f0SGreg Roach     * @return ResponseInterface
4565a78cd34SGreg Roach     */
45757ab2231SGreg Roach    public function getShowAction(ServerRequestInterface $request): ResponseInterface
458c1010edaSGreg Roach    {
45957ab2231SGreg Roach        $tree = $request->getAttribute('tree');
46075964c75SGreg Roach        assert($tree instanceof Tree);
46157ab2231SGreg Roach
4625a78cd34SGreg Roach        return $this->viewResponse('modules/clippings/show', [
46377b78a63SRichard Cissée            'module'  => $this->name(),
4645a78cd34SGreg Roach            'records' => $this->allRecordsInCart($tree),
4655a78cd34SGreg Roach            'title'   => I18N::translate('Family tree clippings cart'),
4665a78cd34SGreg Roach            'tree'    => $tree,
4675a78cd34SGreg Roach        ]);
4685a78cd34SGreg Roach    }
4695a78cd34SGreg Roach
4705a78cd34SGreg Roach    /**
471d45701ccSGreg Roach     * Get all the records in the cart.
472d45701ccSGreg Roach     *
473d45701ccSGreg Roach     * @param Tree $tree
474d45701ccSGreg Roach     *
475d45701ccSGreg Roach     * @return GedcomRecord[]
476d45701ccSGreg Roach     */
477d45701ccSGreg Roach    private function allRecordsInCart(Tree $tree): array
478d45701ccSGreg Roach    {
479d45701ccSGreg Roach        $cart = Session::get('cart', []);
480d45701ccSGreg Roach
481d45701ccSGreg Roach        $xrefs = array_keys($cart[$tree->name()] ?? []);
482d45701ccSGreg Roach        $xrefs = array_map('strval', $xrefs); // PHP converts numeric keys to integers.
483d45701ccSGreg Roach
484d45701ccSGreg Roach        // Fetch all the records in the cart.
485d45701ccSGreg Roach        $records = array_map(static function (string $xref) use ($tree): ?GedcomRecord {
486d45701ccSGreg Roach            return Registry::gedcomRecordFactory()->make($xref, $tree);
487d45701ccSGreg Roach        }, $xrefs);
488d45701ccSGreg Roach
489d45701ccSGreg Roach        // Some records may have been deleted after they were added to the cart.
490d45701ccSGreg Roach        $records = array_filter($records);
491d45701ccSGreg Roach
492d45701ccSGreg Roach        // Group and sort.
493d45701ccSGreg Roach        uasort($records, static function (GedcomRecord $x, GedcomRecord $y): int {
494d45701ccSGreg Roach            return $x->tag() <=> $y->tag() ?: GedcomRecord::nameComparator()($x, $y);
495d45701ccSGreg Roach        });
496d45701ccSGreg Roach
497d45701ccSGreg Roach        return $records;
498d45701ccSGreg Roach    }
499d45701ccSGreg Roach
500d45701ccSGreg Roach    /**
5016ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
5025a78cd34SGreg Roach     *
5036ccdf4f0SGreg Roach     * @return ResponseInterface
5045a78cd34SGreg Roach     */
50557ab2231SGreg Roach    public function getAddFamilyAction(ServerRequestInterface $request): ResponseInterface
506c1010edaSGreg Roach    {
50757ab2231SGreg Roach        $tree = $request->getAttribute('tree');
50875964c75SGreg Roach        assert($tree instanceof Tree);
5095229eadeSGreg Roach
510d45701ccSGreg Roach        $xref = $request->getQueryParams()['xref'] ?? '';
5115a78cd34SGreg Roach
5126b9cb339SGreg Roach        $family = Registry::familyFactory()->make($xref, $tree);
513d45701ccSGreg Roach        $family = Auth::checkFamilyAccess($family);
514d45701ccSGreg Roach        $name   = $family->fullName();
5155a78cd34SGreg Roach
516d45701ccSGreg Roach        $options = [
51703f99a78SGreg Roach            self::ADD_RECORD_ONLY => $name,
518bbb76c12SGreg Roach            /* I18N: %s is a family (husband + wife) */
51903f99a78SGreg Roach            self::ADD_CHILDREN    => I18N::translate('%s and their children', $name),
520bbb76c12SGreg Roach            /* I18N: %s is a family (husband + wife) */
52103f99a78SGreg Roach            self::ADD_DESCENDANTS => I18N::translate('%s and their descendants', $name),
5225a78cd34SGreg Roach        ];
523d45701ccSGreg Roach
524d45701ccSGreg Roach        $title = I18N::translate('Add %s to the clippings cart', $name);
525d45701ccSGreg Roach
526d45701ccSGreg Roach        return $this->viewResponse('modules/clippings/add-options', [
527d45701ccSGreg Roach            'options' => $options,
528d45701ccSGreg Roach            'record'  => $family,
529d45701ccSGreg Roach            'title'   => $title,
530d45701ccSGreg Roach            'tree'    => $tree,
531d45701ccSGreg Roach        ]);
5325a78cd34SGreg Roach    }
5335a78cd34SGreg Roach
5345a78cd34SGreg Roach    /**
5356ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
5365a78cd34SGreg Roach     *
5376ccdf4f0SGreg Roach     * @return ResponseInterface
5385a78cd34SGreg Roach     */
53957ab2231SGreg Roach    public function postAddFamilyAction(ServerRequestInterface $request): ResponseInterface
540c1010edaSGreg Roach    {
54157ab2231SGreg Roach        $tree = $request->getAttribute('tree');
5424ea62551SGreg Roach        assert($tree instanceof Tree);
5434ea62551SGreg Roach
544b46c87bdSGreg Roach        $params = (array) $request->getParsedBody();
545b46c87bdSGreg Roach
546d45701ccSGreg Roach        $xref   = $params['xref'] ?? '';
547d45701ccSGreg Roach        $option = $params['option'] ?? '';
5485a78cd34SGreg Roach
5496b9cb339SGreg Roach        $family = Registry::familyFactory()->make($xref, $tree);
550d45701ccSGreg Roach        $family = Auth::checkFamilyAccess($family);
5515a78cd34SGreg Roach
5525a78cd34SGreg Roach        switch ($option) {
55303f99a78SGreg Roach            case self::ADD_RECORD_ONLY:
5545a78cd34SGreg Roach                $this->addFamilyToCart($family);
5555a78cd34SGreg Roach                break;
5565a78cd34SGreg Roach
55703f99a78SGreg Roach            case self::ADD_CHILDREN:
5585a78cd34SGreg Roach                $this->addFamilyAndChildrenToCart($family);
5595a78cd34SGreg Roach                break;
5605a78cd34SGreg Roach
56103f99a78SGreg Roach            case self::ADD_DESCENDANTS:
5625a78cd34SGreg Roach                $this->addFamilyAndDescendantsToCart($family);
5635a78cd34SGreg Roach                break;
5645a78cd34SGreg Roach        }
5655a78cd34SGreg Roach
5666ccdf4f0SGreg Roach        return redirect($family->url());
5675a78cd34SGreg Roach    }
5685a78cd34SGreg Roach
5695a78cd34SGreg Roach
5705a78cd34SGreg Roach    /**
5715a78cd34SGreg Roach     * @param Family $family
57218d7a90dSGreg Roach     *
57318d7a90dSGreg Roach     * @return void
5745a78cd34SGreg Roach     */
575d45701ccSGreg Roach    protected function addFamilyAndChildrenToCart(Family $family): void
576c1010edaSGreg Roach    {
577d45701ccSGreg Roach        $this->addFamilyToCart($family);
5785a78cd34SGreg Roach
57939ca88baSGreg Roach        foreach ($family->children() as $child) {
580d45701ccSGreg Roach            $this->addIndividualToCart($child);
5815a78cd34SGreg Roach        }
5825a78cd34SGreg Roach    }
5835a78cd34SGreg Roach
5845a78cd34SGreg Roach    /**
5855a78cd34SGreg Roach     * @param Family $family
58618d7a90dSGreg Roach     *
58718d7a90dSGreg Roach     * @return void
5885a78cd34SGreg Roach     */
589d45701ccSGreg Roach    protected function addFamilyAndDescendantsToCart(Family $family): void
590c1010edaSGreg Roach    {
591d45701ccSGreg Roach        $this->addFamilyAndChildrenToCart($family);
5925a78cd34SGreg Roach
59339ca88baSGreg Roach        foreach ($family->children() as $child) {
59439ca88baSGreg Roach            foreach ($child->spouseFamilies() as $child_family) {
5955a78cd34SGreg Roach                $this->addFamilyAndDescendantsToCart($child_family);
5965a78cd34SGreg Roach            }
5975a78cd34SGreg Roach        }
5985a78cd34SGreg Roach    }
5995a78cd34SGreg Roach
6005a78cd34SGreg Roach    /**
6016ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
6025a78cd34SGreg Roach     *
6036ccdf4f0SGreg Roach     * @return ResponseInterface
6045a78cd34SGreg Roach     */
60557ab2231SGreg Roach    public function getAddIndividualAction(ServerRequestInterface $request): ResponseInterface
606c1010edaSGreg Roach    {
60757ab2231SGreg Roach        $tree = $request->getAttribute('tree');
60875964c75SGreg Roach        assert($tree instanceof Tree);
6095229eadeSGreg Roach
610d45701ccSGreg Roach        $xref = $request->getQueryParams()['xref'] ?? '';
6115a78cd34SGreg Roach
6126b9cb339SGreg Roach        $individual = Registry::individualFactory()->make($xref, $tree);
613d45701ccSGreg Roach        $individual = Auth::checkIndividualAccess($individual);
614d45701ccSGreg Roach        $name       = $individual->fullName();
6155a78cd34SGreg Roach
61639ca88baSGreg Roach        if ($individual->sex() === 'F') {
617d45701ccSGreg Roach            $options = [
61803f99a78SGreg Roach                self::ADD_RECORD_ONLY       => $name,
61903f99a78SGreg Roach                self::ADD_PARENT_FAMILIES   => I18N::translate('%s, her parents and siblings', $name),
62003f99a78SGreg Roach                self::ADD_SPOUSE_FAMILIES   => I18N::translate('%s, her spouses and children', $name),
62103f99a78SGreg Roach                self::ADD_ANCESTORS         => I18N::translate('%s and her ancestors', $name),
62203f99a78SGreg Roach                self::ADD_ANCESTOR_FAMILIES => I18N::translate('%s, her ancestors and their families', $name),
62303f99a78SGreg Roach                self::ADD_DESCENDANTS       => I18N::translate('%s, her spouses and descendants', $name),
6245a78cd34SGreg Roach            ];
625d45701ccSGreg Roach        } else {
626d45701ccSGreg Roach            $options = [
62703f99a78SGreg Roach                self::ADD_RECORD_ONLY       => $name,
62803f99a78SGreg Roach                self::ADD_PARENT_FAMILIES   => I18N::translate('%s, his parents and siblings', $name),
62903f99a78SGreg Roach                self::ADD_SPOUSE_FAMILIES   => I18N::translate('%s, his spouses and children', $name),
63003f99a78SGreg Roach                self::ADD_ANCESTORS         => I18N::translate('%s and his ancestors', $name),
63103f99a78SGreg Roach                self::ADD_ANCESTOR_FAMILIES => I18N::translate('%s, his ancestors and their families', $name),
63203f99a78SGreg Roach                self::ADD_DESCENDANTS       => I18N::translate('%s, his spouses and descendants', $name),
6335a78cd34SGreg Roach            ];
6345a78cd34SGreg Roach        }
6355a78cd34SGreg Roach
636d45701ccSGreg Roach        $title = I18N::translate('Add %s to the clippings cart', $name);
637d45701ccSGreg Roach
638d45701ccSGreg Roach        return $this->viewResponse('modules/clippings/add-options', [
639d45701ccSGreg Roach            'options' => $options,
640d45701ccSGreg Roach            'record'  => $individual,
641d45701ccSGreg Roach            'title'   => $title,
642d45701ccSGreg Roach            'tree'    => $tree,
643d45701ccSGreg Roach        ]);
644d45701ccSGreg Roach    }
645d45701ccSGreg Roach
6465a78cd34SGreg Roach    /**
6476ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
6485a78cd34SGreg Roach     *
6496ccdf4f0SGreg Roach     * @return ResponseInterface
6505a78cd34SGreg Roach     */
65157ab2231SGreg Roach    public function postAddIndividualAction(ServerRequestInterface $request): ResponseInterface
652c1010edaSGreg Roach    {
65357ab2231SGreg Roach        $tree = $request->getAttribute('tree');
6544ea62551SGreg Roach        assert($tree instanceof Tree);
6554ea62551SGreg Roach
656b46c87bdSGreg Roach        $params = (array) $request->getParsedBody();
657b46c87bdSGreg Roach
658d45701ccSGreg Roach        $xref   = $params['xref'] ?? '';
659d45701ccSGreg Roach        $option = $params['option'] ?? '';
6605a78cd34SGreg Roach
6616b9cb339SGreg Roach        $individual = Registry::individualFactory()->make($xref, $tree);
662d45701ccSGreg Roach        $individual = Auth::checkIndividualAccess($individual);
6635a78cd34SGreg Roach
6645a78cd34SGreg Roach        switch ($option) {
66503f99a78SGreg Roach            case self::ADD_RECORD_ONLY:
666d45701ccSGreg Roach                $this->addIndividualToCart($individual);
6675a78cd34SGreg Roach                break;
6685a78cd34SGreg Roach
66903f99a78SGreg Roach            case self::ADD_PARENT_FAMILIES:
67039ca88baSGreg Roach                foreach ($individual->childFamilies() as $family) {
6715a78cd34SGreg Roach                    $this->addFamilyAndChildrenToCart($family);
6725a78cd34SGreg Roach                }
6735a78cd34SGreg Roach                break;
6745a78cd34SGreg Roach
67503f99a78SGreg Roach            case self::ADD_SPOUSE_FAMILIES:
67639ca88baSGreg Roach                foreach ($individual->spouseFamilies() as $family) {
6775a78cd34SGreg Roach                    $this->addFamilyAndChildrenToCart($family);
6785a78cd34SGreg Roach                }
6795a78cd34SGreg Roach                break;
6805a78cd34SGreg Roach
68103f99a78SGreg Roach            case self::ADD_ANCESTORS:
6825a78cd34SGreg Roach                $this->addAncestorsToCart($individual);
6835a78cd34SGreg Roach                break;
6845a78cd34SGreg Roach
68503f99a78SGreg Roach            case self::ADD_ANCESTOR_FAMILIES:
6865a78cd34SGreg Roach                $this->addAncestorFamiliesToCart($individual);
6875a78cd34SGreg Roach                break;
6885a78cd34SGreg Roach
68903f99a78SGreg Roach            case self::ADD_DESCENDANTS:
69039ca88baSGreg Roach                foreach ($individual->spouseFamilies() as $family) {
6915a78cd34SGreg Roach                    $this->addFamilyAndDescendantsToCart($family);
6925a78cd34SGreg Roach                }
6935a78cd34SGreg Roach                break;
6945a78cd34SGreg Roach        }
6955a78cd34SGreg Roach
6966ccdf4f0SGreg Roach        return redirect($individual->url());
6975a78cd34SGreg Roach    }
6985a78cd34SGreg Roach
6995a78cd34SGreg Roach    /**
7005a78cd34SGreg Roach     * @param Individual $individual
70118d7a90dSGreg Roach     *
70218d7a90dSGreg Roach     * @return void
7035a78cd34SGreg Roach     */
704d45701ccSGreg Roach    protected function addAncestorsToCart(Individual $individual): void
705c1010edaSGreg Roach    {
706d45701ccSGreg Roach        $this->addIndividualToCart($individual);
7075a78cd34SGreg Roach
70839ca88baSGreg Roach        foreach ($individual->childFamilies() as $family) {
709d45701ccSGreg Roach            $this->addFamilyToCart($family);
7108df4c68dSGreg Roach
71139ca88baSGreg Roach            foreach ($family->spouses() as $parent) {
7125a78cd34SGreg Roach                $this->addAncestorsToCart($parent);
7135a78cd34SGreg Roach            }
7145a78cd34SGreg Roach        }
7155a78cd34SGreg Roach    }
7165a78cd34SGreg Roach
7175a78cd34SGreg Roach    /**
7185a78cd34SGreg Roach     * @param Individual $individual
71918d7a90dSGreg Roach     *
72018d7a90dSGreg Roach     * @return void
7215a78cd34SGreg Roach     */
722d45701ccSGreg Roach    protected function addAncestorFamiliesToCart(Individual $individual): void
723c1010edaSGreg Roach    {
72439ca88baSGreg Roach        foreach ($individual->childFamilies() as $family) {
7255a78cd34SGreg Roach            $this->addFamilyAndChildrenToCart($family);
7268df4c68dSGreg Roach
72739ca88baSGreg Roach            foreach ($family->spouses() as $parent) {
728cad6d3f3SGreg Roach                $this->addAncestorFamiliesToCart($parent);
7295a78cd34SGreg Roach            }
7305a78cd34SGreg Roach        }
7315a78cd34SGreg Roach    }
7325a78cd34SGreg Roach
7335a78cd34SGreg Roach    /**
7346ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
7355a78cd34SGreg Roach     *
7366ccdf4f0SGreg Roach     * @return ResponseInterface
7375a78cd34SGreg Roach     */
738e8ded2caSGreg Roach    public function getAddLocationAction(ServerRequestInterface $request): ResponseInterface
739e8ded2caSGreg Roach    {
740e8ded2caSGreg Roach        $tree = $request->getAttribute('tree');
741e8ded2caSGreg Roach        assert($tree instanceof Tree);
742e8ded2caSGreg Roach
743e8ded2caSGreg Roach        $xref = $request->getQueryParams()['xref'] ?? '';
744e8ded2caSGreg Roach
745e8ded2caSGreg Roach        $location = Registry::locationFactory()->make($xref, $tree);
746e8ded2caSGreg Roach        $location = Auth::checkLocationAccess($location);
747e8ded2caSGreg Roach        $name     = $location->fullName();
748e8ded2caSGreg Roach
749e8ded2caSGreg Roach        $options = [
75003f99a78SGreg Roach            self::ADD_RECORD_ONLY => $name,
751e8ded2caSGreg Roach        ];
752e8ded2caSGreg Roach
753e8ded2caSGreg Roach        $title = I18N::translate('Add %s to the clippings cart', $name);
754e8ded2caSGreg Roach
755e8ded2caSGreg Roach        return $this->viewResponse('modules/clippings/add-options', [
756e8ded2caSGreg Roach            'options' => $options,
757e8ded2caSGreg Roach            'record'  => $location,
758e8ded2caSGreg Roach            'title'   => $title,
759e8ded2caSGreg Roach            'tree'    => $tree,
760e8ded2caSGreg Roach        ]);
761e8ded2caSGreg Roach    }
762e8ded2caSGreg Roach
763e8ded2caSGreg Roach    /**
764e8ded2caSGreg Roach     * @param ServerRequestInterface $request
765e8ded2caSGreg Roach     *
766e8ded2caSGreg Roach     * @return ResponseInterface
767e8ded2caSGreg Roach     */
768e8ded2caSGreg Roach    public function postAddLocationAction(ServerRequestInterface $request): ResponseInterface
769e8ded2caSGreg Roach    {
770e8ded2caSGreg Roach        $tree = $request->getAttribute('tree');
771e8ded2caSGreg Roach        assert($tree instanceof Tree);
772e8ded2caSGreg Roach
773e8ded2caSGreg Roach        $xref = $request->getQueryParams()['xref'] ?? '';
774e8ded2caSGreg Roach
775e8ded2caSGreg Roach        $location = Registry::locationFactory()->make($xref, $tree);
776e8ded2caSGreg Roach        $location = Auth::checkLocationAccess($location);
777e8ded2caSGreg Roach
778e8ded2caSGreg Roach        $this->addLocationToCart($location);
779e8ded2caSGreg Roach
780e8ded2caSGreg Roach        return redirect($location->url());
781e8ded2caSGreg Roach    }
782e8ded2caSGreg Roach
783e8ded2caSGreg Roach    /**
784e8ded2caSGreg Roach     * @param ServerRequestInterface $request
785e8ded2caSGreg Roach     *
786e8ded2caSGreg Roach     * @return ResponseInterface
787e8ded2caSGreg Roach     */
78857ab2231SGreg Roach    public function getAddMediaAction(ServerRequestInterface $request): ResponseInterface
789c1010edaSGreg Roach    {
79057ab2231SGreg Roach        $tree = $request->getAttribute('tree');
79175964c75SGreg Roach        assert($tree instanceof Tree);
7925229eadeSGreg Roach
793d45701ccSGreg Roach        $xref = $request->getQueryParams()['xref'] ?? '';
7945a78cd34SGreg Roach
7956b9cb339SGreg Roach        $media = Registry::mediaFactory()->make($xref, $tree);
796d45701ccSGreg Roach        $media = Auth::checkMediaAccess($media);
797d45701ccSGreg Roach        $name  = $media->fullName();
7985a78cd34SGreg Roach
799d45701ccSGreg Roach        $options = [
80003f99a78SGreg Roach            self::ADD_RECORD_ONLY => $name,
801d45701ccSGreg Roach        ];
8025a78cd34SGreg Roach
803d45701ccSGreg Roach        $title = I18N::translate('Add %s to the clippings cart', $name);
8045a78cd34SGreg Roach
8055a78cd34SGreg Roach        return $this->viewResponse('modules/clippings/add-options', [
8065a78cd34SGreg Roach            'options' => $options,
8075a78cd34SGreg Roach            'record'  => $media,
8085a78cd34SGreg Roach            'title'   => $title,
8095a78cd34SGreg Roach            'tree'    => $tree,
8105a78cd34SGreg Roach        ]);
8115a78cd34SGreg Roach    }
8125a78cd34SGreg Roach
8135a78cd34SGreg Roach    /**
8146ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
8155a78cd34SGreg Roach     *
8166ccdf4f0SGreg Roach     * @return ResponseInterface
8175a78cd34SGreg Roach     */
81857ab2231SGreg Roach    public function postAddMediaAction(ServerRequestInterface $request): ResponseInterface
819c1010edaSGreg Roach    {
82057ab2231SGreg Roach        $tree = $request->getAttribute('tree');
82175964c75SGreg Roach        assert($tree instanceof Tree);
8225229eadeSGreg Roach
823d45701ccSGreg Roach        $xref = $request->getQueryParams()['xref'] ?? '';
8245a78cd34SGreg Roach
8256b9cb339SGreg Roach        $media = Registry::mediaFactory()->make($xref, $tree);
826d45701ccSGreg Roach        $media = Auth::checkMediaAccess($media);
8275a78cd34SGreg Roach
828d45701ccSGreg Roach        $this->addMediaToCart($media);
8295a78cd34SGreg Roach
8306ccdf4f0SGreg Roach        return redirect($media->url());
8315a78cd34SGreg Roach    }
8325a78cd34SGreg Roach
8335a78cd34SGreg Roach    /**
8346ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
8355a78cd34SGreg Roach     *
8366ccdf4f0SGreg Roach     * @return ResponseInterface
8375a78cd34SGreg Roach     */
83857ab2231SGreg Roach    public function getAddNoteAction(ServerRequestInterface $request): ResponseInterface
839c1010edaSGreg Roach    {
84057ab2231SGreg Roach        $tree = $request->getAttribute('tree');
84175964c75SGreg Roach        assert($tree instanceof Tree);
8425229eadeSGreg Roach
843d45701ccSGreg Roach        $xref = $request->getQueryParams()['xref'] ?? '';
8445a78cd34SGreg Roach
8456b9cb339SGreg Roach        $note = Registry::noteFactory()->make($xref, $tree);
846d45701ccSGreg Roach        $note = Auth::checkNoteAccess($note);
847d45701ccSGreg Roach        $name = $note->fullName();
8485a78cd34SGreg Roach
849d45701ccSGreg Roach        $options = [
85003f99a78SGreg Roach            self::ADD_RECORD_ONLY => $name,
851d45701ccSGreg Roach        ];
8525a78cd34SGreg Roach
853d45701ccSGreg Roach        $title = I18N::translate('Add %s to the clippings cart', $name);
8545a78cd34SGreg Roach
8555a78cd34SGreg Roach        return $this->viewResponse('modules/clippings/add-options', [
8565a78cd34SGreg Roach            'options' => $options,
8575a78cd34SGreg Roach            'record'  => $note,
8585a78cd34SGreg Roach            'title'   => $title,
8595a78cd34SGreg Roach            'tree'    => $tree,
8605a78cd34SGreg Roach        ]);
8615a78cd34SGreg Roach    }
8625a78cd34SGreg Roach
8635a78cd34SGreg Roach    /**
8646ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
8655a78cd34SGreg Roach     *
8666ccdf4f0SGreg Roach     * @return ResponseInterface
8675a78cd34SGreg Roach     */
86857ab2231SGreg Roach    public function postAddNoteAction(ServerRequestInterface $request): ResponseInterface
869c1010edaSGreg Roach    {
87057ab2231SGreg Roach        $tree = $request->getAttribute('tree');
87175964c75SGreg Roach        assert($tree instanceof Tree);
8725229eadeSGreg Roach
873d45701ccSGreg Roach        $xref = $request->getQueryParams()['xref'] ?? '';
8745a78cd34SGreg Roach
8756b9cb339SGreg Roach        $note = Registry::noteFactory()->make($xref, $tree);
876d45701ccSGreg Roach        $note = Auth::checkNoteAccess($note);
8775a78cd34SGreg Roach
878d45701ccSGreg Roach        $this->addNoteToCart($note);
8795a78cd34SGreg Roach
8806ccdf4f0SGreg Roach        return redirect($note->url());
8815a78cd34SGreg Roach    }
8825a78cd34SGreg Roach
8835a78cd34SGreg Roach    /**
8846ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
8855a78cd34SGreg Roach     *
8866ccdf4f0SGreg Roach     * @return ResponseInterface
8875a78cd34SGreg Roach     */
88857ab2231SGreg Roach    public function getAddRepositoryAction(ServerRequestInterface $request): ResponseInterface
889c1010edaSGreg Roach    {
89057ab2231SGreg Roach        $tree = $request->getAttribute('tree');
89175964c75SGreg Roach        assert($tree instanceof Tree);
8925229eadeSGreg Roach
893d45701ccSGreg Roach        $xref = $request->getQueryParams()['xref'] ?? '';
8945a78cd34SGreg Roach
8956b9cb339SGreg Roach        $repository = Registry::repositoryFactory()->make($xref, $tree);
896d45701ccSGreg Roach        $repository = Auth::checkRepositoryAccess($repository);
897d45701ccSGreg Roach        $name       = $repository->fullName();
8985a78cd34SGreg Roach
899d45701ccSGreg Roach        $options = [
90003f99a78SGreg Roach            self::ADD_RECORD_ONLY => $name,
901d45701ccSGreg Roach        ];
9025a78cd34SGreg Roach
903d45701ccSGreg Roach        $title = I18N::translate('Add %s to the clippings cart', $name);
9045a78cd34SGreg Roach
9055a78cd34SGreg Roach        return $this->viewResponse('modules/clippings/add-options', [
9065a78cd34SGreg Roach            'options' => $options,
9075a78cd34SGreg Roach            'record'  => $repository,
9085a78cd34SGreg Roach            'title'   => $title,
9095a78cd34SGreg Roach            'tree'    => $tree,
9105a78cd34SGreg Roach        ]);
9115a78cd34SGreg Roach    }
9125a78cd34SGreg Roach
9135a78cd34SGreg Roach    /**
9146ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
9155a78cd34SGreg Roach     *
9166ccdf4f0SGreg Roach     * @return ResponseInterface
9175a78cd34SGreg Roach     */
91857ab2231SGreg Roach    public function postAddRepositoryAction(ServerRequestInterface $request): ResponseInterface
919c1010edaSGreg Roach    {
92057ab2231SGreg Roach        $tree = $request->getAttribute('tree');
92175964c75SGreg Roach        assert($tree instanceof Tree);
9225229eadeSGreg Roach
923d45701ccSGreg Roach        $xref = $request->getQueryParams()['xref'] ?? '';
9245a78cd34SGreg Roach
9256b9cb339SGreg Roach        $repository = Registry::repositoryFactory()->make($xref, $tree);
926d45701ccSGreg Roach        $repository = Auth::checkRepositoryAccess($repository);
9275a78cd34SGreg Roach
928d45701ccSGreg Roach        $this->addRepositoryToCart($repository);
929d45701ccSGreg Roach
930d45701ccSGreg Roach        foreach ($repository->linkedSources('REPO') as $source) {
931d45701ccSGreg Roach            $this->addSourceToCart($source);
9325a78cd34SGreg Roach        }
9335a78cd34SGreg Roach
9346ccdf4f0SGreg Roach        return redirect($repository->url());
9355a78cd34SGreg Roach    }
9365a78cd34SGreg Roach
9375a78cd34SGreg Roach    /**
9386ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
9395a78cd34SGreg Roach     *
9406ccdf4f0SGreg Roach     * @return ResponseInterface
9415a78cd34SGreg Roach     */
94257ab2231SGreg Roach    public function getAddSourceAction(ServerRequestInterface $request): ResponseInterface
943c1010edaSGreg Roach    {
94457ab2231SGreg Roach        $tree = $request->getAttribute('tree');
94575964c75SGreg Roach        assert($tree instanceof Tree);
9465229eadeSGreg Roach
947d45701ccSGreg Roach        $xref = $request->getQueryParams()['xref'] ?? '';
9485a78cd34SGreg Roach
9496b9cb339SGreg Roach        $source = Registry::sourceFactory()->make($xref, $tree);
950d45701ccSGreg Roach        $source = Auth::checkSourceAccess($source);
951d45701ccSGreg Roach        $name   = $source->fullName();
9525a78cd34SGreg Roach
953d45701ccSGreg Roach        $options = [
95403f99a78SGreg Roach            self::ADD_RECORD_ONLY        => $name,
95503f99a78SGreg Roach            self::ADD_LINKED_INDIVIDUALS => I18N::translate('%s and the individuals that reference it.', $name),
956d45701ccSGreg Roach        ];
9575a78cd34SGreg Roach
958d45701ccSGreg Roach        $title = I18N::translate('Add %s to the clippings cart', $name);
9595a78cd34SGreg Roach
9605a78cd34SGreg Roach        return $this->viewResponse('modules/clippings/add-options', [
9615a78cd34SGreg Roach            'options' => $options,
9625a78cd34SGreg Roach            'record'  => $source,
9635a78cd34SGreg Roach            'title'   => $title,
9645a78cd34SGreg Roach            'tree'    => $tree,
9655a78cd34SGreg Roach        ]);
9665a78cd34SGreg Roach    }
9675a78cd34SGreg Roach
9685a78cd34SGreg Roach    /**
9696ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
9705a78cd34SGreg Roach     *
9716ccdf4f0SGreg Roach     * @return ResponseInterface
9725a78cd34SGreg Roach     */
97357ab2231SGreg Roach    public function postAddSourceAction(ServerRequestInterface $request): ResponseInterface
974c1010edaSGreg Roach    {
97557ab2231SGreg Roach        $tree = $request->getAttribute('tree');
97675964c75SGreg Roach        assert($tree instanceof Tree);
9775229eadeSGreg Roach
978b46c87bdSGreg Roach        $params = (array) $request->getParsedBody();
979b46c87bdSGreg Roach
980d45701ccSGreg Roach        $xref   = $params['xref'] ?? '';
981d45701ccSGreg Roach        $option = $params['option'] ?? '';
9825a78cd34SGreg Roach
9836b9cb339SGreg Roach        $source = Registry::sourceFactory()->make($xref, $tree);
984d45701ccSGreg Roach        $source = Auth::checkSourceAccess($source);
9855a78cd34SGreg Roach
986d45701ccSGreg Roach        $this->addSourceToCart($source);
9875a78cd34SGreg Roach
98803f99a78SGreg Roach        if ($option === self::ADD_LINKED_INDIVIDUALS) {
9895a78cd34SGreg Roach            foreach ($source->linkedIndividuals('SOUR') as $individual) {
990d45701ccSGreg Roach                $this->addIndividualToCart($individual);
9915a78cd34SGreg Roach            }
9925a78cd34SGreg Roach            foreach ($source->linkedFamilies('SOUR') as $family) {
993d45701ccSGreg Roach                $this->addFamilyToCart($family);
9945a78cd34SGreg Roach            }
9955a78cd34SGreg Roach        }
9965a78cd34SGreg Roach
9976ccdf4f0SGreg Roach        return redirect($source->url());
9985a78cd34SGreg Roach    }
9995a78cd34SGreg Roach
10005a78cd34SGreg Roach    /**
1001d45701ccSGreg Roach     * @param ServerRequestInterface $request
10025a78cd34SGreg Roach     *
1003d45701ccSGreg Roach     * @return ResponseInterface
10045a78cd34SGreg Roach     */
1005d45701ccSGreg Roach    public function getAddSubmitterAction(ServerRequestInterface $request): ResponseInterface
1006c1010edaSGreg Roach    {
1007d45701ccSGreg Roach        $tree = $request->getAttribute('tree');
1008d45701ccSGreg Roach        assert($tree instanceof Tree);
10095a78cd34SGreg Roach
1010d45701ccSGreg Roach        $xref = $request->getQueryParams()['xref'] ?? '';
10115a78cd34SGreg Roach
1012d45701ccSGreg Roach        $submitter = Registry::submitterFactory()->make($xref, $tree);
1013d45701ccSGreg Roach        $submitter = Auth::checkSubmitterAccess($submitter);
1014d45701ccSGreg Roach        $name      = $submitter->fullName();
10155a78cd34SGreg Roach
1016d45701ccSGreg Roach        $options = [
101703f99a78SGreg Roach            self::ADD_RECORD_ONLY => $name,
1018d45701ccSGreg Roach        ];
10195a78cd34SGreg Roach
1020d45701ccSGreg Roach        $title = I18N::translate('Add %s to the clippings cart', $name);
10215a78cd34SGreg Roach
1022d45701ccSGreg Roach        return $this->viewResponse('modules/clippings/add-options', [
1023d45701ccSGreg Roach            'options' => $options,
1024d45701ccSGreg Roach            'record'  => $submitter,
1025d45701ccSGreg Roach            'title'   => $title,
1026d45701ccSGreg Roach            'tree'    => $tree,
1027d45701ccSGreg Roach        ]);
10285a78cd34SGreg Roach    }
10295a78cd34SGreg Roach
10305a78cd34SGreg Roach    /**
1031d45701ccSGreg Roach     * @param ServerRequestInterface $request
10325a78cd34SGreg Roach     *
1033d45701ccSGreg Roach     * @return ResponseInterface
10345a78cd34SGreg Roach     */
1035d45701ccSGreg Roach    public function postAddSubmitterAction(ServerRequestInterface $request): ResponseInterface
1036d45701ccSGreg Roach    {
1037d45701ccSGreg Roach        $tree = $request->getAttribute('tree');
1038d45701ccSGreg Roach        assert($tree instanceof Tree);
1039d45701ccSGreg Roach
1040d45701ccSGreg Roach        $xref = $request->getQueryParams()['xref'] ?? '';
1041d45701ccSGreg Roach
1042d45701ccSGreg Roach        $submitter = Registry::submitterFactory()->make($xref, $tree);
1043d45701ccSGreg Roach        $submitter = Auth::checkSubmitterAccess($submitter);
1044d45701ccSGreg Roach
1045d45701ccSGreg Roach        $this->addSubmitterToCart($submitter);
1046d45701ccSGreg Roach
1047d45701ccSGreg Roach        return redirect($submitter->url());
1048d45701ccSGreg Roach    }
1049d45701ccSGreg Roach
1050d45701ccSGreg Roach    /**
1051d45701ccSGreg Roach     * @param Family $family
1052d45701ccSGreg Roach     */
1053d45701ccSGreg Roach    protected function addFamilyToCart(Family $family): void
1054c1010edaSGreg Roach    {
10555a78cd34SGreg Roach        $cart = Session::get('cart', []);
1056d45701ccSGreg Roach        $tree = $family->tree()->name();
1057d45701ccSGreg Roach        $xref = $family->xref();
10585a78cd34SGreg Roach
1059d45701ccSGreg Roach        if (($cart[$tree][$xref] ?? false) === false) {
1060d45701ccSGreg Roach            $cart[$tree][$xref] = true;
10615a78cd34SGreg Roach
1062d45701ccSGreg Roach            Session::put('cart', $cart);
10635a78cd34SGreg Roach
1064d45701ccSGreg Roach            foreach ($family->spouses() as $spouse) {
1065d45701ccSGreg Roach                $this->addIndividualToCart($spouse);
10665a78cd34SGreg Roach            }
10675a78cd34SGreg Roach
1068e8ded2caSGreg Roach            $this->addLocationLinksToCart($family);
1069d45701ccSGreg Roach            $this->addMediaLinksToCart($family);
1070d45701ccSGreg Roach            $this->addNoteLinksToCart($family);
1071d45701ccSGreg Roach            $this->addSourceLinksToCart($family);
1072d45701ccSGreg Roach            $this->addSubmitterLinksToCart($family);
1073d45701ccSGreg Roach        }
1074d45701ccSGreg Roach    }
1075d45701ccSGreg Roach
1076d45701ccSGreg Roach    /**
1077d45701ccSGreg Roach     * @param Individual $individual
1078d45701ccSGreg Roach     */
1079d45701ccSGreg Roach    protected function addIndividualToCart(Individual $individual): void
1080d45701ccSGreg Roach    {
1081d45701ccSGreg Roach        $cart = Session::get('cart', []);
1082d45701ccSGreg Roach        $tree = $individual->tree()->name();
1083d45701ccSGreg Roach        $xref = $individual->xref();
1084d45701ccSGreg Roach
1085d45701ccSGreg Roach        if (($cart[$tree][$xref] ?? false) === false) {
1086d45701ccSGreg Roach            $cart[$tree][$xref] = true;
1087d45701ccSGreg Roach
1088d45701ccSGreg Roach            Session::put('cart', $cart);
1089d45701ccSGreg Roach
1090e8ded2caSGreg Roach            $this->addLocationLinksToCart($individual);
1091d45701ccSGreg Roach            $this->addMediaLinksToCart($individual);
1092d45701ccSGreg Roach            $this->addNoteLinksToCart($individual);
1093d45701ccSGreg Roach            $this->addSourceLinksToCart($individual);
1094d45701ccSGreg Roach        }
1095d45701ccSGreg Roach    }
1096d45701ccSGreg Roach
1097d45701ccSGreg Roach    /**
1098e8ded2caSGreg Roach     * @param Location $location
1099e8ded2caSGreg Roach     */
1100e8ded2caSGreg Roach    protected function addLocationToCart(Location $location): void
1101e8ded2caSGreg Roach    {
1102e8ded2caSGreg Roach        $cart = Session::get('cart', []);
1103e8ded2caSGreg Roach        $tree = $location->tree()->name();
1104e8ded2caSGreg Roach        $xref = $location->xref();
1105e8ded2caSGreg Roach
1106e8ded2caSGreg Roach        if (($cart[$tree][$xref] ?? false) === false) {
1107e8ded2caSGreg Roach            $cart[$tree][$xref] = true;
1108e8ded2caSGreg Roach
1109e8ded2caSGreg Roach            Session::put('cart', $cart);
1110e8ded2caSGreg Roach
1111e8ded2caSGreg Roach            $this->addLocationLinksToCart($location);
1112e8ded2caSGreg Roach            $this->addMediaLinksToCart($location);
1113e8ded2caSGreg Roach            $this->addNoteLinksToCart($location);
1114e8ded2caSGreg Roach            $this->addSourceLinksToCart($location);
1115e8ded2caSGreg Roach        }
1116e8ded2caSGreg Roach    }
1117e8ded2caSGreg Roach
1118e8ded2caSGreg Roach    /**
1119e8ded2caSGreg Roach     * @param GedcomRecord $record
1120e8ded2caSGreg Roach     */
1121e8ded2caSGreg Roach    protected function addLocationLinksToCart(GedcomRecord $record): void
1122e8ded2caSGreg Roach    {
1123e8ded2caSGreg Roach        preg_match_all('/\n\d _LOC @(' . Gedcom::REGEX_XREF . ')@/', $record->gedcom(), $matches);
1124e8ded2caSGreg Roach
1125e8ded2caSGreg Roach        foreach ($matches[1] as $xref) {
1126e8ded2caSGreg Roach            $location = Registry::locationFactory()->make($xref, $record->tree());
1127e8ded2caSGreg Roach
1128e8ded2caSGreg Roach            if ($location instanceof Location && $location->canShow()) {
1129e8ded2caSGreg Roach                $this->addLocationToCart($location);
1130e8ded2caSGreg Roach            }
1131e8ded2caSGreg Roach        }
1132e8ded2caSGreg Roach    }
1133e8ded2caSGreg Roach
1134e8ded2caSGreg Roach    /**
1135d45701ccSGreg Roach     * @param Media $media
1136d45701ccSGreg Roach     */
1137d45701ccSGreg Roach    protected function addMediaToCart(Media $media): void
1138d45701ccSGreg Roach    {
1139d45701ccSGreg Roach        $cart = Session::get('cart', []);
1140d45701ccSGreg Roach        $tree = $media->tree()->name();
1141d45701ccSGreg Roach        $xref = $media->xref();
1142d45701ccSGreg Roach
1143d45701ccSGreg Roach        if (($cart[$tree][$xref] ?? false) === false) {
1144d45701ccSGreg Roach            $cart[$tree][$xref] = true;
1145d45701ccSGreg Roach
1146d45701ccSGreg Roach            Session::put('cart', $cart);
1147d45701ccSGreg Roach
1148d45701ccSGreg Roach            $this->addNoteLinksToCart($media);
1149d45701ccSGreg Roach        }
1150d45701ccSGreg Roach    }
1151d45701ccSGreg Roach
1152d45701ccSGreg Roach    /**
1153d45701ccSGreg Roach     * @param GedcomRecord $record
1154d45701ccSGreg Roach     */
1155d45701ccSGreg Roach    protected function addMediaLinksToCart(GedcomRecord $record): void
1156d45701ccSGreg Roach    {
1157d45701ccSGreg Roach        preg_match_all('/\n\d OBJE @(' . Gedcom::REGEX_XREF . ')@/', $record->gedcom(), $matches);
1158d45701ccSGreg Roach
1159d45701ccSGreg Roach        foreach ($matches[1] as $xref) {
1160d45701ccSGreg Roach            $media = Registry::mediaFactory()->make($xref, $record->tree());
1161d45701ccSGreg Roach
1162d45701ccSGreg Roach            if ($media instanceof Media && $media->canShow()) {
1163d45701ccSGreg Roach                $this->addMediaToCart($media);
1164d45701ccSGreg Roach            }
1165d45701ccSGreg Roach        }
1166d45701ccSGreg Roach    }
1167d45701ccSGreg Roach
1168d45701ccSGreg Roach    /**
1169d45701ccSGreg Roach     * @param Note $note
1170d45701ccSGreg Roach     */
1171d45701ccSGreg Roach    protected function addNoteToCart(Note $note): void
1172d45701ccSGreg Roach    {
1173d45701ccSGreg Roach        $cart = Session::get('cart', []);
1174d45701ccSGreg Roach        $tree = $note->tree()->name();
1175d45701ccSGreg Roach        $xref = $note->xref();
1176d45701ccSGreg Roach
1177d45701ccSGreg Roach        if (($cart[$tree][$xref] ?? false) === false) {
1178d45701ccSGreg Roach            $cart[$tree][$xref] = true;
1179d45701ccSGreg Roach
11805a78cd34SGreg Roach            Session::put('cart', $cart);
11815a78cd34SGreg Roach        }
1182d45701ccSGreg Roach    }
11835a78cd34SGreg Roach
11845a78cd34SGreg Roach    /**
1185d45701ccSGreg Roach     * @param GedcomRecord $record
11865a78cd34SGreg Roach     */
1187d45701ccSGreg Roach    protected function addNoteLinksToCart(GedcomRecord $record): void
1188d45701ccSGreg Roach    {
1189d45701ccSGreg Roach        preg_match_all('/\n\d NOTE @(' . Gedcom::REGEX_XREF . ')@/', $record->gedcom(), $matches);
1190d45701ccSGreg Roach
1191d45701ccSGreg Roach        foreach ($matches[1] as $xref) {
1192d45701ccSGreg Roach            $note = Registry::noteFactory()->make($xref, $record->tree());
1193d45701ccSGreg Roach
1194d45701ccSGreg Roach            if ($note instanceof Note && $note->canShow()) {
1195d45701ccSGreg Roach                $this->addNoteToCart($note);
1196d45701ccSGreg Roach            }
1197d45701ccSGreg Roach        }
1198d45701ccSGreg Roach    }
1199d45701ccSGreg Roach
1200d45701ccSGreg Roach    /**
1201d45701ccSGreg Roach     * @param Source $source
1202d45701ccSGreg Roach     */
1203d45701ccSGreg Roach    protected function addSourceToCart(Source $source): void
1204c1010edaSGreg Roach    {
12055a78cd34SGreg Roach        $cart = Session::get('cart', []);
1206d45701ccSGreg Roach        $tree = $source->tree()->name();
1207d45701ccSGreg Roach        $xref = $source->xref();
12085a78cd34SGreg Roach
1209d45701ccSGreg Roach        if (($cart[$tree][$xref] ?? false) === false) {
1210d45701ccSGreg Roach            $cart[$tree][$xref] = true;
1211d45701ccSGreg Roach
1212d45701ccSGreg Roach            Session::put('cart', $cart);
1213d45701ccSGreg Roach
1214d45701ccSGreg Roach            $this->addNoteLinksToCart($source);
1215d45701ccSGreg Roach            $this->addRepositoryLinksToCart($source);
1216d45701ccSGreg Roach        }
1217d45701ccSGreg Roach    }
1218d45701ccSGreg Roach
1219d45701ccSGreg Roach    /**
1220d45701ccSGreg Roach     * @param GedcomRecord $record
1221d45701ccSGreg Roach     */
1222d45701ccSGreg Roach    protected function addSourceLinksToCart(GedcomRecord $record): void
1223d45701ccSGreg Roach    {
1224d45701ccSGreg Roach        preg_match_all('/\n\d SOUR @(' . Gedcom::REGEX_XREF . ')@/', $record->gedcom(), $matches);
1225d45701ccSGreg Roach
1226d45701ccSGreg Roach        foreach ($matches[1] as $xref) {
1227d45701ccSGreg Roach            $source = Registry::sourceFactory()->make($xref, $record->tree());
1228d45701ccSGreg Roach
1229d45701ccSGreg Roach            if ($source instanceof Source && $source->canShow()) {
1230d45701ccSGreg Roach                $this->addSourceToCart($source);
1231d45701ccSGreg Roach            }
1232d45701ccSGreg Roach        }
1233d45701ccSGreg Roach    }
1234d45701ccSGreg Roach
1235d45701ccSGreg Roach    /**
1236d45701ccSGreg Roach     * @param Repository $repository
1237d45701ccSGreg Roach     */
1238d45701ccSGreg Roach    protected function addRepositoryToCart(Repository $repository): void
1239d45701ccSGreg Roach    {
1240d45701ccSGreg Roach        $cart = Session::get('cart', []);
1241d45701ccSGreg Roach        $tree = $repository->tree()->name();
1242d45701ccSGreg Roach        $xref = $repository->xref();
1243d45701ccSGreg Roach
1244d45701ccSGreg Roach        if (($cart[$tree][$xref] ?? false) === false) {
1245d45701ccSGreg Roach            $cart[$tree][$xref] = true;
1246d45701ccSGreg Roach
1247d45701ccSGreg Roach            Session::put('cart', $cart);
1248d45701ccSGreg Roach
1249d45701ccSGreg Roach            $this->addNoteLinksToCart($repository);
1250d45701ccSGreg Roach        }
1251d45701ccSGreg Roach    }
1252d45701ccSGreg Roach
1253d45701ccSGreg Roach    /**
1254d45701ccSGreg Roach     * @param GedcomRecord $record
1255d45701ccSGreg Roach     */
1256d45701ccSGreg Roach    protected function addRepositoryLinksToCart(GedcomRecord $record): void
1257d45701ccSGreg Roach    {
1258d45701ccSGreg Roach        preg_match_all('/\n\d REPO @(' . Gedcom::REGEX_XREF . '@)/', $record->gedcom(), $matches);
1259d45701ccSGreg Roach
1260d45701ccSGreg Roach        foreach ($matches[1] as $xref) {
1261d45701ccSGreg Roach            $repository = Registry::repositoryFactory()->make($xref, $record->tree());
1262d45701ccSGreg Roach
1263d45701ccSGreg Roach            if ($repository instanceof Repository && $repository->canShow()) {
1264d45701ccSGreg Roach                $this->addRepositoryToCart($repository);
1265d45701ccSGreg Roach            }
1266d45701ccSGreg Roach        }
1267d45701ccSGreg Roach    }
1268d45701ccSGreg Roach
1269d45701ccSGreg Roach    /**
1270d45701ccSGreg Roach     * @param Submitter $submitter
1271d45701ccSGreg Roach     */
1272d45701ccSGreg Roach    protected function addSubmitterToCart(Submitter $submitter): void
1273d45701ccSGreg Roach    {
1274d45701ccSGreg Roach        $cart = Session::get('cart', []);
1275d45701ccSGreg Roach        $tree = $submitter->tree()->name();
1276d45701ccSGreg Roach        $xref = $submitter->xref();
1277d45701ccSGreg Roach
1278d45701ccSGreg Roach        if (($cart[$tree][$xref] ?? false) === false) {
1279d45701ccSGreg Roach            $cart[$tree][$xref] = true;
1280d45701ccSGreg Roach
1281d45701ccSGreg Roach            Session::put('cart', $cart);
1282d45701ccSGreg Roach
1283d45701ccSGreg Roach            $this->addNoteLinksToCart($submitter);
1284d45701ccSGreg Roach        }
1285d45701ccSGreg Roach    }
1286d45701ccSGreg Roach
1287d45701ccSGreg Roach    /**
1288d45701ccSGreg Roach     * @param GedcomRecord $record
1289d45701ccSGreg Roach     */
1290d45701ccSGreg Roach    protected function addSubmitterLinksToCart(GedcomRecord $record): void
1291d45701ccSGreg Roach    {
1292d45701ccSGreg Roach        preg_match_all('/\n\d SUBM @(' . Gedcom::REGEX_XREF . ')@/', $record->gedcom(), $matches);
1293d45701ccSGreg Roach
1294d45701ccSGreg Roach        foreach ($matches[1] as $xref) {
1295d45701ccSGreg Roach            $submitter = Registry::submitterFactory()->make($xref, $record->tree());
1296d45701ccSGreg Roach
1297d45701ccSGreg Roach            if ($submitter instanceof Submitter && $submitter->canShow()) {
1298d45701ccSGreg Roach                $this->addSubmitterToCart($submitter);
1299d45701ccSGreg Roach            }
1300d45701ccSGreg Roach        }
13015a78cd34SGreg Roach    }
13028c2e8227SGreg Roach}
1303