xref: /webtrees/app/Module/SiteMapModule.php (revision 89f7189b61a494347591c99bdb92afb7d8b66e1b)
18c2e8227SGreg Roach<?php
23976b470SGreg Roach
38c2e8227SGreg Roach/**
48c2e8227SGreg Roach * webtrees: online genealogy
5*89f7189bSGreg 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
15*89f7189bSGreg 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;
26d501c45dSGreg Roachuse Fisharebest\Webtrees\Exceptions\HttpNotFoundException;
276b9cb339SGreg Roachuse Fisharebest\Webtrees\Registry;
28c5a1ffe6SGreg Roachuse Fisharebest\Webtrees\Family;
29a5f7ed67SGreg Roachuse Fisharebest\Webtrees\FlashMessages;
30a5f7ed67SGreg Roachuse Fisharebest\Webtrees\GedcomRecord;
31b1b85189SGreg Roachuse Fisharebest\Webtrees\Html;
320e62c4b8SGreg Roachuse Fisharebest\Webtrees\I18N;
330e62c4b8SGreg Roachuse Fisharebest\Webtrees\Individual;
340e62c4b8SGreg Roachuse Fisharebest\Webtrees\Media;
350e62c4b8SGreg Roachuse Fisharebest\Webtrees\Note;
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
763df1e584SGreg Roach    /** @var TreeService */
773df1e584SGreg Roach    private $tree_service;
783df1e584SGreg Roach
793df1e584SGreg Roach    /**
803df1e584SGreg Roach     * TreesMenuModule constructor.
813df1e584SGreg Roach     *
823df1e584SGreg Roach     * @param TreeService $tree_service
833df1e584SGreg Roach     */
843df1e584SGreg Roach    public function __construct(TreeService $tree_service)
853df1e584SGreg Roach    {
863df1e584SGreg Roach        $this->tree_service = $tree_service;
873df1e584SGreg Roach    }
883df1e584SGreg Roach
89a5f7ed67SGreg Roach    /**
9016a40a66SGreg Roach     * Initialization.
9116a40a66SGreg Roach     *
9216a40a66SGreg Roach     * @return void
9316a40a66SGreg Roach     */
9416a40a66SGreg Roach    public function boot(): void
9516a40a66SGreg Roach    {
9616a40a66SGreg Roach        $router_container = app(RouterContainer::class);
9716a40a66SGreg Roach        assert($router_container instanceof RouterContainer);
9816a40a66SGreg Roach
9916a40a66SGreg Roach        $router_container->getMap()
10014aabe72SGreg Roach            ->get('sitemap-style', '/sitemap.xsl', $this);
10114aabe72SGreg Roach
10214aabe72SGreg Roach        $router_container->getMap()
10316a40a66SGreg Roach            ->get('sitemap-index', '/sitemap.xml', $this);
10416a40a66SGreg Roach
10516a40a66SGreg Roach        $router_container->getMap()
106c5a1ffe6SGreg Roach            ->get('sitemap-file', '/sitemap-{tree}-{type}-{page}.xml', $this);
10716a40a66SGreg Roach    }
10816a40a66SGreg Roach
10916a40a66SGreg Roach    /**
110a5f7ed67SGreg Roach     * A sentence describing what this module does.
111a5f7ed67SGreg Roach     *
112a5f7ed67SGreg Roach     * @return string
113a5f7ed67SGreg Roach     */
11449a243cbSGreg Roach    public function description(): string
115c1010edaSGreg Roach    {
116bbb76c12SGreg Roach        /* I18N: Description of the “Sitemaps” module */
117bbb76c12SGreg Roach        return I18N::translate('Generate sitemap files for search engines.');
1188c2e8227SGreg Roach    }
1198c2e8227SGreg Roach
12076692c8bSGreg Roach    /**
121abafa13cSGreg Roach     * Should this module be enabled when it is first installed?
122abafa13cSGreg Roach     *
123abafa13cSGreg Roach     * @return bool
124abafa13cSGreg Roach     */
125abafa13cSGreg Roach    public function isEnabledByDefault(): bool
126abafa13cSGreg Roach    {
127abafa13cSGreg Roach        return false;
128abafa13cSGreg Roach    }
129abafa13cSGreg Roach
130abafa13cSGreg Roach    /**
13157ab2231SGreg Roach     * @param ServerRequestInterface $request
13257ab2231SGreg Roach     *
1336ccdf4f0SGreg Roach     * @return ResponseInterface
1348c2e8227SGreg Roach     */
13557ab2231SGreg Roach    public function getAdminAction(ServerRequestInterface $request): ResponseInterface
136c1010edaSGreg Roach    {
137a5f7ed67SGreg Roach        $this->layout = 'layouts/administration';
138a5f7ed67SGreg Roach
13916a40a66SGreg Roach        $sitemap_url = route('sitemap-index');
140a5f7ed67SGreg Roach
141ce42304aSGreg Roach        // This list comes from https://en.wikipedia.org/wiki/Sitemaps
142a5f7ed67SGreg Roach        $submit_urls = [
143a5f7ed67SGreg Roach            'Bing/Yahoo' => Html::url('https://www.bing.com/webmaster/ping.aspx', ['siteMap' => $sitemap_url]),
144a5f7ed67SGreg Roach            'Google'     => Html::url('https://www.google.com/webmasters/tools/ping', ['sitemap' => $sitemap_url]),
145a5f7ed67SGreg Roach        ];
146a5f7ed67SGreg Roach
147291c1b19SGreg Roach        return $this->viewResponse('modules/sitemap/config', [
1483df1e584SGreg Roach            'all_trees'   => $this->tree_service->all(),
149a5f7ed67SGreg Roach            'sitemap_url' => $sitemap_url,
150a5f7ed67SGreg Roach            'submit_urls' => $submit_urls,
15149a243cbSGreg Roach            'title'       => $this->title(),
152a5f7ed67SGreg Roach        ]);
1538c2e8227SGreg Roach    }
1548c2e8227SGreg Roach
1558c2e8227SGreg Roach    /**
1566ccdf4f0SGreg Roach     * How should this module be identified in the control panel, etc.?
157a5f7ed67SGreg Roach     *
1586ccdf4f0SGreg Roach     * @return string
1598c2e8227SGreg Roach     */
1606ccdf4f0SGreg Roach    public function title(): string
1616ccdf4f0SGreg Roach    {
1626ccdf4f0SGreg Roach        /* I18N: Name of a module - see http://en.wikipedia.org/wiki/Sitemaps */
1636ccdf4f0SGreg Roach        return I18N::translate('Sitemaps');
1646ccdf4f0SGreg Roach    }
1656ccdf4f0SGreg Roach
1666ccdf4f0SGreg Roach    /**
1676ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
1686ccdf4f0SGreg Roach     *
1696ccdf4f0SGreg Roach     * @return ResponseInterface
1706ccdf4f0SGreg Roach     */
1716ccdf4f0SGreg Roach    public function postAdminAction(ServerRequestInterface $request): ResponseInterface
172c1010edaSGreg Roach    {
173b46c87bdSGreg Roach        $params = (array) $request->getParsedBody();
174b6b9dcc9SGreg Roach
1753df1e584SGreg Roach        foreach ($this->tree_service->all() as $tree) {
176b6b9dcc9SGreg Roach            $include_in_sitemap = (bool) ($params['sitemap' . $tree->id()] ?? false);
177a5f7ed67SGreg Roach            $tree->setPreference('include_in_sitemap', (string) $include_in_sitemap);
1788c2e8227SGreg Roach        }
179a5f7ed67SGreg Roach
18049a243cbSGreg Roach        FlashMessages::addMessage(I18N::translate('The preferences for the module “%s” have been updated.', $this->title()), 'success');
181a5f7ed67SGreg Roach
1826ccdf4f0SGreg Roach        return redirect($this->getConfigLink());
1838c2e8227SGreg Roach    }
1848c2e8227SGreg Roach
1858c2e8227SGreg Roach    /**
18657ab2231SGreg Roach     * @param ServerRequestInterface $request
18757ab2231SGreg Roach     *
1886ccdf4f0SGreg Roach     * @return ResponseInterface
1898c2e8227SGreg Roach     */
19016a40a66SGreg Roach    public function handle(ServerRequestInterface $request): ResponseInterface
191c1010edaSGreg Roach    {
19216a40a66SGreg Roach        $route = $request->getAttribute('route');
19316a40a66SGreg Roach        assert($route instanceof Route);
194a5f7ed67SGreg Roach
19514aabe72SGreg Roach        if ($route->name === 'sitemap-style') {
19614aabe72SGreg Roach            $content = view('modules/sitemap/sitemap-xsl');
19714aabe72SGreg Roach
19814aabe72SGreg Roach            return response($content, StatusCodeInterface::STATUS_OK, [
19914aabe72SGreg Roach                'Content-Type' => 'application/xml',
20014aabe72SGreg Roach            ]);
20114aabe72SGreg Roach        }
20214aabe72SGreg Roach
20316a40a66SGreg Roach        if ($route->name === 'sitemap-index') {
20416a40a66SGreg Roach            return $this->siteMapIndex($request);
20516a40a66SGreg Roach        }
20616a40a66SGreg Roach
20716a40a66SGreg Roach        return $this->siteMapFile($request);
20816a40a66SGreg Roach    }
20916a40a66SGreg Roach
21016a40a66SGreg Roach    /**
21116a40a66SGreg Roach     * @param ServerRequestInterface $request
21216a40a66SGreg Roach     *
21316a40a66SGreg Roach     * @return ResponseInterface
21416a40a66SGreg Roach     */
21516a40a66SGreg Roach    private function siteMapIndex(ServerRequestInterface $request): ResponseInterface
21616a40a66SGreg Roach    {
2176b9cb339SGreg Roach        $content = Registry::cache()->file()->remember('sitemap.xml', function (): string {
218659aa375SGreg Roach            // Which trees have sitemaps enabled?
219659aa375SGreg Roach            $tree_ids = $this->tree_service->all()->filter(static function (Tree $tree): bool {
220659aa375SGreg Roach                return $tree->getPreference('include_in_sitemap') === '1';
221659aa375SGreg Roach            })->map(static function (Tree $tree): int {
222659aa375SGreg Roach                return $tree->id();
223659aa375SGreg Roach            });
224659aa375SGreg Roach
225c5a1ffe6SGreg Roach            $count_families = DB::table('families')
226c5a1ffe6SGreg Roach                ->join('gedcom', 'f_file', '=', 'gedcom_id')
227c5a1ffe6SGreg Roach                ->whereIn('gedcom_id', $tree_ids)
228c5a1ffe6SGreg Roach                ->groupBy(['gedcom_id'])
229c5a1ffe6SGreg Roach                ->select([new Expression('COUNT(*) AS total'), 'gedcom_name'])
230c5a1ffe6SGreg Roach                ->pluck('total', 'gedcom_name');
231c5a1ffe6SGreg Roach
232fa17fb66SGreg Roach            $count_individuals = DB::table('individuals')
23316a40a66SGreg Roach                ->join('gedcom', 'i_file', '=', 'gedcom_id')
23416a40a66SGreg Roach                ->whereIn('gedcom_id', $tree_ids)
23516a40a66SGreg Roach                ->groupBy(['gedcom_id'])
23616a40a66SGreg Roach                ->select([new Expression('COUNT(*) AS total'), 'gedcom_name'])
23716a40a66SGreg Roach                ->pluck('total', 'gedcom_name');
238a5f7ed67SGreg Roach
239fa17fb66SGreg Roach            $count_media = DB::table('media')
24016a40a66SGreg Roach                ->join('gedcom', 'm_file', '=', 'gedcom_id')
24116a40a66SGreg Roach                ->whereIn('gedcom_id', $tree_ids)
24216a40a66SGreg Roach                ->groupBy(['gedcom_id'])
24316a40a66SGreg Roach                ->select([new Expression('COUNT(*) AS total'), 'gedcom_name'])
24416a40a66SGreg Roach                ->pluck('total', 'gedcom_name');
245a5f7ed67SGreg Roach
246fa17fb66SGreg Roach            $count_notes = DB::table('other')
24716a40a66SGreg Roach                ->join('gedcom', 'o_file', '=', 'gedcom_id')
24816a40a66SGreg Roach                ->whereIn('gedcom_id', $tree_ids)
249c5a1ffe6SGreg Roach                ->where('o_type', '=', Note::RECORD_TYPE)
25016a40a66SGreg Roach                ->groupBy(['gedcom_id'])
25116a40a66SGreg Roach                ->select([new Expression('COUNT(*) AS total'), 'gedcom_name'])
25216a40a66SGreg Roach                ->pluck('total', 'gedcom_name');
253a5f7ed67SGreg Roach
254fa17fb66SGreg Roach            $count_repositories = DB::table('other')
25516a40a66SGreg Roach                ->join('gedcom', 'o_file', '=', 'gedcom_id')
25616a40a66SGreg Roach                ->whereIn('gedcom_id', $tree_ids)
257c5a1ffe6SGreg Roach                ->where('o_type', '=', Repository::RECORD_TYPE)
25816a40a66SGreg Roach                ->groupBy(['gedcom_id'])
25916a40a66SGreg Roach                ->select([new Expression('COUNT(*) AS total'), 'gedcom_name'])
26016a40a66SGreg Roach                ->pluck('total', 'gedcom_name');
261a5f7ed67SGreg Roach
262fa17fb66SGreg Roach            $count_sources = DB::table('sources')
26316a40a66SGreg Roach                ->join('gedcom', 's_file', '=', 'gedcom_id')
26416a40a66SGreg Roach                ->whereIn('gedcom_id', $tree_ids)
26516a40a66SGreg Roach                ->groupBy(['gedcom_id'])
26616a40a66SGreg Roach                ->select([new Expression('COUNT(*) AS total'), 'gedcom_name'])
26716a40a66SGreg Roach                ->pluck('total', 'gedcom_name');
268a5f7ed67SGreg Roach
269c5a1ffe6SGreg Roach            $count_submitters = DB::table('other')
270c5a1ffe6SGreg Roach                ->join('gedcom', 'o_file', '=', 'gedcom_id')
271c5a1ffe6SGreg Roach                ->whereIn('gedcom_id', $tree_ids)
272c5a1ffe6SGreg Roach                ->where('o_type', '=', Submitter::RECORD_TYPE)
273c5a1ffe6SGreg Roach                ->groupBy(['gedcom_id'])
274c5a1ffe6SGreg Roach                ->select([new Expression('COUNT(*) AS total'), 'gedcom_name'])
275c5a1ffe6SGreg Roach                ->pluck('total', 'gedcom_name');
276c5a1ffe6SGreg Roach
27716a40a66SGreg Roach            // Versions 2.0.1 and earlier of this module stored large amounts of data in the settings.
27816a40a66SGreg Roach            DB::table('module_setting')
27916a40a66SGreg Roach                ->where('module_name', '=', $this->name())
28016a40a66SGreg Roach                ->delete();
28116a40a66SGreg Roach
28214aabe72SGreg Roach            return view('modules/sitemap/sitemap-index-xml', [
2833df1e584SGreg Roach                'all_trees'          => $this->tree_service->all(),
284c5a1ffe6SGreg Roach                'count_families'     => $count_families,
285a5f7ed67SGreg Roach                'count_individuals'  => $count_individuals,
286a5f7ed67SGreg Roach                'count_media'        => $count_media,
287a5f7ed67SGreg Roach                'count_notes'        => $count_notes,
288a5f7ed67SGreg Roach                'count_repositories' => $count_repositories,
289a5f7ed67SGreg Roach                'count_sources'      => $count_sources,
290c5a1ffe6SGreg Roach                'count_submitters'   => $count_submitters,
291a5f7ed67SGreg Roach                'last_mod'           => date('Y-m-d'),
292a5f7ed67SGreg Roach                'records_per_volume' => self::RECORDS_PER_VOLUME,
29314aabe72SGreg Roach                'sitemap_xsl'        => route('sitemap-style'),
294a5f7ed67SGreg Roach            ]);
29516a40a66SGreg Roach        }, self::CACHE_LIFE);
296a5f7ed67SGreg Roach
2976ccdf4f0SGreg Roach        return response($content, StatusCodeInterface::STATUS_OK, [
298a5f7ed67SGreg Roach            'Content-Type' => 'application/xml',
299a5f7ed67SGreg Roach        ]);
300a5f7ed67SGreg Roach    }
301a5f7ed67SGreg Roach
302a5f7ed67SGreg Roach    /**
3036ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
304a5f7ed67SGreg Roach     *
3056ccdf4f0SGreg Roach     * @return ResponseInterface
306a5f7ed67SGreg Roach     */
30716a40a66SGreg Roach    private function siteMapFile(ServerRequestInterface $request): ResponseInterface
308c1010edaSGreg Roach    {
30916a40a66SGreg Roach        $tree = $request->getAttribute('tree');
31016a40a66SGreg Roach        assert($tree instanceof Tree);
311a5f7ed67SGreg Roach
312c5a1ffe6SGreg Roach        $type = $request->getAttribute('type');
313c5a1ffe6SGreg Roach        $page = (int) $request->getAttribute('page');
314a5f7ed67SGreg Roach
315659aa375SGreg Roach        if ($tree->getPreference('include_in_sitemap') !== '1') {
316659aa375SGreg Roach            throw new HttpNotFoundException();
317659aa375SGreg Roach        }
318659aa375SGreg Roach
319c5a1ffe6SGreg Roach        $cache_key = 'sitemap/' . $tree->id() . '/' . $type . '/' . $page . '.xml';
32016a40a66SGreg Roach
3216b9cb339SGreg Roach        $content = Registry::cache()->file()->remember($cache_key, function () use ($tree, $type, $page): string {
322c5a1ffe6SGreg Roach            $records = $this->sitemapRecords($tree, $type, self::RECORDS_PER_VOLUME, self::RECORDS_PER_VOLUME * $page);
32316a40a66SGreg Roach
32414aabe72SGreg Roach            return view('modules/sitemap/sitemap-file-xml', [
325c5a1ffe6SGreg Roach                'priority'    => self::PRIORITY[$type],
3269e3c7a99SGreg Roach                'records'     => $records,
32714aabe72SGreg Roach                'sitemap_xsl' => route('sitemap-style'),
3289e3c7a99SGreg Roach                'tree'        => $tree,
3299e3c7a99SGreg Roach            ]);
33016a40a66SGreg Roach        }, self::CACHE_LIFE);
331a5f7ed67SGreg Roach
3326ccdf4f0SGreg Roach        return response($content, StatusCodeInterface::STATUS_OK, [
333a5f7ed67SGreg Roach            'Content-Type' => 'application/xml',
334a5f7ed67SGreg Roach        ]);
335a5f7ed67SGreg Roach    }
336a5f7ed67SGreg Roach
337a5f7ed67SGreg Roach    /**
338a5f7ed67SGreg Roach     * @param Tree   $tree
339a5f7ed67SGreg Roach     * @param string $type
340a5f7ed67SGreg Roach     * @param int    $limit
341a5f7ed67SGreg Roach     * @param int    $offset
342a5f7ed67SGreg Roach     *
343b5c8fd7eSGreg Roach     * @return Collection<GedcomRecord>
344a5f7ed67SGreg Roach     */
345886b77daSGreg Roach    private function sitemapRecords(Tree $tree, string $type, int $limit, int $offset): Collection
346c1010edaSGreg Roach    {
347a5f7ed67SGreg Roach        switch ($type) {
348c5a1ffe6SGreg Roach            case Family::RECORD_TYPE:
349c5a1ffe6SGreg Roach                $records = $this->sitemapFamilies($tree, $limit, $offset);
350c5a1ffe6SGreg Roach                break;
351c5a1ffe6SGreg Roach
35216a40a66SGreg Roach            case Individual::RECORD_TYPE:
353a5f7ed67SGreg Roach                $records = $this->sitemapIndividuals($tree, $limit, $offset);
354a5f7ed67SGreg Roach                break;
355a5f7ed67SGreg Roach
35616a40a66SGreg Roach            case Media::RECORD_TYPE:
357a5f7ed67SGreg Roach                $records = $this->sitemapMedia($tree, $limit, $offset);
358a5f7ed67SGreg Roach                break;
359a5f7ed67SGreg Roach
36016a40a66SGreg Roach            case Note::RECORD_TYPE:
361a5f7ed67SGreg Roach                $records = $this->sitemapNotes($tree, $limit, $offset);
362a5f7ed67SGreg Roach                break;
363a5f7ed67SGreg Roach
36416a40a66SGreg Roach            case Repository::RECORD_TYPE:
365a5f7ed67SGreg Roach                $records = $this->sitemapRepositories($tree, $limit, $offset);
366a5f7ed67SGreg Roach                break;
367a5f7ed67SGreg Roach
36816a40a66SGreg Roach            case Source::RECORD_TYPE:
369a5f7ed67SGreg Roach                $records = $this->sitemapSources($tree, $limit, $offset);
370a5f7ed67SGreg Roach                break;
371a5f7ed67SGreg Roach
372c5a1ffe6SGreg Roach            case Submitter::RECORD_TYPE:
373c5a1ffe6SGreg Roach                $records = $this->sitemapSubmitters($tree, $limit, $offset);
374c5a1ffe6SGreg Roach                break;
375c5a1ffe6SGreg Roach
376a5f7ed67SGreg Roach            default:
377d501c45dSGreg Roach                throw new HttpNotFoundException('Invalid record type: ' . $type);
378a5f7ed67SGreg Roach        }
379a5f7ed67SGreg Roach
380a5f7ed67SGreg Roach        // Skip private records.
381659aa375SGreg Roach        $records = $records->filter(static function (GedcomRecord $record): bool {
382659aa375SGreg Roach            return $record->canShow(Auth::PRIV_PRIVATE);
383659aa375SGreg Roach        });
384a5f7ed67SGreg Roach
385a5f7ed67SGreg Roach        return $records;
386a5f7ed67SGreg Roach    }
387a5f7ed67SGreg Roach
388a5f7ed67SGreg Roach    /**
389a5f7ed67SGreg Roach     * @param Tree $tree
390a5f7ed67SGreg Roach     * @param int  $limit
391a5f7ed67SGreg Roach     * @param int  $offset
392a5f7ed67SGreg Roach     *
393c5a1ffe6SGreg Roach     * @return Collection<Family>
394c5a1ffe6SGreg Roach     */
395c5a1ffe6SGreg Roach    private function sitemapFamilies(Tree $tree, int $limit, int $offset): Collection
396c5a1ffe6SGreg Roach    {
397c5a1ffe6SGreg Roach        return DB::table('families')
398c5a1ffe6SGreg Roach            ->where('f_file', '=', $tree->id())
399c5a1ffe6SGreg Roach            ->orderBy('f_id')
400c5a1ffe6SGreg Roach            ->skip($offset)
401c5a1ffe6SGreg Roach            ->take($limit)
402c5a1ffe6SGreg Roach            ->get()
4036b9cb339SGreg Roach            ->map(Registry::familyFactory()->mapper($tree));
404c5a1ffe6SGreg Roach    }
405c5a1ffe6SGreg Roach
406c5a1ffe6SGreg Roach    /**
407c5a1ffe6SGreg Roach     * @param Tree $tree
408c5a1ffe6SGreg Roach     * @param int  $limit
409c5a1ffe6SGreg Roach     * @param int  $offset
410c5a1ffe6SGreg Roach     *
411b5c8fd7eSGreg Roach     * @return Collection<Individual>
412a5f7ed67SGreg Roach     */
413886b77daSGreg Roach    private function sitemapIndividuals(Tree $tree, int $limit, int $offset): Collection
414c1010edaSGreg Roach    {
415886b77daSGreg Roach        return DB::table('individuals')
416fa17fb66SGreg Roach            ->where('i_file', '=', $tree->id())
417fa17fb66SGreg Roach            ->orderBy('i_id')
418fa17fb66SGreg Roach            ->skip($offset)
419fa17fb66SGreg Roach            ->take($limit)
420886b77daSGreg Roach            ->get()
4216b9cb339SGreg Roach            ->map(Registry::individualFactory()->mapper($tree));
4228c2e8227SGreg Roach    }
423a5f7ed67SGreg Roach
424a5f7ed67SGreg Roach    /**
425a5f7ed67SGreg Roach     * @param Tree $tree
426a5f7ed67SGreg Roach     * @param int  $limit
427a5f7ed67SGreg Roach     * @param int  $offset
428a5f7ed67SGreg Roach     *
429b5c8fd7eSGreg Roach     * @return Collection<Media>
430a5f7ed67SGreg Roach     */
431886b77daSGreg Roach    private function sitemapMedia(Tree $tree, int $limit, int $offset): Collection
432c1010edaSGreg Roach    {
433886b77daSGreg Roach        return DB::table('media')
434fa17fb66SGreg Roach            ->where('m_file', '=', $tree->id())
435fa17fb66SGreg Roach            ->orderBy('m_id')
436fa17fb66SGreg Roach            ->skip($offset)
437fa17fb66SGreg Roach            ->take($limit)
438886b77daSGreg Roach            ->get()
4396b9cb339SGreg Roach            ->map(Registry::mediaFactory()->mapper($tree));
4408c2e8227SGreg Roach    }
4418c2e8227SGreg Roach
4428c2e8227SGreg Roach    /**
443a5f7ed67SGreg Roach     * @param Tree $tree
444a5f7ed67SGreg Roach     * @param int  $limit
445a5f7ed67SGreg Roach     * @param int  $offset
446a5f7ed67SGreg Roach     *
447b5c8fd7eSGreg Roach     * @return Collection<Note>
4488c2e8227SGreg Roach     */
449886b77daSGreg Roach    private function sitemapNotes(Tree $tree, int $limit, int $offset): Collection
450c1010edaSGreg Roach    {
451886b77daSGreg Roach        return DB::table('other')
452fa17fb66SGreg Roach            ->where('o_file', '=', $tree->id())
453c5a1ffe6SGreg Roach            ->where('o_type', '=', Note::RECORD_TYPE)
454fa17fb66SGreg Roach            ->orderBy('o_id')
455fa17fb66SGreg Roach            ->skip($offset)
456fa17fb66SGreg Roach            ->take($limit)
457886b77daSGreg Roach            ->get()
4586b9cb339SGreg Roach            ->map(Registry::noteFactory()->mapper($tree));
4598c2e8227SGreg Roach    }
4608c2e8227SGreg Roach
461a5f7ed67SGreg Roach    /**
462a5f7ed67SGreg Roach     * @param Tree $tree
463a5f7ed67SGreg Roach     * @param int  $limit
464a5f7ed67SGreg Roach     * @param int  $offset
465a5f7ed67SGreg Roach     *
466b5c8fd7eSGreg Roach     * @return Collection<Repository>
467a5f7ed67SGreg Roach     */
468886b77daSGreg Roach    private function sitemapRepositories(Tree $tree, int $limit, int $offset): Collection
469c1010edaSGreg Roach    {
470886b77daSGreg Roach        return DB::table('other')
471fa17fb66SGreg Roach            ->where('o_file', '=', $tree->id())
472c5a1ffe6SGreg Roach            ->where('o_type', '=', Repository::RECORD_TYPE)
473fa17fb66SGreg Roach            ->orderBy('o_id')
474fa17fb66SGreg Roach            ->skip($offset)
475fa17fb66SGreg Roach            ->take($limit)
476886b77daSGreg Roach            ->get()
4776b9cb339SGreg Roach            ->map(Registry::repositoryFactory()->mapper($tree));
478a5f7ed67SGreg Roach    }
479a5f7ed67SGreg Roach
480a5f7ed67SGreg Roach    /**
481a5f7ed67SGreg Roach     * @param Tree $tree
482a5f7ed67SGreg Roach     * @param int  $limit
483a5f7ed67SGreg Roach     * @param int  $offset
484a5f7ed67SGreg Roach     *
485b5c8fd7eSGreg Roach     * @return Collection<Source>
486a5f7ed67SGreg Roach     */
487886b77daSGreg Roach    private function sitemapSources(Tree $tree, int $limit, int $offset): Collection
488c1010edaSGreg Roach    {
489886b77daSGreg Roach        return DB::table('sources')
490fa17fb66SGreg Roach            ->where('s_file', '=', $tree->id())
491fa17fb66SGreg Roach            ->orderBy('s_id')
492fa17fb66SGreg Roach            ->skip($offset)
493fa17fb66SGreg Roach            ->take($limit)
494886b77daSGreg Roach            ->get()
4956b9cb339SGreg Roach            ->map(Registry::sourceFactory()->mapper($tree));
4968c2e8227SGreg Roach    }
497c5a1ffe6SGreg Roach
498c5a1ffe6SGreg Roach    /**
499c5a1ffe6SGreg Roach     * @param Tree $tree
500c5a1ffe6SGreg Roach     * @param int  $limit
501c5a1ffe6SGreg Roach     * @param int  $offset
502c5a1ffe6SGreg Roach     *
503c5a1ffe6SGreg Roach     * @return Collection<Submitter>
504c5a1ffe6SGreg Roach     */
505c5a1ffe6SGreg Roach    private function sitemapSubmitters(Tree $tree, int $limit, int $offset): Collection
506c5a1ffe6SGreg Roach    {
507c5a1ffe6SGreg Roach        return DB::table('other')
508c5a1ffe6SGreg Roach            ->where('o_file', '=', $tree->id())
509c5a1ffe6SGreg Roach            ->where('o_type', '=', Submitter::RECORD_TYPE)
510c5a1ffe6SGreg Roach            ->orderBy('o_id')
511c5a1ffe6SGreg Roach            ->skip($offset)
512c5a1ffe6SGreg Roach            ->take($limit)
513c5a1ffe6SGreg Roach            ->get()
5146b9cb339SGreg Roach            ->map(Registry::submitterFactory()->mapper($tree));
515c5a1ffe6SGreg Roach    }
5168c2e8227SGreg Roach}
517