xref: /webtrees/app/Module/SiteMapModule.php (revision a09ea7cc9ca431639d5ceeae1f257bc3ec3752d1)
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
2216a40a66SGreg Roachuse Aura\Router\Route;
2316a40a66SGreg Roachuse Aura\Router\RouterContainer;
246ccdf4f0SGreg Roachuse Fig\Http\Message\StatusCodeInterface;
25659aa375SGreg Roachuse Fisharebest\Webtrees\Auth;
26c5a1ffe6SGreg Roachuse Fisharebest\Webtrees\Family;
27a5f7ed67SGreg Roachuse Fisharebest\Webtrees\FlashMessages;
28a5f7ed67SGreg Roachuse Fisharebest\Webtrees\GedcomRecord;
29b1b85189SGreg Roachuse Fisharebest\Webtrees\Html;
3081b729d3SGreg Roachuse Fisharebest\Webtrees\Http\Exceptions\HttpNotFoundException;
310e62c4b8SGreg Roachuse Fisharebest\Webtrees\I18N;
320e62c4b8SGreg Roachuse Fisharebest\Webtrees\Individual;
330e62c4b8SGreg Roachuse Fisharebest\Webtrees\Media;
340e62c4b8SGreg Roachuse Fisharebest\Webtrees\Note;
3581b729d3SGreg Roachuse Fisharebest\Webtrees\Registry;
360e62c4b8SGreg Roachuse Fisharebest\Webtrees\Repository;
373df1e584SGreg Roachuse Fisharebest\Webtrees\Services\TreeService;
380e62c4b8SGreg Roachuse Fisharebest\Webtrees\Source;
39c5a1ffe6SGreg Roachuse Fisharebest\Webtrees\Submitter;
400e62c4b8SGreg Roachuse Fisharebest\Webtrees\Tree;
41fa17fb66SGreg Roachuse Illuminate\Database\Capsule\Manager as DB;
42a69f5655SGreg Roachuse Illuminate\Database\Query\Expression;
43886b77daSGreg Roachuse Illuminate\Support\Collection;
446ccdf4f0SGreg Roachuse Psr\Http\Message\ResponseInterface;
456ccdf4f0SGreg Roachuse Psr\Http\Message\ServerRequestInterface;
4616a40a66SGreg Roachuse Psr\Http\Server\RequestHandlerInterface;
47d519210eSGreg Roach
4816a40a66SGreg Roachuse function app;
4916a40a66SGreg Roachuse function assert;
5016a40a66SGreg Roachuse function date;
513df1e584SGreg Roachuse function redirect;
5216a40a66SGreg Roachuse function response;
5316a40a66SGreg Roachuse function route;
543df1e584SGreg Roachuse function view;
553df1e584SGreg Roach
568c2e8227SGreg Roach/**
578c2e8227SGreg Roach * Class SiteMapModule
588c2e8227SGreg Roach */
5916a40a66SGreg Roachclass SiteMapModule extends AbstractModule implements ModuleConfigInterface, RequestHandlerInterface
60c1010edaSGreg Roach{
6149a243cbSGreg Roach    use ModuleConfigTrait;
6249a243cbSGreg Roach
6316d6367aSGreg Roach    private const RECORDS_PER_VOLUME = 500; // Keep sitemap files small, for memory, CPU and max_allowed_packet limits.
640daf451eSGreg Roach    private const CACHE_LIFE         = 209600; // Two weeks
658c2e8227SGreg Roach
66c5a1ffe6SGreg Roach    private const PRIORITY = [
67c5a1ffe6SGreg Roach        Family::RECORD_TYPE     => 0.7,
68c5a1ffe6SGreg Roach        Individual::RECORD_TYPE => 0.9,
69c5a1ffe6SGreg Roach        Media::RECORD_TYPE      => 0.5,
70c5a1ffe6SGreg Roach        Note::RECORD_TYPE       => 0.3,
71c5a1ffe6SGreg Roach        Repository::RECORD_TYPE => 0.5,
72c5a1ffe6SGreg Roach        Source::RECORD_TYPE     => 0.5,
73c5a1ffe6SGreg Roach        Submitter::RECORD_TYPE  => 0.3,
74c5a1ffe6SGreg Roach    ];
75c5a1ffe6SGreg Roach
7643f2f523SGreg Roach    private TreeService $tree_service;
773df1e584SGreg Roach
783df1e584SGreg Roach    /**
793df1e584SGreg Roach     * TreesMenuModule constructor.
803df1e584SGreg Roach     *
813df1e584SGreg Roach     * @param TreeService $tree_service
823df1e584SGreg Roach     */
833df1e584SGreg Roach    public function __construct(TreeService $tree_service)
843df1e584SGreg Roach    {
853df1e584SGreg Roach        $this->tree_service = $tree_service;
863df1e584SGreg Roach    }
873df1e584SGreg Roach
88a5f7ed67SGreg Roach    /**
8916a40a66SGreg Roach     * Initialization.
9016a40a66SGreg Roach     *
9116a40a66SGreg Roach     * @return void
9216a40a66SGreg Roach     */
9316a40a66SGreg Roach    public function boot(): void
9416a40a66SGreg Roach    {
9516a40a66SGreg Roach        $router_container = app(RouterContainer::class);
9616a40a66SGreg Roach        assert($router_container instanceof RouterContainer);
9716a40a66SGreg Roach
9816a40a66SGreg Roach        $router_container->getMap()
9914aabe72SGreg Roach            ->get('sitemap-style', '/sitemap.xsl', $this);
10014aabe72SGreg Roach
10114aabe72SGreg Roach        $router_container->getMap()
10216a40a66SGreg Roach            ->get('sitemap-index', '/sitemap.xml', $this);
10316a40a66SGreg Roach
10416a40a66SGreg Roach        $router_container->getMap()
105c5a1ffe6SGreg Roach            ->get('sitemap-file', '/sitemap-{tree}-{type}-{page}.xml', $this);
10616a40a66SGreg Roach    }
10716a40a66SGreg Roach
10816a40a66SGreg Roach    /**
109a5f7ed67SGreg Roach     * A sentence describing what this module does.
110a5f7ed67SGreg Roach     *
111a5f7ed67SGreg Roach     * @return string
112a5f7ed67SGreg Roach     */
11349a243cbSGreg Roach    public function description(): string
114c1010edaSGreg Roach    {
115bbb76c12SGreg Roach        /* I18N: Description of the “Sitemaps” module */
116bbb76c12SGreg Roach        return I18N::translate('Generate sitemap files for search engines.');
1178c2e8227SGreg Roach    }
1188c2e8227SGreg Roach
11976692c8bSGreg Roach    /**
120abafa13cSGreg Roach     * Should this module be enabled when it is first installed?
121abafa13cSGreg Roach     *
122abafa13cSGreg Roach     * @return bool
123abafa13cSGreg Roach     */
124abafa13cSGreg Roach    public function isEnabledByDefault(): bool
125abafa13cSGreg Roach    {
126abafa13cSGreg Roach        return false;
127abafa13cSGreg Roach    }
128abafa13cSGreg Roach
129abafa13cSGreg Roach    /**
13057ab2231SGreg Roach     * @param ServerRequestInterface $request
13157ab2231SGreg Roach     *
1326ccdf4f0SGreg Roach     * @return ResponseInterface
1338c2e8227SGreg Roach     */
134*a09ea7ccSGreg Roach    public function getAdminAction(/** @scrutinizer ignore-unused */ ServerRequestInterface $request): ResponseInterface
135c1010edaSGreg Roach    {
136a5f7ed67SGreg Roach        $this->layout = 'layouts/administration';
137a5f7ed67SGreg Roach
13816a40a66SGreg Roach        $sitemap_url = route('sitemap-index');
139a5f7ed67SGreg Roach
140ce42304aSGreg Roach        // This list comes from https://en.wikipedia.org/wiki/Sitemaps
141a5f7ed67SGreg Roach        $submit_urls = [
142a5f7ed67SGreg Roach            'Bing/Yahoo' => Html::url('https://www.bing.com/webmaster/ping.aspx', ['siteMap' => $sitemap_url]),
143a5f7ed67SGreg Roach            'Google'     => Html::url('https://www.google.com/webmasters/tools/ping', ['sitemap' => $sitemap_url]),
144a5f7ed67SGreg Roach        ];
145a5f7ed67SGreg Roach
146291c1b19SGreg Roach        return $this->viewResponse('modules/sitemap/config', [
1473df1e584SGreg Roach            'all_trees'   => $this->tree_service->all(),
148a5f7ed67SGreg Roach            'sitemap_url' => $sitemap_url,
149a5f7ed67SGreg Roach            'submit_urls' => $submit_urls,
15049a243cbSGreg Roach            'title'       => $this->title(),
151a5f7ed67SGreg Roach        ]);
1528c2e8227SGreg Roach    }
1538c2e8227SGreg Roach
1548c2e8227SGreg Roach    /**
1556ccdf4f0SGreg Roach     * How should this module be identified in the control panel, etc.?
156a5f7ed67SGreg Roach     *
1576ccdf4f0SGreg Roach     * @return string
1588c2e8227SGreg Roach     */
1596ccdf4f0SGreg Roach    public function title(): string
1606ccdf4f0SGreg Roach    {
161ad3143ccSGreg Roach        /* I18N: Name of a module - see https://en.wikipedia.org/wiki/Sitemaps */
1626ccdf4f0SGreg Roach        return I18N::translate('Sitemaps');
1636ccdf4f0SGreg Roach    }
1646ccdf4f0SGreg Roach
1656ccdf4f0SGreg Roach    /**
1666ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
1676ccdf4f0SGreg Roach     *
1686ccdf4f0SGreg Roach     * @return ResponseInterface
1696ccdf4f0SGreg Roach     */
1706ccdf4f0SGreg Roach    public function postAdminAction(ServerRequestInterface $request): ResponseInterface
171c1010edaSGreg Roach    {
172b46c87bdSGreg Roach        $params = (array) $request->getParsedBody();
173b6b9dcc9SGreg Roach
1743df1e584SGreg Roach        foreach ($this->tree_service->all() as $tree) {
175b6b9dcc9SGreg Roach            $include_in_sitemap = (bool) ($params['sitemap' . $tree->id()] ?? false);
176a5f7ed67SGreg Roach            $tree->setPreference('include_in_sitemap', (string) $include_in_sitemap);
1778c2e8227SGreg Roach        }
178a5f7ed67SGreg Roach
17949a243cbSGreg Roach        FlashMessages::addMessage(I18N::translate('The preferences for the module “%s” have been updated.', $this->title()), 'success');
180a5f7ed67SGreg Roach
1816ccdf4f0SGreg Roach        return redirect($this->getConfigLink());
1828c2e8227SGreg Roach    }
1838c2e8227SGreg Roach
1848c2e8227SGreg Roach    /**
18557ab2231SGreg Roach     * @param ServerRequestInterface $request
18657ab2231SGreg Roach     *
1876ccdf4f0SGreg Roach     * @return ResponseInterface
1888c2e8227SGreg Roach     */
18916a40a66SGreg Roach    public function handle(ServerRequestInterface $request): ResponseInterface
190c1010edaSGreg Roach    {
19116a40a66SGreg Roach        $route = $request->getAttribute('route');
19216a40a66SGreg Roach        assert($route instanceof Route);
193a5f7ed67SGreg Roach
19414aabe72SGreg Roach        if ($route->name === 'sitemap-style') {
19514aabe72SGreg Roach            $content = view('modules/sitemap/sitemap-xsl');
19614aabe72SGreg Roach
19714aabe72SGreg Roach            return response($content, StatusCodeInterface::STATUS_OK, [
19814aabe72SGreg Roach                'Content-Type' => 'application/xml',
19914aabe72SGreg Roach            ]);
20014aabe72SGreg Roach        }
20114aabe72SGreg Roach
20216a40a66SGreg Roach        if ($route->name === 'sitemap-index') {
20316a40a66SGreg Roach            return $this->siteMapIndex($request);
20416a40a66SGreg Roach        }
20516a40a66SGreg Roach
20616a40a66SGreg Roach        return $this->siteMapFile($request);
20716a40a66SGreg Roach    }
20816a40a66SGreg Roach
20916a40a66SGreg Roach    /**
21016a40a66SGreg Roach     * @param ServerRequestInterface $request
21116a40a66SGreg Roach     *
21216a40a66SGreg Roach     * @return ResponseInterface
21316a40a66SGreg Roach     */
214*a09ea7ccSGreg Roach    private function siteMapIndex(/** @scrutinizer ignore-unused */ ServerRequestInterface $request): ResponseInterface
21516a40a66SGreg Roach    {
2166b9cb339SGreg Roach        $content = Registry::cache()->file()->remember('sitemap.xml', function (): string {
217659aa375SGreg Roach            // Which trees have sitemaps enabled?
218659aa375SGreg Roach            $tree_ids = $this->tree_service->all()->filter(static function (Tree $tree): bool {
219659aa375SGreg Roach                return $tree->getPreference('include_in_sitemap') === '1';
220659aa375SGreg Roach            })->map(static function (Tree $tree): int {
221659aa375SGreg Roach                return $tree->id();
222659aa375SGreg Roach            });
223659aa375SGreg Roach
224c5a1ffe6SGreg Roach            $count_families = DB::table('families')
225c5a1ffe6SGreg Roach                ->join('gedcom', 'f_file', '=', 'gedcom_id')
226c5a1ffe6SGreg Roach                ->whereIn('gedcom_id', $tree_ids)
227c5a1ffe6SGreg Roach                ->groupBy(['gedcom_id'])
228c5a1ffe6SGreg Roach                ->select([new Expression('COUNT(*) AS total'), 'gedcom_name'])
229c5a1ffe6SGreg Roach                ->pluck('total', 'gedcom_name');
230c5a1ffe6SGreg Roach
231fa17fb66SGreg Roach            $count_individuals = DB::table('individuals')
23216a40a66SGreg Roach                ->join('gedcom', 'i_file', '=', 'gedcom_id')
23316a40a66SGreg Roach                ->whereIn('gedcom_id', $tree_ids)
23416a40a66SGreg Roach                ->groupBy(['gedcom_id'])
23516a40a66SGreg Roach                ->select([new Expression('COUNT(*) AS total'), 'gedcom_name'])
23616a40a66SGreg Roach                ->pluck('total', 'gedcom_name');
237a5f7ed67SGreg Roach
238fa17fb66SGreg Roach            $count_media = DB::table('media')
23916a40a66SGreg Roach                ->join('gedcom', 'm_file', '=', 'gedcom_id')
24016a40a66SGreg Roach                ->whereIn('gedcom_id', $tree_ids)
24116a40a66SGreg Roach                ->groupBy(['gedcom_id'])
24216a40a66SGreg Roach                ->select([new Expression('COUNT(*) AS total'), 'gedcom_name'])
24316a40a66SGreg Roach                ->pluck('total', 'gedcom_name');
244a5f7ed67SGreg Roach
245fa17fb66SGreg Roach            $count_notes = DB::table('other')
24616a40a66SGreg Roach                ->join('gedcom', 'o_file', '=', 'gedcom_id')
24716a40a66SGreg Roach                ->whereIn('gedcom_id', $tree_ids)
248c5a1ffe6SGreg Roach                ->where('o_type', '=', Note::RECORD_TYPE)
24916a40a66SGreg Roach                ->groupBy(['gedcom_id'])
25016a40a66SGreg Roach                ->select([new Expression('COUNT(*) AS total'), 'gedcom_name'])
25116a40a66SGreg Roach                ->pluck('total', 'gedcom_name');
252a5f7ed67SGreg Roach
253fa17fb66SGreg Roach            $count_repositories = DB::table('other')
25416a40a66SGreg Roach                ->join('gedcom', 'o_file', '=', 'gedcom_id')
25516a40a66SGreg Roach                ->whereIn('gedcom_id', $tree_ids)
256c5a1ffe6SGreg Roach                ->where('o_type', '=', Repository::RECORD_TYPE)
25716a40a66SGreg Roach                ->groupBy(['gedcom_id'])
25816a40a66SGreg Roach                ->select([new Expression('COUNT(*) AS total'), 'gedcom_name'])
25916a40a66SGreg Roach                ->pluck('total', 'gedcom_name');
260a5f7ed67SGreg Roach
261fa17fb66SGreg Roach            $count_sources = DB::table('sources')
26216a40a66SGreg Roach                ->join('gedcom', 's_file', '=', 'gedcom_id')
26316a40a66SGreg Roach                ->whereIn('gedcom_id', $tree_ids)
26416a40a66SGreg Roach                ->groupBy(['gedcom_id'])
26516a40a66SGreg Roach                ->select([new Expression('COUNT(*) AS total'), 'gedcom_name'])
26616a40a66SGreg Roach                ->pluck('total', 'gedcom_name');
267a5f7ed67SGreg Roach
268c5a1ffe6SGreg Roach            $count_submitters = DB::table('other')
269c5a1ffe6SGreg Roach                ->join('gedcom', 'o_file', '=', 'gedcom_id')
270c5a1ffe6SGreg Roach                ->whereIn('gedcom_id', $tree_ids)
271c5a1ffe6SGreg Roach                ->where('o_type', '=', Submitter::RECORD_TYPE)
272c5a1ffe6SGreg Roach                ->groupBy(['gedcom_id'])
273c5a1ffe6SGreg Roach                ->select([new Expression('COUNT(*) AS total'), 'gedcom_name'])
274c5a1ffe6SGreg Roach                ->pluck('total', 'gedcom_name');
275c5a1ffe6SGreg Roach
27616a40a66SGreg Roach            // Versions 2.0.1 and earlier of this module stored large amounts of data in the settings.
27716a40a66SGreg Roach            DB::table('module_setting')
27816a40a66SGreg Roach                ->where('module_name', '=', $this->name())
27916a40a66SGreg Roach                ->delete();
28016a40a66SGreg Roach
28114aabe72SGreg Roach            return view('modules/sitemap/sitemap-index-xml', [
2823df1e584SGreg Roach                'all_trees'          => $this->tree_service->all(),
283c5a1ffe6SGreg Roach                'count_families'     => $count_families,
284a5f7ed67SGreg Roach                'count_individuals'  => $count_individuals,
285a5f7ed67SGreg Roach                'count_media'        => $count_media,
286a5f7ed67SGreg Roach                'count_notes'        => $count_notes,
287a5f7ed67SGreg Roach                'count_repositories' => $count_repositories,
288a5f7ed67SGreg Roach                'count_sources'      => $count_sources,
289c5a1ffe6SGreg Roach                'count_submitters'   => $count_submitters,
290a5f7ed67SGreg Roach                'last_mod'           => date('Y-m-d'),
291a5f7ed67SGreg Roach                'records_per_volume' => self::RECORDS_PER_VOLUME,
29214aabe72SGreg Roach                'sitemap_xsl'        => route('sitemap-style'),
293a5f7ed67SGreg Roach            ]);
29416a40a66SGreg Roach        }, self::CACHE_LIFE);
295a5f7ed67SGreg Roach
2966ccdf4f0SGreg Roach        return response($content, StatusCodeInterface::STATUS_OK, [
297a5f7ed67SGreg Roach            'Content-Type' => 'application/xml',
298a5f7ed67SGreg Roach        ]);
299a5f7ed67SGreg Roach    }
300a5f7ed67SGreg Roach
301a5f7ed67SGreg Roach    /**
3026ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
303a5f7ed67SGreg Roach     *
3046ccdf4f0SGreg Roach     * @return ResponseInterface
305a5f7ed67SGreg Roach     */
30616a40a66SGreg Roach    private function siteMapFile(ServerRequestInterface $request): ResponseInterface
307c1010edaSGreg Roach    {
30816a40a66SGreg Roach        $tree = $request->getAttribute('tree');
30916a40a66SGreg Roach        assert($tree instanceof Tree);
310a5f7ed67SGreg Roach
311c5a1ffe6SGreg Roach        $type = $request->getAttribute('type');
312c5a1ffe6SGreg Roach        $page = (int) $request->getAttribute('page');
313a5f7ed67SGreg Roach
314659aa375SGreg Roach        if ($tree->getPreference('include_in_sitemap') !== '1') {
315659aa375SGreg Roach            throw new HttpNotFoundException();
316659aa375SGreg Roach        }
317659aa375SGreg Roach
318c5a1ffe6SGreg Roach        $cache_key = 'sitemap/' . $tree->id() . '/' . $type . '/' . $page . '.xml';
31916a40a66SGreg Roach
3206b9cb339SGreg Roach        $content = Registry::cache()->file()->remember($cache_key, function () use ($tree, $type, $page): string {
321c5a1ffe6SGreg Roach            $records = $this->sitemapRecords($tree, $type, self::RECORDS_PER_VOLUME, self::RECORDS_PER_VOLUME * $page);
32216a40a66SGreg Roach
32314aabe72SGreg Roach            return view('modules/sitemap/sitemap-file-xml', [
324c5a1ffe6SGreg Roach                'priority'    => self::PRIORITY[$type],
3259e3c7a99SGreg Roach                'records'     => $records,
32614aabe72SGreg Roach                'sitemap_xsl' => route('sitemap-style'),
3279e3c7a99SGreg Roach                'tree'        => $tree,
3289e3c7a99SGreg Roach            ]);
32916a40a66SGreg Roach        }, self::CACHE_LIFE);
330a5f7ed67SGreg Roach
3316ccdf4f0SGreg Roach        return response($content, StatusCodeInterface::STATUS_OK, [
332a5f7ed67SGreg Roach            'Content-Type' => 'application/xml',
333a5f7ed67SGreg Roach        ]);
334a5f7ed67SGreg Roach    }
335a5f7ed67SGreg Roach
336a5f7ed67SGreg Roach    /**
337a5f7ed67SGreg Roach     * @param Tree   $tree
338a5f7ed67SGreg Roach     * @param string $type
339a5f7ed67SGreg Roach     * @param int    $limit
340a5f7ed67SGreg Roach     * @param int    $offset
341a5f7ed67SGreg Roach     *
342b5c8fd7eSGreg Roach     * @return Collection<GedcomRecord>
343a5f7ed67SGreg Roach     */
344886b77daSGreg Roach    private function sitemapRecords(Tree $tree, string $type, int $limit, int $offset): Collection
345c1010edaSGreg Roach    {
346a5f7ed67SGreg Roach        switch ($type) {
347c5a1ffe6SGreg Roach            case Family::RECORD_TYPE:
348c5a1ffe6SGreg Roach                $records = $this->sitemapFamilies($tree, $limit, $offset);
349c5a1ffe6SGreg Roach                break;
350c5a1ffe6SGreg Roach
35116a40a66SGreg Roach            case Individual::RECORD_TYPE:
352a5f7ed67SGreg Roach                $records = $this->sitemapIndividuals($tree, $limit, $offset);
353a5f7ed67SGreg Roach                break;
354a5f7ed67SGreg Roach
35516a40a66SGreg Roach            case Media::RECORD_TYPE:
356a5f7ed67SGreg Roach                $records = $this->sitemapMedia($tree, $limit, $offset);
357a5f7ed67SGreg Roach                break;
358a5f7ed67SGreg Roach
35916a40a66SGreg Roach            case Note::RECORD_TYPE:
360a5f7ed67SGreg Roach                $records = $this->sitemapNotes($tree, $limit, $offset);
361a5f7ed67SGreg Roach                break;
362a5f7ed67SGreg Roach
36316a40a66SGreg Roach            case Repository::RECORD_TYPE:
364a5f7ed67SGreg Roach                $records = $this->sitemapRepositories($tree, $limit, $offset);
365a5f7ed67SGreg Roach                break;
366a5f7ed67SGreg Roach
36716a40a66SGreg Roach            case Source::RECORD_TYPE:
368a5f7ed67SGreg Roach                $records = $this->sitemapSources($tree, $limit, $offset);
369a5f7ed67SGreg Roach                break;
370a5f7ed67SGreg Roach
371c5a1ffe6SGreg Roach            case Submitter::RECORD_TYPE:
372c5a1ffe6SGreg Roach                $records = $this->sitemapSubmitters($tree, $limit, $offset);
373c5a1ffe6SGreg Roach                break;
374c5a1ffe6SGreg Roach
375a5f7ed67SGreg Roach            default:
376d501c45dSGreg Roach                throw new HttpNotFoundException('Invalid record type: ' . $type);
377a5f7ed67SGreg Roach        }
378a5f7ed67SGreg Roach
379a5f7ed67SGreg Roach        // Skip private records.
380659aa375SGreg Roach        $records = $records->filter(static function (GedcomRecord $record): bool {
381659aa375SGreg Roach            return $record->canShow(Auth::PRIV_PRIVATE);
382659aa375SGreg Roach        });
383a5f7ed67SGreg Roach
384a5f7ed67SGreg Roach        return $records;
385a5f7ed67SGreg Roach    }
386a5f7ed67SGreg Roach
387a5f7ed67SGreg Roach    /**
388a5f7ed67SGreg Roach     * @param Tree $tree
389a5f7ed67SGreg Roach     * @param int  $limit
390a5f7ed67SGreg Roach     * @param int  $offset
391a5f7ed67SGreg Roach     *
392c5a1ffe6SGreg Roach     * @return Collection<Family>
393c5a1ffe6SGreg Roach     */
394c5a1ffe6SGreg Roach    private function sitemapFamilies(Tree $tree, int $limit, int $offset): Collection
395c5a1ffe6SGreg Roach    {
396c5a1ffe6SGreg Roach        return DB::table('families')
397c5a1ffe6SGreg Roach            ->where('f_file', '=', $tree->id())
398c5a1ffe6SGreg Roach            ->orderBy('f_id')
399c5a1ffe6SGreg Roach            ->skip($offset)
400c5a1ffe6SGreg Roach            ->take($limit)
401c5a1ffe6SGreg Roach            ->get()
4026b9cb339SGreg Roach            ->map(Registry::familyFactory()->mapper($tree));
403c5a1ffe6SGreg Roach    }
404c5a1ffe6SGreg Roach
405c5a1ffe6SGreg Roach    /**
406c5a1ffe6SGreg Roach     * @param Tree $tree
407c5a1ffe6SGreg Roach     * @param int  $limit
408c5a1ffe6SGreg Roach     * @param int  $offset
409c5a1ffe6SGreg Roach     *
410b5c8fd7eSGreg Roach     * @return Collection<Individual>
411a5f7ed67SGreg Roach     */
412886b77daSGreg Roach    private function sitemapIndividuals(Tree $tree, int $limit, int $offset): Collection
413c1010edaSGreg Roach    {
414886b77daSGreg Roach        return DB::table('individuals')
415fa17fb66SGreg Roach            ->where('i_file', '=', $tree->id())
416fa17fb66SGreg Roach            ->orderBy('i_id')
417fa17fb66SGreg Roach            ->skip($offset)
418fa17fb66SGreg Roach            ->take($limit)
419886b77daSGreg Roach            ->get()
4206b9cb339SGreg Roach            ->map(Registry::individualFactory()->mapper($tree));
4218c2e8227SGreg Roach    }
422a5f7ed67SGreg Roach
423a5f7ed67SGreg Roach    /**
424a5f7ed67SGreg Roach     * @param Tree $tree
425a5f7ed67SGreg Roach     * @param int  $limit
426a5f7ed67SGreg Roach     * @param int  $offset
427a5f7ed67SGreg Roach     *
428b5c8fd7eSGreg Roach     * @return Collection<Media>
429a5f7ed67SGreg Roach     */
430886b77daSGreg Roach    private function sitemapMedia(Tree $tree, int $limit, int $offset): Collection
431c1010edaSGreg Roach    {
432886b77daSGreg Roach        return DB::table('media')
433fa17fb66SGreg Roach            ->where('m_file', '=', $tree->id())
434fa17fb66SGreg Roach            ->orderBy('m_id')
435fa17fb66SGreg Roach            ->skip($offset)
436fa17fb66SGreg Roach            ->take($limit)
437886b77daSGreg Roach            ->get()
4386b9cb339SGreg Roach            ->map(Registry::mediaFactory()->mapper($tree));
4398c2e8227SGreg Roach    }
4408c2e8227SGreg Roach
4418c2e8227SGreg Roach    /**
442a5f7ed67SGreg Roach     * @param Tree $tree
443a5f7ed67SGreg Roach     * @param int  $limit
444a5f7ed67SGreg Roach     * @param int  $offset
445a5f7ed67SGreg Roach     *
446b5c8fd7eSGreg Roach     * @return Collection<Note>
4478c2e8227SGreg Roach     */
448886b77daSGreg Roach    private function sitemapNotes(Tree $tree, int $limit, int $offset): Collection
449c1010edaSGreg Roach    {
450886b77daSGreg Roach        return DB::table('other')
451fa17fb66SGreg Roach            ->where('o_file', '=', $tree->id())
452c5a1ffe6SGreg Roach            ->where('o_type', '=', Note::RECORD_TYPE)
453fa17fb66SGreg Roach            ->orderBy('o_id')
454fa17fb66SGreg Roach            ->skip($offset)
455fa17fb66SGreg Roach            ->take($limit)
456886b77daSGreg Roach            ->get()
4576b9cb339SGreg Roach            ->map(Registry::noteFactory()->mapper($tree));
4588c2e8227SGreg Roach    }
4598c2e8227SGreg Roach
460a5f7ed67SGreg Roach    /**
461a5f7ed67SGreg Roach     * @param Tree $tree
462a5f7ed67SGreg Roach     * @param int  $limit
463a5f7ed67SGreg Roach     * @param int  $offset
464a5f7ed67SGreg Roach     *
465b5c8fd7eSGreg Roach     * @return Collection<Repository>
466a5f7ed67SGreg Roach     */
467886b77daSGreg Roach    private function sitemapRepositories(Tree $tree, int $limit, int $offset): Collection
468c1010edaSGreg Roach    {
469886b77daSGreg Roach        return DB::table('other')
470fa17fb66SGreg Roach            ->where('o_file', '=', $tree->id())
471c5a1ffe6SGreg Roach            ->where('o_type', '=', Repository::RECORD_TYPE)
472fa17fb66SGreg Roach            ->orderBy('o_id')
473fa17fb66SGreg Roach            ->skip($offset)
474fa17fb66SGreg Roach            ->take($limit)
475886b77daSGreg Roach            ->get()
4766b9cb339SGreg Roach            ->map(Registry::repositoryFactory()->mapper($tree));
477a5f7ed67SGreg Roach    }
478a5f7ed67SGreg Roach
479a5f7ed67SGreg Roach    /**
480a5f7ed67SGreg Roach     * @param Tree $tree
481a5f7ed67SGreg Roach     * @param int  $limit
482a5f7ed67SGreg Roach     * @param int  $offset
483a5f7ed67SGreg Roach     *
484b5c8fd7eSGreg Roach     * @return Collection<Source>
485a5f7ed67SGreg Roach     */
486886b77daSGreg Roach    private function sitemapSources(Tree $tree, int $limit, int $offset): Collection
487c1010edaSGreg Roach    {
488886b77daSGreg Roach        return DB::table('sources')
489fa17fb66SGreg Roach            ->where('s_file', '=', $tree->id())
490fa17fb66SGreg Roach            ->orderBy('s_id')
491fa17fb66SGreg Roach            ->skip($offset)
492fa17fb66SGreg Roach            ->take($limit)
493886b77daSGreg Roach            ->get()
4946b9cb339SGreg Roach            ->map(Registry::sourceFactory()->mapper($tree));
4958c2e8227SGreg Roach    }
496c5a1ffe6SGreg Roach
497c5a1ffe6SGreg Roach    /**
498c5a1ffe6SGreg Roach     * @param Tree $tree
499c5a1ffe6SGreg Roach     * @param int  $limit
500c5a1ffe6SGreg Roach     * @param int  $offset
501c5a1ffe6SGreg Roach     *
502c5a1ffe6SGreg Roach     * @return Collection<Submitter>
503c5a1ffe6SGreg Roach     */
504c5a1ffe6SGreg Roach    private function sitemapSubmitters(Tree $tree, int $limit, int $offset): Collection
505c5a1ffe6SGreg Roach    {
506c5a1ffe6SGreg Roach        return DB::table('other')
507c5a1ffe6SGreg Roach            ->where('o_file', '=', $tree->id())
508c5a1ffe6SGreg Roach            ->where('o_type', '=', Submitter::RECORD_TYPE)
509c5a1ffe6SGreg Roach            ->orderBy('o_id')
510c5a1ffe6SGreg Roach            ->skip($offset)
511c5a1ffe6SGreg Roach            ->take($limit)
512c5a1ffe6SGreg Roach            ->get()
5136b9cb339SGreg Roach            ->map(Registry::submitterFactory()->mapper($tree));
514c5a1ffe6SGreg Roach    }
5158c2e8227SGreg Roach}
516