xref: /webtrees/app/Module/ClippingsCartModule.php (revision ca47b2ada94c22f0e53abe22300f5c1c43240913)
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;
5400c45d23SGreg Roachuse Psr\Http\Message\ResponseFactoryInterface;
556ccdf4f0SGreg Roachuse Psr\Http\Message\ResponseInterface;
566ccdf4f0SGreg Roachuse Psr\Http\Message\ServerRequestInterface;
5700c45d23SGreg 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
113*ca47b2adSGreg Roach    private GedcomExportService $gedcom_export_service;
11469c05a6eSGreg Roach
115*ca47b2adSGreg Roach    private UserService $user_service;
116*ca47b2adSGreg Roach
117*ca47b2adSGreg Roach    private ResponseFactoryInterface $response_factory;
118*ca47b2adSGreg Roach
119*ca47b2adSGreg Roach    private StreamFactoryInterface $stream_factory;
120e5a6b4d4SGreg Roach
121e5a6b4d4SGreg Roach    /**
122e5a6b4d4SGreg Roach     * ClippingsCartModule constructor.
123e5a6b4d4SGreg Roach     *
12469c05a6eSGreg Roach     * @param GedcomExportService      $gedcom_export_service
125*ca47b2adSGreg Roach     * @param ResponseFactoryInterface $response_factory
126*ca47b2adSGreg Roach     * @param StreamFactoryInterface   $stream_factory
127e5a6b4d4SGreg Roach     * @param UserService              $user_service
128e5a6b4d4SGreg Roach     */
129*ca47b2adSGreg Roach    public function __construct(
130*ca47b2adSGreg Roach        GedcomExportService $gedcom_export_service,
131*ca47b2adSGreg Roach        ResponseFactoryInterface $response_factory,
132*ca47b2adSGreg Roach        StreamFactoryInterface $stream_factory,
133*ca47b2adSGreg Roach        UserService $user_service
134*ca47b2adSGreg Roach    ) {
13569c05a6eSGreg Roach        $this->gedcom_export_service = $gedcom_export_service;
136*ca47b2adSGreg Roach        $this->response_factory      = $response_factory;
137*ca47b2adSGreg Roach        $this->stream_factory        = $stream_factory;
138e5a6b4d4SGreg Roach        $this->user_service          = $user_service;
139e5a6b4d4SGreg Roach    }
140e5a6b4d4SGreg Roach
141e5a6b4d4SGreg Roach    /**
142961ec755SGreg Roach     * A sentence describing what this module does.
143961ec755SGreg Roach     *
144961ec755SGreg Roach     * @return string
145961ec755SGreg Roach     */
14649a243cbSGreg Roach    public function description(): string
147c1010edaSGreg Roach    {
148bbb76c12SGreg Roach        /* I18N: Description of the “Clippings cart” module */
149bbb76c12SGreg Roach        return I18N::translate('Select records from your family tree and save them as a GEDCOM file.');
1508c2e8227SGreg Roach    }
1518c2e8227SGreg Roach
1520ee13198SGreg Roach    /**
15349a243cbSGreg Roach     * The default position for this menu.  It can be changed in the control panel.
1540ee13198SGreg Roach     *
1550ee13198SGreg Roach     * @return int
1560ee13198SGreg Roach     */
1578f53f488SRico Sonntag    public function defaultMenuOrder(): int
158c1010edaSGreg Roach    {
159353b36abSGreg Roach        return 6;
1608c2e8227SGreg Roach    }
1618c2e8227SGreg Roach
1620ee13198SGreg Roach    /**
1630ee13198SGreg Roach     * A menu, to be added to the main application menu.
1640ee13198SGreg Roach     *
165aee13b6dSGreg Roach     * @param Tree $tree
166aee13b6dSGreg Roach     *
1670ee13198SGreg Roach     * @return Menu|null
1680ee13198SGreg Roach     */
16946295629SGreg Roach    public function getMenu(Tree $tree): ?Menu
170c1010edaSGreg Roach    {
171eb235819SGreg Roach        /** @var ServerRequestInterface $request */
1726ccdf4f0SGreg Roach        $request = app(ServerRequestInterface::class);
1738c2e8227SGreg Roach
174f7ab47b1SGreg Roach        $route = $request->getAttribute('route');
175de2aa325SGreg Roach        assert($route instanceof Route);
1765a78cd34SGreg Roach
177d45701ccSGreg Roach        $cart  = Session::get('cart', []);
178d45701ccSGreg Roach        $count = count($cart[$tree->name()] ?? []);
179d45701ccSGreg Roach        $badge = view('components/badge', ['count' => $count]);
180d45701ccSGreg Roach
1815a78cd34SGreg Roach        $submenus = [
182d45701ccSGreg Roach            new Menu($this->title() . ' ' . $badge, route('module', [
18326684e68SGreg Roach                'module' => $this->name(),
184c1010edaSGreg Roach                'action' => 'Show',
185d72b284aSGreg Roach                'tree'   => $tree->name(),
186c1010edaSGreg Roach            ]), 'menu-clippings-cart', ['rel' => 'nofollow']),
1875a78cd34SGreg Roach        ];
1885a78cd34SGreg Roach
1892b0d92b4SGreg Roach        $action = array_search($route->name, self::ROUTES_WITH_RECORDS, true);
190f95e0480SGreg Roach        if ($action !== false) {
1912b0d92b4SGreg Roach            $xref = $route->attributes['xref'];
192ddeb3354SGreg Roach            assert(is_string($xref));
193ddeb3354SGreg Roach
194c1010edaSGreg Roach            $add_route = route('module', [
19526684e68SGreg Roach                'module' => $this->name(),
196f95e0480SGreg Roach                'action' => 'Add' . $action,
197c1010edaSGreg Roach                'xref'   => $xref,
198d72b284aSGreg Roach                'tree'   => $tree->name(),
199c1010edaSGreg Roach            ]);
2005a78cd34SGreg Roach
20125b2dde3SGreg Roach            $submenus[] = new Menu(I18N::translate('Add to the clippings cart'), $add_route, 'menu-clippings-add', ['rel' => 'nofollow']);
2028c2e8227SGreg Roach        }
203cbc1590aSGreg Roach
2045a78cd34SGreg Roach        if (!$this->isCartEmpty($tree)) {
205c1010edaSGreg Roach            $submenus[] = new Menu(I18N::translate('Empty the clippings cart'), route('module', [
20626684e68SGreg Roach                'module' => $this->name(),
207c1010edaSGreg Roach                'action' => 'Empty',
208d72b284aSGreg Roach                'tree'   => $tree->name(),
209c1010edaSGreg Roach            ]), 'menu-clippings-empty', ['rel' => 'nofollow']);
210f95e0480SGreg Roach
211c1010edaSGreg Roach            $submenus[] = new Menu(I18N::translate('Download'), route('module', [
21226684e68SGreg Roach                'module' => $this->name(),
213c1010edaSGreg Roach                'action' => 'DownloadForm',
214d72b284aSGreg Roach                'tree'   => $tree->name(),
215c1010edaSGreg Roach            ]), 'menu-clippings-download', ['rel' => 'nofollow']);
2165a78cd34SGreg Roach        }
2175a78cd34SGreg Roach
21849a243cbSGreg Roach        return new Menu($this->title(), '#', 'menu-clippings', ['rel' => 'nofollow'], $submenus);
2198c2e8227SGreg Roach    }
2208c2e8227SGreg Roach
22176692c8bSGreg Roach    /**
222d45701ccSGreg Roach     * How should this module be identified in the control panel, etc.?
223d45701ccSGreg Roach     *
224d45701ccSGreg Roach     * @return string
225d45701ccSGreg Roach     */
226d45701ccSGreg Roach    public function title(): string
227d45701ccSGreg Roach    {
228d45701ccSGreg Roach        /* I18N: Name of a module */
229d45701ccSGreg Roach        return I18N::translate('Clippings cart');
230d45701ccSGreg Roach    }
231d45701ccSGreg Roach
232d45701ccSGreg Roach    /**
233d45701ccSGreg Roach     * @param Tree $tree
234d45701ccSGreg Roach     *
235d45701ccSGreg Roach     * @return bool
236d45701ccSGreg Roach     */
237d45701ccSGreg Roach    private function isCartEmpty(Tree $tree): bool
238d45701ccSGreg Roach    {
239d45701ccSGreg Roach        $cart     = Session::get('cart', []);
240d45701ccSGreg Roach        $contents = $cart[$tree->name()] ?? [];
241d45701ccSGreg Roach
242d45701ccSGreg Roach        return $contents === [];
243d45701ccSGreg Roach    }
244d45701ccSGreg Roach
245d45701ccSGreg Roach    /**
246d45701ccSGreg Roach     * @param ServerRequestInterface $request
247d45701ccSGreg Roach     *
248d45701ccSGreg Roach     * @return ResponseInterface
249d45701ccSGreg Roach     */
250d45701ccSGreg Roach    public function getDownloadFormAction(ServerRequestInterface $request): ResponseInterface
251d45701ccSGreg Roach    {
252d45701ccSGreg Roach        $tree = $request->getAttribute('tree');
253d45701ccSGreg Roach        assert($tree instanceof Tree);
254d45701ccSGreg Roach
255d45701ccSGreg Roach        $user  = $request->getAttribute('user');
256d45701ccSGreg Roach        $title = I18N::translate('Family tree clippings cart') . ' — ' . I18N::translate('Download');
257d45701ccSGreg Roach
258d45701ccSGreg Roach        return $this->viewResponse('modules/clippings/download', [
259d45701ccSGreg Roach            'is_manager' => Auth::isManager($tree, $user),
260d45701ccSGreg Roach            'is_member'  => Auth::isMember($tree, $user),
261d45701ccSGreg Roach            'module'     => $this->name(),
262d45701ccSGreg Roach            'title'      => $title,
263d45701ccSGreg Roach            'tree'       => $tree,
264d45701ccSGreg Roach        ]);
265d45701ccSGreg Roach    }
266d45701ccSGreg Roach
267d45701ccSGreg Roach    /**
2686ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
26976692c8bSGreg Roach     *
2706ccdf4f0SGreg Roach     * @return ResponseInterface
271f0448b68SGreg Roach     * @throws FilesystemException
27276692c8bSGreg Roach     */
273f95e0480SGreg Roach    public function postDownloadAction(ServerRequestInterface $request): ResponseInterface
274c1010edaSGreg Roach    {
27557ab2231SGreg Roach        $tree = $request->getAttribute('tree');
2764ea62551SGreg Roach        assert($tree instanceof Tree);
2774ea62551SGreg Roach
2786b9cb339SGreg Roach        $data_filesystem = Registry::filesystem()->data();
279a04bb9a2SGreg Roach
280b46c87bdSGreg Roach        $params = (array) $request->getParsedBody();
281b46c87bdSGreg Roach
282d45701ccSGreg Roach        $privatize_export = $params['privatize_export'] ?? 'none';
283e2ed7c79SGreg Roach
284e2ed7c79SGreg Roach        if ($privatize_export === 'none' && !Auth::isManager($tree)) {
285e2ed7c79SGreg Roach            $privatize_export = 'member';
286e2ed7c79SGreg Roach        }
287e2ed7c79SGreg Roach
288e2ed7c79SGreg Roach        if ($privatize_export === 'gedadmin' && !Auth::isManager($tree)) {
289e2ed7c79SGreg Roach            $privatize_export = 'member';
290e2ed7c79SGreg Roach        }
291e2ed7c79SGreg Roach
292e2ed7c79SGreg Roach        if ($privatize_export === 'user' && !Auth::isMember($tree)) {
293e2ed7c79SGreg Roach            $privatize_export = 'visitor';
294e2ed7c79SGreg Roach        }
295e2ed7c79SGreg Roach
296b46c87bdSGreg Roach        $convert = (bool) ($params['convert'] ?? false);
2978c2e8227SGreg Roach
29813abd6f3SGreg Roach        $cart = Session::get('cart', []);
2998c2e8227SGreg Roach
300aa6f03bbSGreg Roach        $xrefs = array_keys($cart[$tree->name()] ?? []);
301c8846facSGreg Roach        $xrefs = array_map('strval', $xrefs); // PHP converts numeric keys to integers.
3025a78cd34SGreg Roach
3035a78cd34SGreg Roach        // Create a new/empty .ZIP file
304a00baf47SGreg Roach        $temp_zip_file  = stream_get_meta_data(tmpfile())['uri'];
305f32d77e6SGreg Roach        $zip_provider   = new FilesystemZipArchiveProvider($temp_zip_file, 0755);
306f32d77e6SGreg Roach        $zip_adapter    = new ZipArchiveAdapter($zip_provider);
3077f996f6eSGreg Roach        $zip_filesystem = new Filesystem($zip_adapter);
3085a78cd34SGreg Roach
309fa695506SGreg Roach        $media_filesystem = $tree->mediaFilesystem($data_filesystem);
31061bf91b2SGreg Roach
3115a78cd34SGreg Roach        // Media file prefix
3125a78cd34SGreg Roach        $path = $tree->getPreference('MEDIA_DIRECTORY');
3135a78cd34SGreg Roach
31469c05a6eSGreg Roach        $encoding = $convert ? 'ANSI' : 'UTF-8';
31569c05a6eSGreg Roach
31669c05a6eSGreg Roach        $records = new Collection();
3175a78cd34SGreg Roach
3185a78cd34SGreg Roach        switch ($privatize_export) {
3195a78cd34SGreg Roach            case 'gedadmin':
3205a78cd34SGreg Roach                $access_level = Auth::PRIV_NONE;
3215a78cd34SGreg Roach                break;
3225a78cd34SGreg Roach            case 'user':
3235a78cd34SGreg Roach                $access_level = Auth::PRIV_USER;
3245a78cd34SGreg Roach                break;
3255a78cd34SGreg Roach            case 'visitor':
3265a78cd34SGreg Roach                $access_level = Auth::PRIV_PRIVATE;
3275a78cd34SGreg Roach                break;
3285a78cd34SGreg Roach            case 'none':
3295a78cd34SGreg Roach            default:
3305a78cd34SGreg Roach                $access_level = Auth::PRIV_HIDE;
3315a78cd34SGreg Roach                break;
3325a78cd34SGreg Roach        }
3335a78cd34SGreg Roach
3345a78cd34SGreg Roach        foreach ($xrefs as $xref) {
3356b9cb339SGreg Roach            $object = Registry::gedcomRecordFactory()->make($xref, $tree);
3365a78cd34SGreg Roach            // The object may have been deleted since we added it to the cart....
337bed27cedSGreg Roach            if ($object instanceof GedcomRecord) {
3385a78cd34SGreg Roach                $record = $object->privatizeGedcom($access_level);
3395a78cd34SGreg Roach                // Remove links to objects that aren't in the cart
3408d0ebef0SGreg Roach                preg_match_all('/\n1 ' . Gedcom::REGEX_TAG . ' @(' . Gedcom::REGEX_XREF . ')@(\n[2-9].*)*/', $record, $matches, PREG_SET_ORDER);
3415a78cd34SGreg Roach                foreach ($matches as $match) {
342bf80ec58SGreg Roach                    if (!in_array($match[1], $xrefs, true)) {
3435a78cd34SGreg Roach                        $record = str_replace($match[0], '', $record);
3445a78cd34SGreg Roach                    }
3455a78cd34SGreg Roach                }
3468d0ebef0SGreg Roach                preg_match_all('/\n2 ' . Gedcom::REGEX_TAG . ' @(' . Gedcom::REGEX_XREF . ')@(\n[3-9].*)*/', $record, $matches, PREG_SET_ORDER);
3475a78cd34SGreg Roach                foreach ($matches as $match) {
348bf80ec58SGreg Roach                    if (!in_array($match[1], $xrefs, true)) {
3495a78cd34SGreg Roach                        $record = str_replace($match[0], '', $record);
3505a78cd34SGreg Roach                    }
3515a78cd34SGreg Roach                }
3528d0ebef0SGreg Roach                preg_match_all('/\n3 ' . Gedcom::REGEX_TAG . ' @(' . Gedcom::REGEX_XREF . ')@(\n[4-9].*)*/', $record, $matches, PREG_SET_ORDER);
3535a78cd34SGreg Roach                foreach ($matches as $match) {
354bf80ec58SGreg Roach                    if (!in_array($match[1], $xrefs, true)) {
3555a78cd34SGreg Roach                        $record = str_replace($match[0], '', $record);
3565a78cd34SGreg Roach                    }
3575a78cd34SGreg Roach                }
3585a78cd34SGreg Roach
35955167344SGreg Roach                if ($object instanceof Individual || $object instanceof Family) {
36069c05a6eSGreg Roach                    $records->add($record . "\n1 SOUR @WEBTREES@\n2 PAGE " . $object->url());
36155167344SGreg Roach                } elseif ($object instanceof Source) {
36269c05a6eSGreg Roach                    $records->add($record . "\n1 NOTE " . $object->url());
36355167344SGreg Roach                } elseif ($object instanceof Media) {
36455167344SGreg Roach                    // Add the media files to the archive
3655a78cd34SGreg Roach                    foreach ($object->mediaFiles() as $media_file) {
366fa695506SGreg Roach                        $from = $media_file->filename();
367fa695506SGreg Roach                        $to   = $path . $media_file->filename();
368f0448b68SGreg Roach                        if (!$media_file->isExternal() && $media_filesystem->fileExists($from)) {
369fa695506SGreg Roach                            $zip_filesystem->writeStream($to, $media_filesystem->readStream($from));
3705a78cd34SGreg Roach                        }
3715a78cd34SGreg Roach                    }
37269c05a6eSGreg Roach                    $records->add($record);
37355167344SGreg Roach                } else {
37469c05a6eSGreg Roach                    $records->add($record);
3758c2e8227SGreg Roach                }
3768c2e8227SGreg Roach            }
3778c2e8227SGreg Roach        }
3788c2e8227SGreg Roach
3799b93b7c3SGreg Roach        $base_url = $request->getAttribute('base_url');
3809b93b7c3SGreg Roach
3815a78cd34SGreg Roach        // Create a source, to indicate the source of the data.
38269c05a6eSGreg Roach        $record = "0 @WEBTREES@ SOUR\n1 TITL " . $base_url;
383e5a6b4d4SGreg Roach        $author = $this->user_service->find((int) $tree->getPreference('CONTACT_USER_ID'));
3845a78cd34SGreg Roach        if ($author !== null) {
38569c05a6eSGreg Roach            $record .= "\n1 AUTH " . $author->realName();
3865a78cd34SGreg Roach        }
38769c05a6eSGreg Roach        $records->add($record);
3885a78cd34SGreg Roach
38969c05a6eSGreg Roach        $stream = fopen('php://temp', 'wb+');
3905a78cd34SGreg Roach
39169c05a6eSGreg Roach        if ($stream === false) {
39269c05a6eSGreg Roach            throw new RuntimeException('Failed to create temporary stream');
3938c2e8227SGreg Roach        }
394cbc1590aSGreg Roach
39569c05a6eSGreg Roach        // We have already applied privacy filtering, so do not do it again.
39669c05a6eSGreg Roach        $this->gedcom_export_service->export($tree, $stream, false, $encoding, Auth::PRIV_HIDE, $path, $records);
39769c05a6eSGreg Roach        rewind($stream);
39869c05a6eSGreg Roach
3995a78cd34SGreg Roach        // Finally add the GEDCOM file to the .ZIP file.
40069c05a6eSGreg Roach        $zip_filesystem->writeStream('clippings.ged', $stream);
4015a78cd34SGreg Roach
4026ccdf4f0SGreg Roach        // Use a stream, so that we do not have to load the entire file into memory.
403*ca47b2adSGreg Roach        $stream = $this->stream_factory->createStreamFromFile($temp_zip_file);
4045a78cd34SGreg Roach
405*ca47b2adSGreg Roach        return $this->response_factory->createResponse()
4066ccdf4f0SGreg Roach            ->withBody($stream)
4071b3d4731SGreg Roach            ->withHeader('Content-Type', 'application/zip')
408bed27cedSGreg Roach            ->withHeader('Content-Disposition', 'attachment; filename="clippings.zip');
4098c2e8227SGreg Roach    }
4108c2e8227SGreg Roach
4118c2e8227SGreg Roach    /**
41257ab2231SGreg Roach     * @param ServerRequestInterface $request
41376692c8bSGreg Roach     *
4146ccdf4f0SGreg Roach     * @return ResponseInterface
4158c2e8227SGreg Roach     */
41657ab2231SGreg Roach    public function getEmptyAction(ServerRequestInterface $request): ResponseInterface
417c1010edaSGreg Roach    {
41857ab2231SGreg Roach        $tree = $request->getAttribute('tree');
4194ea62551SGreg Roach        assert($tree instanceof Tree);
4204ea62551SGreg Roach
4215a78cd34SGreg Roach        $cart                = Session::get('cart', []);
422aa6f03bbSGreg Roach        $cart[$tree->name()] = [];
4235a78cd34SGreg Roach        Session::put('cart', $cart);
4248c2e8227SGreg Roach
425c1010edaSGreg Roach        $url = route('module', [
42626684e68SGreg Roach            'module' => $this->name(),
427c1010edaSGreg Roach            'action' => 'Show',
428d72b284aSGreg Roach            'tree'   => $tree->name(),
429c1010edaSGreg Roach        ]);
4305a78cd34SGreg Roach
4316ccdf4f0SGreg Roach        return redirect($url);
4325a78cd34SGreg Roach    }
4335a78cd34SGreg Roach
4345a78cd34SGreg Roach    /**
4356ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
4365a78cd34SGreg Roach     *
4376ccdf4f0SGreg Roach     * @return ResponseInterface
4385a78cd34SGreg Roach     */
43957ab2231SGreg Roach    public function postRemoveAction(ServerRequestInterface $request): ResponseInterface
440c1010edaSGreg Roach    {
44157ab2231SGreg Roach        $tree = $request->getAttribute('tree');
44275964c75SGreg Roach        assert($tree instanceof Tree);
4435229eadeSGreg Roach
444d45701ccSGreg Roach        $xref = $request->getQueryParams()['xref'] ?? '';
4455a78cd34SGreg Roach
4465a78cd34SGreg Roach        $cart = Session::get('cart', []);
447aa6f03bbSGreg Roach        unset($cart[$tree->name()][$xref]);
4485a78cd34SGreg Roach        Session::put('cart', $cart);
4495a78cd34SGreg Roach
450c1010edaSGreg Roach        $url = route('module', [
45126684e68SGreg Roach            'module' => $this->name(),
452c1010edaSGreg Roach            'action' => 'Show',
453d72b284aSGreg Roach            'tree'   => $tree->name(),
454c1010edaSGreg Roach        ]);
4555a78cd34SGreg Roach
4566ccdf4f0SGreg Roach        return redirect($url);
4575a78cd34SGreg Roach    }
4585a78cd34SGreg Roach
4595a78cd34SGreg Roach    /**
46057ab2231SGreg Roach     * @param ServerRequestInterface $request
4615a78cd34SGreg Roach     *
4626ccdf4f0SGreg Roach     * @return ResponseInterface
4635a78cd34SGreg Roach     */
46457ab2231SGreg Roach    public function getShowAction(ServerRequestInterface $request): ResponseInterface
465c1010edaSGreg Roach    {
46657ab2231SGreg Roach        $tree = $request->getAttribute('tree');
46775964c75SGreg Roach        assert($tree instanceof Tree);
46857ab2231SGreg Roach
4695a78cd34SGreg Roach        return $this->viewResponse('modules/clippings/show', [
47077b78a63SRichard Cissée            'module'  => $this->name(),
4715a78cd34SGreg Roach            'records' => $this->allRecordsInCart($tree),
4725a78cd34SGreg Roach            'title'   => I18N::translate('Family tree clippings cart'),
4735a78cd34SGreg Roach            'tree'    => $tree,
4745a78cd34SGreg Roach        ]);
4755a78cd34SGreg Roach    }
4765a78cd34SGreg Roach
4775a78cd34SGreg Roach    /**
478d45701ccSGreg Roach     * Get all the records in the cart.
479d45701ccSGreg Roach     *
480d45701ccSGreg Roach     * @param Tree $tree
481d45701ccSGreg Roach     *
482d45701ccSGreg Roach     * @return GedcomRecord[]
483d45701ccSGreg Roach     */
484d45701ccSGreg Roach    private function allRecordsInCart(Tree $tree): array
485d45701ccSGreg Roach    {
486d45701ccSGreg Roach        $cart = Session::get('cart', []);
487d45701ccSGreg Roach
488d45701ccSGreg Roach        $xrefs = array_keys($cart[$tree->name()] ?? []);
489d45701ccSGreg Roach        $xrefs = array_map('strval', $xrefs); // PHP converts numeric keys to integers.
490d45701ccSGreg Roach
491d45701ccSGreg Roach        // Fetch all the records in the cart.
492d45701ccSGreg Roach        $records = array_map(static function (string $xref) use ($tree): ?GedcomRecord {
493d45701ccSGreg Roach            return Registry::gedcomRecordFactory()->make($xref, $tree);
494d45701ccSGreg Roach        }, $xrefs);
495d45701ccSGreg Roach
496d45701ccSGreg Roach        // Some records may have been deleted after they were added to the cart.
497d45701ccSGreg Roach        $records = array_filter($records);
498d45701ccSGreg Roach
499d45701ccSGreg Roach        // Group and sort.
500d45701ccSGreg Roach        uasort($records, static function (GedcomRecord $x, GedcomRecord $y): int {
501d45701ccSGreg Roach            return $x->tag() <=> $y->tag() ?: GedcomRecord::nameComparator()($x, $y);
502d45701ccSGreg Roach        });
503d45701ccSGreg Roach
504d45701ccSGreg Roach        return $records;
505d45701ccSGreg Roach    }
506d45701ccSGreg Roach
507d45701ccSGreg Roach    /**
5086ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
5095a78cd34SGreg Roach     *
5106ccdf4f0SGreg Roach     * @return ResponseInterface
5115a78cd34SGreg Roach     */
51257ab2231SGreg Roach    public function getAddFamilyAction(ServerRequestInterface $request): ResponseInterface
513c1010edaSGreg Roach    {
51457ab2231SGreg Roach        $tree = $request->getAttribute('tree');
51575964c75SGreg Roach        assert($tree instanceof Tree);
5165229eadeSGreg Roach
517d45701ccSGreg Roach        $xref = $request->getQueryParams()['xref'] ?? '';
5185a78cd34SGreg Roach
5196b9cb339SGreg Roach        $family = Registry::familyFactory()->make($xref, $tree);
520d45701ccSGreg Roach        $family = Auth::checkFamilyAccess($family);
521d45701ccSGreg Roach        $name   = $family->fullName();
5225a78cd34SGreg Roach
523d45701ccSGreg Roach        $options = [
52403f99a78SGreg Roach            self::ADD_RECORD_ONLY => $name,
525bbb76c12SGreg Roach            /* I18N: %s is a family (husband + wife) */
52603f99a78SGreg Roach            self::ADD_CHILDREN    => I18N::translate('%s and their children', $name),
527bbb76c12SGreg Roach            /* I18N: %s is a family (husband + wife) */
52803f99a78SGreg Roach            self::ADD_DESCENDANTS => I18N::translate('%s and their descendants', $name),
5295a78cd34SGreg Roach        ];
530d45701ccSGreg Roach
531d45701ccSGreg Roach        $title = I18N::translate('Add %s to the clippings cart', $name);
532d45701ccSGreg Roach
533d45701ccSGreg Roach        return $this->viewResponse('modules/clippings/add-options', [
534d45701ccSGreg Roach            'options' => $options,
535d45701ccSGreg Roach            'record'  => $family,
536d45701ccSGreg Roach            'title'   => $title,
537d45701ccSGreg Roach            'tree'    => $tree,
538d45701ccSGreg Roach        ]);
5395a78cd34SGreg Roach    }
5405a78cd34SGreg Roach
5415a78cd34SGreg Roach    /**
5426ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
5435a78cd34SGreg Roach     *
5446ccdf4f0SGreg Roach     * @return ResponseInterface
5455a78cd34SGreg Roach     */
54657ab2231SGreg Roach    public function postAddFamilyAction(ServerRequestInterface $request): ResponseInterface
547c1010edaSGreg Roach    {
54857ab2231SGreg Roach        $tree = $request->getAttribute('tree');
5494ea62551SGreg Roach        assert($tree instanceof Tree);
5504ea62551SGreg Roach
551b46c87bdSGreg Roach        $params = (array) $request->getParsedBody();
552b46c87bdSGreg Roach
553d45701ccSGreg Roach        $xref   = $params['xref'] ?? '';
554d45701ccSGreg Roach        $option = $params['option'] ?? '';
5555a78cd34SGreg Roach
5566b9cb339SGreg Roach        $family = Registry::familyFactory()->make($xref, $tree);
557d45701ccSGreg Roach        $family = Auth::checkFamilyAccess($family);
5585a78cd34SGreg Roach
5595a78cd34SGreg Roach        switch ($option) {
56003f99a78SGreg Roach            case self::ADD_RECORD_ONLY:
5615a78cd34SGreg Roach                $this->addFamilyToCart($family);
5625a78cd34SGreg Roach                break;
5635a78cd34SGreg Roach
56403f99a78SGreg Roach            case self::ADD_CHILDREN:
5655a78cd34SGreg Roach                $this->addFamilyAndChildrenToCart($family);
5665a78cd34SGreg Roach                break;
5675a78cd34SGreg Roach
56803f99a78SGreg Roach            case self::ADD_DESCENDANTS:
5695a78cd34SGreg Roach                $this->addFamilyAndDescendantsToCart($family);
5705a78cd34SGreg Roach                break;
5715a78cd34SGreg Roach        }
5725a78cd34SGreg Roach
5736ccdf4f0SGreg Roach        return redirect($family->url());
5745a78cd34SGreg Roach    }
5755a78cd34SGreg Roach
5765a78cd34SGreg Roach
5775a78cd34SGreg Roach    /**
5785a78cd34SGreg Roach     * @param Family $family
57918d7a90dSGreg Roach     *
58018d7a90dSGreg Roach     * @return void
5815a78cd34SGreg Roach     */
582d45701ccSGreg Roach    protected function addFamilyAndChildrenToCart(Family $family): void
583c1010edaSGreg Roach    {
584d45701ccSGreg Roach        $this->addFamilyToCart($family);
5855a78cd34SGreg Roach
58639ca88baSGreg Roach        foreach ($family->children() as $child) {
587d45701ccSGreg Roach            $this->addIndividualToCart($child);
5885a78cd34SGreg Roach        }
5895a78cd34SGreg Roach    }
5905a78cd34SGreg Roach
5915a78cd34SGreg Roach    /**
5925a78cd34SGreg Roach     * @param Family $family
59318d7a90dSGreg Roach     *
59418d7a90dSGreg Roach     * @return void
5955a78cd34SGreg Roach     */
596d45701ccSGreg Roach    protected function addFamilyAndDescendantsToCart(Family $family): void
597c1010edaSGreg Roach    {
598d45701ccSGreg Roach        $this->addFamilyAndChildrenToCart($family);
5995a78cd34SGreg Roach
60039ca88baSGreg Roach        foreach ($family->children() as $child) {
60139ca88baSGreg Roach            foreach ($child->spouseFamilies() as $child_family) {
6025a78cd34SGreg Roach                $this->addFamilyAndDescendantsToCart($child_family);
6035a78cd34SGreg Roach            }
6045a78cd34SGreg Roach        }
6055a78cd34SGreg Roach    }
6065a78cd34SGreg Roach
6075a78cd34SGreg Roach    /**
6086ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
6095a78cd34SGreg Roach     *
6106ccdf4f0SGreg Roach     * @return ResponseInterface
6115a78cd34SGreg Roach     */
61257ab2231SGreg Roach    public function getAddIndividualAction(ServerRequestInterface $request): ResponseInterface
613c1010edaSGreg Roach    {
61457ab2231SGreg Roach        $tree = $request->getAttribute('tree');
61575964c75SGreg Roach        assert($tree instanceof Tree);
6165229eadeSGreg Roach
617d45701ccSGreg Roach        $xref = $request->getQueryParams()['xref'] ?? '';
6185a78cd34SGreg Roach
6196b9cb339SGreg Roach        $individual = Registry::individualFactory()->make($xref, $tree);
620d45701ccSGreg Roach        $individual = Auth::checkIndividualAccess($individual);
621d45701ccSGreg Roach        $name       = $individual->fullName();
6225a78cd34SGreg Roach
62339ca88baSGreg Roach        if ($individual->sex() === 'F') {
624d45701ccSGreg Roach            $options = [
62503f99a78SGreg Roach                self::ADD_RECORD_ONLY       => $name,
62603f99a78SGreg Roach                self::ADD_PARENT_FAMILIES   => I18N::translate('%s, her parents and siblings', $name),
62703f99a78SGreg Roach                self::ADD_SPOUSE_FAMILIES   => I18N::translate('%s, her spouses and children', $name),
62803f99a78SGreg Roach                self::ADD_ANCESTORS         => I18N::translate('%s and her ancestors', $name),
62903f99a78SGreg Roach                self::ADD_ANCESTOR_FAMILIES => I18N::translate('%s, her ancestors and their families', $name),
63003f99a78SGreg Roach                self::ADD_DESCENDANTS       => I18N::translate('%s, her spouses and descendants', $name),
6315a78cd34SGreg Roach            ];
632d45701ccSGreg Roach        } else {
633d45701ccSGreg Roach            $options = [
63403f99a78SGreg Roach                self::ADD_RECORD_ONLY       => $name,
63503f99a78SGreg Roach                self::ADD_PARENT_FAMILIES   => I18N::translate('%s, his parents and siblings', $name),
63603f99a78SGreg Roach                self::ADD_SPOUSE_FAMILIES   => I18N::translate('%s, his spouses and children', $name),
63703f99a78SGreg Roach                self::ADD_ANCESTORS         => I18N::translate('%s and his ancestors', $name),
63803f99a78SGreg Roach                self::ADD_ANCESTOR_FAMILIES => I18N::translate('%s, his ancestors and their families', $name),
63903f99a78SGreg Roach                self::ADD_DESCENDANTS       => I18N::translate('%s, his spouses and descendants', $name),
6405a78cd34SGreg Roach            ];
6415a78cd34SGreg Roach        }
6425a78cd34SGreg Roach
643d45701ccSGreg Roach        $title = I18N::translate('Add %s to the clippings cart', $name);
644d45701ccSGreg Roach
645d45701ccSGreg Roach        return $this->viewResponse('modules/clippings/add-options', [
646d45701ccSGreg Roach            'options' => $options,
647d45701ccSGreg Roach            'record'  => $individual,
648d45701ccSGreg Roach            'title'   => $title,
649d45701ccSGreg Roach            'tree'    => $tree,
650d45701ccSGreg Roach        ]);
651d45701ccSGreg Roach    }
652d45701ccSGreg Roach
6535a78cd34SGreg Roach    /**
6546ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
6555a78cd34SGreg Roach     *
6566ccdf4f0SGreg Roach     * @return ResponseInterface
6575a78cd34SGreg Roach     */
65857ab2231SGreg Roach    public function postAddIndividualAction(ServerRequestInterface $request): ResponseInterface
659c1010edaSGreg Roach    {
66057ab2231SGreg Roach        $tree = $request->getAttribute('tree');
6614ea62551SGreg Roach        assert($tree instanceof Tree);
6624ea62551SGreg Roach
663b46c87bdSGreg Roach        $params = (array) $request->getParsedBody();
664b46c87bdSGreg Roach
665d45701ccSGreg Roach        $xref   = $params['xref'] ?? '';
666d45701ccSGreg Roach        $option = $params['option'] ?? '';
6675a78cd34SGreg Roach
6686b9cb339SGreg Roach        $individual = Registry::individualFactory()->make($xref, $tree);
669d45701ccSGreg Roach        $individual = Auth::checkIndividualAccess($individual);
6705a78cd34SGreg Roach
6715a78cd34SGreg Roach        switch ($option) {
67203f99a78SGreg Roach            case self::ADD_RECORD_ONLY:
673d45701ccSGreg Roach                $this->addIndividualToCart($individual);
6745a78cd34SGreg Roach                break;
6755a78cd34SGreg Roach
67603f99a78SGreg Roach            case self::ADD_PARENT_FAMILIES:
67739ca88baSGreg Roach                foreach ($individual->childFamilies() as $family) {
6785a78cd34SGreg Roach                    $this->addFamilyAndChildrenToCart($family);
6795a78cd34SGreg Roach                }
6805a78cd34SGreg Roach                break;
6815a78cd34SGreg Roach
68203f99a78SGreg Roach            case self::ADD_SPOUSE_FAMILIES:
68339ca88baSGreg Roach                foreach ($individual->spouseFamilies() as $family) {
6845a78cd34SGreg Roach                    $this->addFamilyAndChildrenToCart($family);
6855a78cd34SGreg Roach                }
6865a78cd34SGreg Roach                break;
6875a78cd34SGreg Roach
68803f99a78SGreg Roach            case self::ADD_ANCESTORS:
6895a78cd34SGreg Roach                $this->addAncestorsToCart($individual);
6905a78cd34SGreg Roach                break;
6915a78cd34SGreg Roach
69203f99a78SGreg Roach            case self::ADD_ANCESTOR_FAMILIES:
6935a78cd34SGreg Roach                $this->addAncestorFamiliesToCart($individual);
6945a78cd34SGreg Roach                break;
6955a78cd34SGreg Roach
69603f99a78SGreg Roach            case self::ADD_DESCENDANTS:
69739ca88baSGreg Roach                foreach ($individual->spouseFamilies() as $family) {
6985a78cd34SGreg Roach                    $this->addFamilyAndDescendantsToCart($family);
6995a78cd34SGreg Roach                }
7005a78cd34SGreg Roach                break;
7015a78cd34SGreg Roach        }
7025a78cd34SGreg Roach
7036ccdf4f0SGreg Roach        return redirect($individual->url());
7045a78cd34SGreg Roach    }
7055a78cd34SGreg Roach
7065a78cd34SGreg Roach    /**
7075a78cd34SGreg Roach     * @param Individual $individual
70818d7a90dSGreg Roach     *
70918d7a90dSGreg Roach     * @return void
7105a78cd34SGreg Roach     */
711d45701ccSGreg Roach    protected function addAncestorsToCart(Individual $individual): void
712c1010edaSGreg Roach    {
713d45701ccSGreg Roach        $this->addIndividualToCart($individual);
7145a78cd34SGreg Roach
71539ca88baSGreg Roach        foreach ($individual->childFamilies() as $family) {
716d45701ccSGreg Roach            $this->addFamilyToCart($family);
7178df4c68dSGreg Roach
71839ca88baSGreg Roach            foreach ($family->spouses() as $parent) {
7195a78cd34SGreg Roach                $this->addAncestorsToCart($parent);
7205a78cd34SGreg Roach            }
7215a78cd34SGreg Roach        }
7225a78cd34SGreg Roach    }
7235a78cd34SGreg Roach
7245a78cd34SGreg Roach    /**
7255a78cd34SGreg Roach     * @param Individual $individual
72618d7a90dSGreg Roach     *
72718d7a90dSGreg Roach     * @return void
7285a78cd34SGreg Roach     */
729d45701ccSGreg Roach    protected function addAncestorFamiliesToCart(Individual $individual): void
730c1010edaSGreg Roach    {
73139ca88baSGreg Roach        foreach ($individual->childFamilies() as $family) {
7325a78cd34SGreg Roach            $this->addFamilyAndChildrenToCart($family);
7338df4c68dSGreg Roach
73439ca88baSGreg Roach            foreach ($family->spouses() as $parent) {
735cad6d3f3SGreg Roach                $this->addAncestorFamiliesToCart($parent);
7365a78cd34SGreg Roach            }
7375a78cd34SGreg Roach        }
7385a78cd34SGreg Roach    }
7395a78cd34SGreg Roach
7405a78cd34SGreg Roach    /**
7416ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
7425a78cd34SGreg Roach     *
7436ccdf4f0SGreg Roach     * @return ResponseInterface
7445a78cd34SGreg Roach     */
745e8ded2caSGreg Roach    public function getAddLocationAction(ServerRequestInterface $request): ResponseInterface
746e8ded2caSGreg Roach    {
747e8ded2caSGreg Roach        $tree = $request->getAttribute('tree');
748e8ded2caSGreg Roach        assert($tree instanceof Tree);
749e8ded2caSGreg Roach
750e8ded2caSGreg Roach        $xref = $request->getQueryParams()['xref'] ?? '';
751e8ded2caSGreg Roach
752e8ded2caSGreg Roach        $location = Registry::locationFactory()->make($xref, $tree);
753e8ded2caSGreg Roach        $location = Auth::checkLocationAccess($location);
754e8ded2caSGreg Roach        $name     = $location->fullName();
755e8ded2caSGreg Roach
756e8ded2caSGreg Roach        $options = [
75703f99a78SGreg Roach            self::ADD_RECORD_ONLY => $name,
758e8ded2caSGreg Roach        ];
759e8ded2caSGreg Roach
760e8ded2caSGreg Roach        $title = I18N::translate('Add %s to the clippings cart', $name);
761e8ded2caSGreg Roach
762e8ded2caSGreg Roach        return $this->viewResponse('modules/clippings/add-options', [
763e8ded2caSGreg Roach            'options' => $options,
764e8ded2caSGreg Roach            'record'  => $location,
765e8ded2caSGreg Roach            'title'   => $title,
766e8ded2caSGreg Roach            'tree'    => $tree,
767e8ded2caSGreg Roach        ]);
768e8ded2caSGreg Roach    }
769e8ded2caSGreg Roach
770e8ded2caSGreg Roach    /**
771e8ded2caSGreg Roach     * @param ServerRequestInterface $request
772e8ded2caSGreg Roach     *
773e8ded2caSGreg Roach     * @return ResponseInterface
774e8ded2caSGreg Roach     */
775e8ded2caSGreg Roach    public function postAddLocationAction(ServerRequestInterface $request): ResponseInterface
776e8ded2caSGreg Roach    {
777e8ded2caSGreg Roach        $tree = $request->getAttribute('tree');
778e8ded2caSGreg Roach        assert($tree instanceof Tree);
779e8ded2caSGreg Roach
780e8ded2caSGreg Roach        $xref = $request->getQueryParams()['xref'] ?? '';
781e8ded2caSGreg Roach
782e8ded2caSGreg Roach        $location = Registry::locationFactory()->make($xref, $tree);
783e8ded2caSGreg Roach        $location = Auth::checkLocationAccess($location);
784e8ded2caSGreg Roach
785e8ded2caSGreg Roach        $this->addLocationToCart($location);
786e8ded2caSGreg Roach
787e8ded2caSGreg Roach        return redirect($location->url());
788e8ded2caSGreg Roach    }
789e8ded2caSGreg Roach
790e8ded2caSGreg Roach    /**
791e8ded2caSGreg Roach     * @param ServerRequestInterface $request
792e8ded2caSGreg Roach     *
793e8ded2caSGreg Roach     * @return ResponseInterface
794e8ded2caSGreg Roach     */
79557ab2231SGreg Roach    public function getAddMediaAction(ServerRequestInterface $request): ResponseInterface
796c1010edaSGreg Roach    {
79757ab2231SGreg Roach        $tree = $request->getAttribute('tree');
79875964c75SGreg Roach        assert($tree instanceof Tree);
7995229eadeSGreg Roach
800d45701ccSGreg Roach        $xref = $request->getQueryParams()['xref'] ?? '';
8015a78cd34SGreg Roach
8026b9cb339SGreg Roach        $media = Registry::mediaFactory()->make($xref, $tree);
803d45701ccSGreg Roach        $media = Auth::checkMediaAccess($media);
804d45701ccSGreg Roach        $name  = $media->fullName();
8055a78cd34SGreg Roach
806d45701ccSGreg Roach        $options = [
80703f99a78SGreg Roach            self::ADD_RECORD_ONLY => $name,
808d45701ccSGreg Roach        ];
8095a78cd34SGreg Roach
810d45701ccSGreg Roach        $title = I18N::translate('Add %s to the clippings cart', $name);
8115a78cd34SGreg Roach
8125a78cd34SGreg Roach        return $this->viewResponse('modules/clippings/add-options', [
8135a78cd34SGreg Roach            'options' => $options,
8145a78cd34SGreg Roach            'record'  => $media,
8155a78cd34SGreg Roach            'title'   => $title,
8165a78cd34SGreg Roach            'tree'    => $tree,
8175a78cd34SGreg Roach        ]);
8185a78cd34SGreg Roach    }
8195a78cd34SGreg Roach
8205a78cd34SGreg Roach    /**
8216ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
8225a78cd34SGreg Roach     *
8236ccdf4f0SGreg Roach     * @return ResponseInterface
8245a78cd34SGreg Roach     */
82557ab2231SGreg Roach    public function postAddMediaAction(ServerRequestInterface $request): ResponseInterface
826c1010edaSGreg Roach    {
82757ab2231SGreg Roach        $tree = $request->getAttribute('tree');
82875964c75SGreg Roach        assert($tree instanceof Tree);
8295229eadeSGreg Roach
830d45701ccSGreg Roach        $xref = $request->getQueryParams()['xref'] ?? '';
8315a78cd34SGreg Roach
8326b9cb339SGreg Roach        $media = Registry::mediaFactory()->make($xref, $tree);
833d45701ccSGreg Roach        $media = Auth::checkMediaAccess($media);
8345a78cd34SGreg Roach
835d45701ccSGreg Roach        $this->addMediaToCart($media);
8365a78cd34SGreg Roach
8376ccdf4f0SGreg Roach        return redirect($media->url());
8385a78cd34SGreg Roach    }
8395a78cd34SGreg Roach
8405a78cd34SGreg Roach    /**
8416ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
8425a78cd34SGreg Roach     *
8436ccdf4f0SGreg Roach     * @return ResponseInterface
8445a78cd34SGreg Roach     */
84557ab2231SGreg Roach    public function getAddNoteAction(ServerRequestInterface $request): ResponseInterface
846c1010edaSGreg Roach    {
84757ab2231SGreg Roach        $tree = $request->getAttribute('tree');
84875964c75SGreg Roach        assert($tree instanceof Tree);
8495229eadeSGreg Roach
850d45701ccSGreg Roach        $xref = $request->getQueryParams()['xref'] ?? '';
8515a78cd34SGreg Roach
8526b9cb339SGreg Roach        $note = Registry::noteFactory()->make($xref, $tree);
853d45701ccSGreg Roach        $note = Auth::checkNoteAccess($note);
854d45701ccSGreg Roach        $name = $note->fullName();
8555a78cd34SGreg Roach
856d45701ccSGreg Roach        $options = [
85703f99a78SGreg Roach            self::ADD_RECORD_ONLY => $name,
858d45701ccSGreg Roach        ];
8595a78cd34SGreg Roach
860d45701ccSGreg Roach        $title = I18N::translate('Add %s to the clippings cart', $name);
8615a78cd34SGreg Roach
8625a78cd34SGreg Roach        return $this->viewResponse('modules/clippings/add-options', [
8635a78cd34SGreg Roach            'options' => $options,
8645a78cd34SGreg Roach            'record'  => $note,
8655a78cd34SGreg Roach            'title'   => $title,
8665a78cd34SGreg Roach            'tree'    => $tree,
8675a78cd34SGreg Roach        ]);
8685a78cd34SGreg Roach    }
8695a78cd34SGreg Roach
8705a78cd34SGreg Roach    /**
8716ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
8725a78cd34SGreg Roach     *
8736ccdf4f0SGreg Roach     * @return ResponseInterface
8745a78cd34SGreg Roach     */
87557ab2231SGreg Roach    public function postAddNoteAction(ServerRequestInterface $request): ResponseInterface
876c1010edaSGreg Roach    {
87757ab2231SGreg Roach        $tree = $request->getAttribute('tree');
87875964c75SGreg Roach        assert($tree instanceof Tree);
8795229eadeSGreg Roach
880d45701ccSGreg Roach        $xref = $request->getQueryParams()['xref'] ?? '';
8815a78cd34SGreg Roach
8826b9cb339SGreg Roach        $note = Registry::noteFactory()->make($xref, $tree);
883d45701ccSGreg Roach        $note = Auth::checkNoteAccess($note);
8845a78cd34SGreg Roach
885d45701ccSGreg Roach        $this->addNoteToCart($note);
8865a78cd34SGreg Roach
8876ccdf4f0SGreg Roach        return redirect($note->url());
8885a78cd34SGreg Roach    }
8895a78cd34SGreg Roach
8905a78cd34SGreg Roach    /**
8916ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
8925a78cd34SGreg Roach     *
8936ccdf4f0SGreg Roach     * @return ResponseInterface
8945a78cd34SGreg Roach     */
89557ab2231SGreg Roach    public function getAddRepositoryAction(ServerRequestInterface $request): ResponseInterface
896c1010edaSGreg Roach    {
89757ab2231SGreg Roach        $tree = $request->getAttribute('tree');
89875964c75SGreg Roach        assert($tree instanceof Tree);
8995229eadeSGreg Roach
900d45701ccSGreg Roach        $xref = $request->getQueryParams()['xref'] ?? '';
9015a78cd34SGreg Roach
9026b9cb339SGreg Roach        $repository = Registry::repositoryFactory()->make($xref, $tree);
903d45701ccSGreg Roach        $repository = Auth::checkRepositoryAccess($repository);
904d45701ccSGreg Roach        $name       = $repository->fullName();
9055a78cd34SGreg Roach
906d45701ccSGreg Roach        $options = [
90703f99a78SGreg Roach            self::ADD_RECORD_ONLY => $name,
908d45701ccSGreg Roach        ];
9095a78cd34SGreg Roach
910d45701ccSGreg Roach        $title = I18N::translate('Add %s to the clippings cart', $name);
9115a78cd34SGreg Roach
9125a78cd34SGreg Roach        return $this->viewResponse('modules/clippings/add-options', [
9135a78cd34SGreg Roach            'options' => $options,
9145a78cd34SGreg Roach            'record'  => $repository,
9155a78cd34SGreg Roach            'title'   => $title,
9165a78cd34SGreg Roach            'tree'    => $tree,
9175a78cd34SGreg Roach        ]);
9185a78cd34SGreg Roach    }
9195a78cd34SGreg Roach
9205a78cd34SGreg Roach    /**
9216ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
9225a78cd34SGreg Roach     *
9236ccdf4f0SGreg Roach     * @return ResponseInterface
9245a78cd34SGreg Roach     */
92557ab2231SGreg Roach    public function postAddRepositoryAction(ServerRequestInterface $request): ResponseInterface
926c1010edaSGreg Roach    {
92757ab2231SGreg Roach        $tree = $request->getAttribute('tree');
92875964c75SGreg Roach        assert($tree instanceof Tree);
9295229eadeSGreg Roach
930d45701ccSGreg Roach        $xref = $request->getQueryParams()['xref'] ?? '';
9315a78cd34SGreg Roach
9326b9cb339SGreg Roach        $repository = Registry::repositoryFactory()->make($xref, $tree);
933d45701ccSGreg Roach        $repository = Auth::checkRepositoryAccess($repository);
9345a78cd34SGreg Roach
935d45701ccSGreg Roach        $this->addRepositoryToCart($repository);
936d45701ccSGreg Roach
937d45701ccSGreg Roach        foreach ($repository->linkedSources('REPO') as $source) {
938d45701ccSGreg Roach            $this->addSourceToCart($source);
9395a78cd34SGreg Roach        }
9405a78cd34SGreg Roach
9416ccdf4f0SGreg Roach        return redirect($repository->url());
9425a78cd34SGreg Roach    }
9435a78cd34SGreg Roach
9445a78cd34SGreg Roach    /**
9456ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
9465a78cd34SGreg Roach     *
9476ccdf4f0SGreg Roach     * @return ResponseInterface
9485a78cd34SGreg Roach     */
94957ab2231SGreg Roach    public function getAddSourceAction(ServerRequestInterface $request): ResponseInterface
950c1010edaSGreg Roach    {
95157ab2231SGreg Roach        $tree = $request->getAttribute('tree');
95275964c75SGreg Roach        assert($tree instanceof Tree);
9535229eadeSGreg Roach
954d45701ccSGreg Roach        $xref = $request->getQueryParams()['xref'] ?? '';
9555a78cd34SGreg Roach
9566b9cb339SGreg Roach        $source = Registry::sourceFactory()->make($xref, $tree);
957d45701ccSGreg Roach        $source = Auth::checkSourceAccess($source);
958d45701ccSGreg Roach        $name   = $source->fullName();
9595a78cd34SGreg Roach
960d45701ccSGreg Roach        $options = [
96103f99a78SGreg Roach            self::ADD_RECORD_ONLY        => $name,
96203f99a78SGreg Roach            self::ADD_LINKED_INDIVIDUALS => I18N::translate('%s and the individuals that reference it.', $name),
963d45701ccSGreg Roach        ];
9645a78cd34SGreg Roach
965d45701ccSGreg Roach        $title = I18N::translate('Add %s to the clippings cart', $name);
9665a78cd34SGreg Roach
9675a78cd34SGreg Roach        return $this->viewResponse('modules/clippings/add-options', [
9685a78cd34SGreg Roach            'options' => $options,
9695a78cd34SGreg Roach            'record'  => $source,
9705a78cd34SGreg Roach            'title'   => $title,
9715a78cd34SGreg Roach            'tree'    => $tree,
9725a78cd34SGreg Roach        ]);
9735a78cd34SGreg Roach    }
9745a78cd34SGreg Roach
9755a78cd34SGreg Roach    /**
9766ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
9775a78cd34SGreg Roach     *
9786ccdf4f0SGreg Roach     * @return ResponseInterface
9795a78cd34SGreg Roach     */
98057ab2231SGreg Roach    public function postAddSourceAction(ServerRequestInterface $request): ResponseInterface
981c1010edaSGreg Roach    {
98257ab2231SGreg Roach        $tree = $request->getAttribute('tree');
98375964c75SGreg Roach        assert($tree instanceof Tree);
9845229eadeSGreg Roach
985b46c87bdSGreg Roach        $params = (array) $request->getParsedBody();
986b46c87bdSGreg Roach
987d45701ccSGreg Roach        $xref   = $params['xref'] ?? '';
988d45701ccSGreg Roach        $option = $params['option'] ?? '';
9895a78cd34SGreg Roach
9906b9cb339SGreg Roach        $source = Registry::sourceFactory()->make($xref, $tree);
991d45701ccSGreg Roach        $source = Auth::checkSourceAccess($source);
9925a78cd34SGreg Roach
993d45701ccSGreg Roach        $this->addSourceToCart($source);
9945a78cd34SGreg Roach
99503f99a78SGreg Roach        if ($option === self::ADD_LINKED_INDIVIDUALS) {
9965a78cd34SGreg Roach            foreach ($source->linkedIndividuals('SOUR') as $individual) {
997d45701ccSGreg Roach                $this->addIndividualToCart($individual);
9985a78cd34SGreg Roach            }
9995a78cd34SGreg Roach            foreach ($source->linkedFamilies('SOUR') as $family) {
1000d45701ccSGreg Roach                $this->addFamilyToCart($family);
10015a78cd34SGreg Roach            }
10025a78cd34SGreg Roach        }
10035a78cd34SGreg Roach
10046ccdf4f0SGreg Roach        return redirect($source->url());
10055a78cd34SGreg Roach    }
10065a78cd34SGreg Roach
10075a78cd34SGreg Roach    /**
1008d45701ccSGreg Roach     * @param ServerRequestInterface $request
10095a78cd34SGreg Roach     *
1010d45701ccSGreg Roach     * @return ResponseInterface
10115a78cd34SGreg Roach     */
1012d45701ccSGreg Roach    public function getAddSubmitterAction(ServerRequestInterface $request): ResponseInterface
1013c1010edaSGreg Roach    {
1014d45701ccSGreg Roach        $tree = $request->getAttribute('tree');
1015d45701ccSGreg Roach        assert($tree instanceof Tree);
10165a78cd34SGreg Roach
1017d45701ccSGreg Roach        $xref = $request->getQueryParams()['xref'] ?? '';
10185a78cd34SGreg Roach
1019d45701ccSGreg Roach        $submitter = Registry::submitterFactory()->make($xref, $tree);
1020d45701ccSGreg Roach        $submitter = Auth::checkSubmitterAccess($submitter);
1021d45701ccSGreg Roach        $name      = $submitter->fullName();
10225a78cd34SGreg Roach
1023d45701ccSGreg Roach        $options = [
102403f99a78SGreg Roach            self::ADD_RECORD_ONLY => $name,
1025d45701ccSGreg Roach        ];
10265a78cd34SGreg Roach
1027d45701ccSGreg Roach        $title = I18N::translate('Add %s to the clippings cart', $name);
10285a78cd34SGreg Roach
1029d45701ccSGreg Roach        return $this->viewResponse('modules/clippings/add-options', [
1030d45701ccSGreg Roach            'options' => $options,
1031d45701ccSGreg Roach            'record'  => $submitter,
1032d45701ccSGreg Roach            'title'   => $title,
1033d45701ccSGreg Roach            'tree'    => $tree,
1034d45701ccSGreg Roach        ]);
10355a78cd34SGreg Roach    }
10365a78cd34SGreg Roach
10375a78cd34SGreg Roach    /**
1038d45701ccSGreg Roach     * @param ServerRequestInterface $request
10395a78cd34SGreg Roach     *
1040d45701ccSGreg Roach     * @return ResponseInterface
10415a78cd34SGreg Roach     */
1042d45701ccSGreg Roach    public function postAddSubmitterAction(ServerRequestInterface $request): ResponseInterface
1043d45701ccSGreg Roach    {
1044d45701ccSGreg Roach        $tree = $request->getAttribute('tree');
1045d45701ccSGreg Roach        assert($tree instanceof Tree);
1046d45701ccSGreg Roach
1047d45701ccSGreg Roach        $xref = $request->getQueryParams()['xref'] ?? '';
1048d45701ccSGreg Roach
1049d45701ccSGreg Roach        $submitter = Registry::submitterFactory()->make($xref, $tree);
1050d45701ccSGreg Roach        $submitter = Auth::checkSubmitterAccess($submitter);
1051d45701ccSGreg Roach
1052d45701ccSGreg Roach        $this->addSubmitterToCart($submitter);
1053d45701ccSGreg Roach
1054d45701ccSGreg Roach        return redirect($submitter->url());
1055d45701ccSGreg Roach    }
1056d45701ccSGreg Roach
1057d45701ccSGreg Roach    /**
1058d45701ccSGreg Roach     * @param Family $family
1059d45701ccSGreg Roach     */
1060d45701ccSGreg Roach    protected function addFamilyToCart(Family $family): void
1061c1010edaSGreg Roach    {
10625a78cd34SGreg Roach        $cart = Session::get('cart', []);
1063d45701ccSGreg Roach        $tree = $family->tree()->name();
1064d45701ccSGreg Roach        $xref = $family->xref();
10655a78cd34SGreg Roach
1066d45701ccSGreg Roach        if (($cart[$tree][$xref] ?? false) === false) {
1067d45701ccSGreg Roach            $cart[$tree][$xref] = true;
10685a78cd34SGreg Roach
1069d45701ccSGreg Roach            Session::put('cart', $cart);
10705a78cd34SGreg Roach
1071d45701ccSGreg Roach            foreach ($family->spouses() as $spouse) {
1072d45701ccSGreg Roach                $this->addIndividualToCart($spouse);
10735a78cd34SGreg Roach            }
10745a78cd34SGreg Roach
1075e8ded2caSGreg Roach            $this->addLocationLinksToCart($family);
1076d45701ccSGreg Roach            $this->addMediaLinksToCart($family);
1077d45701ccSGreg Roach            $this->addNoteLinksToCart($family);
1078d45701ccSGreg Roach            $this->addSourceLinksToCart($family);
1079d45701ccSGreg Roach            $this->addSubmitterLinksToCart($family);
1080d45701ccSGreg Roach        }
1081d45701ccSGreg Roach    }
1082d45701ccSGreg Roach
1083d45701ccSGreg Roach    /**
1084d45701ccSGreg Roach     * @param Individual $individual
1085d45701ccSGreg Roach     */
1086d45701ccSGreg Roach    protected function addIndividualToCart(Individual $individual): void
1087d45701ccSGreg Roach    {
1088d45701ccSGreg Roach        $cart = Session::get('cart', []);
1089d45701ccSGreg Roach        $tree = $individual->tree()->name();
1090d45701ccSGreg Roach        $xref = $individual->xref();
1091d45701ccSGreg Roach
1092d45701ccSGreg Roach        if (($cart[$tree][$xref] ?? false) === false) {
1093d45701ccSGreg Roach            $cart[$tree][$xref] = true;
1094d45701ccSGreg Roach
1095d45701ccSGreg Roach            Session::put('cart', $cart);
1096d45701ccSGreg Roach
1097e8ded2caSGreg Roach            $this->addLocationLinksToCart($individual);
1098d45701ccSGreg Roach            $this->addMediaLinksToCart($individual);
1099d45701ccSGreg Roach            $this->addNoteLinksToCart($individual);
1100d45701ccSGreg Roach            $this->addSourceLinksToCart($individual);
1101d45701ccSGreg Roach        }
1102d45701ccSGreg Roach    }
1103d45701ccSGreg Roach
1104d45701ccSGreg Roach    /**
1105e8ded2caSGreg Roach     * @param Location $location
1106e8ded2caSGreg Roach     */
1107e8ded2caSGreg Roach    protected function addLocationToCart(Location $location): void
1108e8ded2caSGreg Roach    {
1109e8ded2caSGreg Roach        $cart = Session::get('cart', []);
1110e8ded2caSGreg Roach        $tree = $location->tree()->name();
1111e8ded2caSGreg Roach        $xref = $location->xref();
1112e8ded2caSGreg Roach
1113e8ded2caSGreg Roach        if (($cart[$tree][$xref] ?? false) === false) {
1114e8ded2caSGreg Roach            $cart[$tree][$xref] = true;
1115e8ded2caSGreg Roach
1116e8ded2caSGreg Roach            Session::put('cart', $cart);
1117e8ded2caSGreg Roach
1118e8ded2caSGreg Roach            $this->addLocationLinksToCart($location);
1119e8ded2caSGreg Roach            $this->addMediaLinksToCart($location);
1120e8ded2caSGreg Roach            $this->addNoteLinksToCart($location);
1121e8ded2caSGreg Roach            $this->addSourceLinksToCart($location);
1122e8ded2caSGreg Roach        }
1123e8ded2caSGreg Roach    }
1124e8ded2caSGreg Roach
1125e8ded2caSGreg Roach    /**
1126e8ded2caSGreg Roach     * @param GedcomRecord $record
1127e8ded2caSGreg Roach     */
1128e8ded2caSGreg Roach    protected function addLocationLinksToCart(GedcomRecord $record): void
1129e8ded2caSGreg Roach    {
1130e8ded2caSGreg Roach        preg_match_all('/\n\d _LOC @(' . Gedcom::REGEX_XREF . ')@/', $record->gedcom(), $matches);
1131e8ded2caSGreg Roach
1132e8ded2caSGreg Roach        foreach ($matches[1] as $xref) {
1133e8ded2caSGreg Roach            $location = Registry::locationFactory()->make($xref, $record->tree());
1134e8ded2caSGreg Roach
1135e8ded2caSGreg Roach            if ($location instanceof Location && $location->canShow()) {
1136e8ded2caSGreg Roach                $this->addLocationToCart($location);
1137e8ded2caSGreg Roach            }
1138e8ded2caSGreg Roach        }
1139e8ded2caSGreg Roach    }
1140e8ded2caSGreg Roach
1141e8ded2caSGreg Roach    /**
1142d45701ccSGreg Roach     * @param Media $media
1143d45701ccSGreg Roach     */
1144d45701ccSGreg Roach    protected function addMediaToCart(Media $media): void
1145d45701ccSGreg Roach    {
1146d45701ccSGreg Roach        $cart = Session::get('cart', []);
1147d45701ccSGreg Roach        $tree = $media->tree()->name();
1148d45701ccSGreg Roach        $xref = $media->xref();
1149d45701ccSGreg Roach
1150d45701ccSGreg Roach        if (($cart[$tree][$xref] ?? false) === false) {
1151d45701ccSGreg Roach            $cart[$tree][$xref] = true;
1152d45701ccSGreg Roach
1153d45701ccSGreg Roach            Session::put('cart', $cart);
1154d45701ccSGreg Roach
1155d45701ccSGreg Roach            $this->addNoteLinksToCart($media);
1156d45701ccSGreg Roach        }
1157d45701ccSGreg Roach    }
1158d45701ccSGreg Roach
1159d45701ccSGreg Roach    /**
1160d45701ccSGreg Roach     * @param GedcomRecord $record
1161d45701ccSGreg Roach     */
1162d45701ccSGreg Roach    protected function addMediaLinksToCart(GedcomRecord $record): void
1163d45701ccSGreg Roach    {
1164d45701ccSGreg Roach        preg_match_all('/\n\d OBJE @(' . Gedcom::REGEX_XREF . ')@/', $record->gedcom(), $matches);
1165d45701ccSGreg Roach
1166d45701ccSGreg Roach        foreach ($matches[1] as $xref) {
1167d45701ccSGreg Roach            $media = Registry::mediaFactory()->make($xref, $record->tree());
1168d45701ccSGreg Roach
1169d45701ccSGreg Roach            if ($media instanceof Media && $media->canShow()) {
1170d45701ccSGreg Roach                $this->addMediaToCart($media);
1171d45701ccSGreg Roach            }
1172d45701ccSGreg Roach        }
1173d45701ccSGreg Roach    }
1174d45701ccSGreg Roach
1175d45701ccSGreg Roach    /**
1176d45701ccSGreg Roach     * @param Note $note
1177d45701ccSGreg Roach     */
1178d45701ccSGreg Roach    protected function addNoteToCart(Note $note): void
1179d45701ccSGreg Roach    {
1180d45701ccSGreg Roach        $cart = Session::get('cart', []);
1181d45701ccSGreg Roach        $tree = $note->tree()->name();
1182d45701ccSGreg Roach        $xref = $note->xref();
1183d45701ccSGreg Roach
1184d45701ccSGreg Roach        if (($cart[$tree][$xref] ?? false) === false) {
1185d45701ccSGreg Roach            $cart[$tree][$xref] = true;
1186d45701ccSGreg Roach
11875a78cd34SGreg Roach            Session::put('cart', $cart);
11885a78cd34SGreg Roach        }
1189d45701ccSGreg Roach    }
11905a78cd34SGreg Roach
11915a78cd34SGreg Roach    /**
1192d45701ccSGreg Roach     * @param GedcomRecord $record
11935a78cd34SGreg Roach     */
1194d45701ccSGreg Roach    protected function addNoteLinksToCart(GedcomRecord $record): void
1195d45701ccSGreg Roach    {
1196d45701ccSGreg Roach        preg_match_all('/\n\d NOTE @(' . Gedcom::REGEX_XREF . ')@/', $record->gedcom(), $matches);
1197d45701ccSGreg Roach
1198d45701ccSGreg Roach        foreach ($matches[1] as $xref) {
1199d45701ccSGreg Roach            $note = Registry::noteFactory()->make($xref, $record->tree());
1200d45701ccSGreg Roach
1201d45701ccSGreg Roach            if ($note instanceof Note && $note->canShow()) {
1202d45701ccSGreg Roach                $this->addNoteToCart($note);
1203d45701ccSGreg Roach            }
1204d45701ccSGreg Roach        }
1205d45701ccSGreg Roach    }
1206d45701ccSGreg Roach
1207d45701ccSGreg Roach    /**
1208d45701ccSGreg Roach     * @param Source $source
1209d45701ccSGreg Roach     */
1210d45701ccSGreg Roach    protected function addSourceToCart(Source $source): void
1211c1010edaSGreg Roach    {
12125a78cd34SGreg Roach        $cart = Session::get('cart', []);
1213d45701ccSGreg Roach        $tree = $source->tree()->name();
1214d45701ccSGreg Roach        $xref = $source->xref();
12155a78cd34SGreg Roach
1216d45701ccSGreg Roach        if (($cart[$tree][$xref] ?? false) === false) {
1217d45701ccSGreg Roach            $cart[$tree][$xref] = true;
1218d45701ccSGreg Roach
1219d45701ccSGreg Roach            Session::put('cart', $cart);
1220d45701ccSGreg Roach
1221d45701ccSGreg Roach            $this->addNoteLinksToCart($source);
1222d45701ccSGreg Roach            $this->addRepositoryLinksToCart($source);
1223d45701ccSGreg Roach        }
1224d45701ccSGreg Roach    }
1225d45701ccSGreg Roach
1226d45701ccSGreg Roach    /**
1227d45701ccSGreg Roach     * @param GedcomRecord $record
1228d45701ccSGreg Roach     */
1229d45701ccSGreg Roach    protected function addSourceLinksToCart(GedcomRecord $record): void
1230d45701ccSGreg Roach    {
1231d45701ccSGreg Roach        preg_match_all('/\n\d SOUR @(' . Gedcom::REGEX_XREF . ')@/', $record->gedcom(), $matches);
1232d45701ccSGreg Roach
1233d45701ccSGreg Roach        foreach ($matches[1] as $xref) {
1234d45701ccSGreg Roach            $source = Registry::sourceFactory()->make($xref, $record->tree());
1235d45701ccSGreg Roach
1236d45701ccSGreg Roach            if ($source instanceof Source && $source->canShow()) {
1237d45701ccSGreg Roach                $this->addSourceToCart($source);
1238d45701ccSGreg Roach            }
1239d45701ccSGreg Roach        }
1240d45701ccSGreg Roach    }
1241d45701ccSGreg Roach
1242d45701ccSGreg Roach    /**
1243d45701ccSGreg Roach     * @param Repository $repository
1244d45701ccSGreg Roach     */
1245d45701ccSGreg Roach    protected function addRepositoryToCart(Repository $repository): void
1246d45701ccSGreg Roach    {
1247d45701ccSGreg Roach        $cart = Session::get('cart', []);
1248d45701ccSGreg Roach        $tree = $repository->tree()->name();
1249d45701ccSGreg Roach        $xref = $repository->xref();
1250d45701ccSGreg Roach
1251d45701ccSGreg Roach        if (($cart[$tree][$xref] ?? false) === false) {
1252d45701ccSGreg Roach            $cart[$tree][$xref] = true;
1253d45701ccSGreg Roach
1254d45701ccSGreg Roach            Session::put('cart', $cart);
1255d45701ccSGreg Roach
1256d45701ccSGreg Roach            $this->addNoteLinksToCart($repository);
1257d45701ccSGreg Roach        }
1258d45701ccSGreg Roach    }
1259d45701ccSGreg Roach
1260d45701ccSGreg Roach    /**
1261d45701ccSGreg Roach     * @param GedcomRecord $record
1262d45701ccSGreg Roach     */
1263d45701ccSGreg Roach    protected function addRepositoryLinksToCart(GedcomRecord $record): void
1264d45701ccSGreg Roach    {
1265d45701ccSGreg Roach        preg_match_all('/\n\d REPO @(' . Gedcom::REGEX_XREF . '@)/', $record->gedcom(), $matches);
1266d45701ccSGreg Roach
1267d45701ccSGreg Roach        foreach ($matches[1] as $xref) {
1268d45701ccSGreg Roach            $repository = Registry::repositoryFactory()->make($xref, $record->tree());
1269d45701ccSGreg Roach
1270d45701ccSGreg Roach            if ($repository instanceof Repository && $repository->canShow()) {
1271d45701ccSGreg Roach                $this->addRepositoryToCart($repository);
1272d45701ccSGreg Roach            }
1273d45701ccSGreg Roach        }
1274d45701ccSGreg Roach    }
1275d45701ccSGreg Roach
1276d45701ccSGreg Roach    /**
1277d45701ccSGreg Roach     * @param Submitter $submitter
1278d45701ccSGreg Roach     */
1279d45701ccSGreg Roach    protected function addSubmitterToCart(Submitter $submitter): void
1280d45701ccSGreg Roach    {
1281d45701ccSGreg Roach        $cart = Session::get('cart', []);
1282d45701ccSGreg Roach        $tree = $submitter->tree()->name();
1283d45701ccSGreg Roach        $xref = $submitter->xref();
1284d45701ccSGreg Roach
1285d45701ccSGreg Roach        if (($cart[$tree][$xref] ?? false) === false) {
1286d45701ccSGreg Roach            $cart[$tree][$xref] = true;
1287d45701ccSGreg Roach
1288d45701ccSGreg Roach            Session::put('cart', $cart);
1289d45701ccSGreg Roach
1290d45701ccSGreg Roach            $this->addNoteLinksToCart($submitter);
1291d45701ccSGreg Roach        }
1292d45701ccSGreg Roach    }
1293d45701ccSGreg Roach
1294d45701ccSGreg Roach    /**
1295d45701ccSGreg Roach     * @param GedcomRecord $record
1296d45701ccSGreg Roach     */
1297d45701ccSGreg Roach    protected function addSubmitterLinksToCart(GedcomRecord $record): void
1298d45701ccSGreg Roach    {
1299d45701ccSGreg Roach        preg_match_all('/\n\d SUBM @(' . Gedcom::REGEX_XREF . ')@/', $record->gedcom(), $matches);
1300d45701ccSGreg Roach
1301d45701ccSGreg Roach        foreach ($matches[1] as $xref) {
1302d45701ccSGreg Roach            $submitter = Registry::submitterFactory()->make($xref, $record->tree());
1303d45701ccSGreg Roach
1304d45701ccSGreg Roach            if ($submitter instanceof Submitter && $submitter->canShow()) {
1305d45701ccSGreg Roach                $this->addSubmitterToCart($submitter);
1306d45701ccSGreg Roach            }
1307d45701ccSGreg Roach        }
13085a78cd34SGreg Roach    }
13098c2e8227SGreg Roach}
1310