xref: /webtrees/app/Module/SiteMapModule.php (revision 5bfc689774bb9a6401271c4ed15a6d50652c991b)
18c2e8227SGreg Roach<?php
23976b470SGreg Roach
38c2e8227SGreg Roach/**
48c2e8227SGreg Roach * webtrees: online genealogy
5*5bfc6897SGreg Roach * Copyright (C) 2022 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;
41b55cbc6bSGreg Roachuse Fisharebest\Webtrees\Validator;
42fa17fb66SGreg Roachuse Illuminate\Database\Capsule\Manager as DB;
43a69f5655SGreg Roachuse Illuminate\Database\Query\Expression;
44886b77daSGreg Roachuse Illuminate\Support\Collection;
456ccdf4f0SGreg Roachuse Psr\Http\Message\ResponseInterface;
466ccdf4f0SGreg Roachuse Psr\Http\Message\ServerRequestInterface;
4716a40a66SGreg Roachuse Psr\Http\Server\RequestHandlerInterface;
48d519210eSGreg Roach
4916a40a66SGreg Roachuse function app;
5016a40a66SGreg Roachuse function assert;
5116a40a66SGreg Roachuse function date;
523df1e584SGreg Roachuse function redirect;
5316a40a66SGreg Roachuse function response;
5416a40a66SGreg Roachuse function route;
553df1e584SGreg Roachuse function view;
563df1e584SGreg Roach
578c2e8227SGreg Roach/**
588c2e8227SGreg Roach * Class SiteMapModule
598c2e8227SGreg Roach */
6016a40a66SGreg Roachclass SiteMapModule extends AbstractModule implements ModuleConfigInterface, RequestHandlerInterface
61c1010edaSGreg Roach{
6249a243cbSGreg Roach    use ModuleConfigTrait;
6349a243cbSGreg Roach
6416d6367aSGreg Roach    private const RECORDS_PER_VOLUME = 500; // Keep sitemap files small, for memory, CPU and max_allowed_packet limits.
650daf451eSGreg Roach    private const CACHE_LIFE         = 209600; // Two weeks
668c2e8227SGreg Roach
67c5a1ffe6SGreg Roach    private const PRIORITY = [
68c5a1ffe6SGreg Roach        Family::RECORD_TYPE     => 0.7,
69c5a1ffe6SGreg Roach        Individual::RECORD_TYPE => 0.9,
70c5a1ffe6SGreg Roach        Media::RECORD_TYPE      => 0.5,
71c5a1ffe6SGreg Roach        Note::RECORD_TYPE       => 0.3,
72c5a1ffe6SGreg Roach        Repository::RECORD_TYPE => 0.5,
73c5a1ffe6SGreg Roach        Source::RECORD_TYPE     => 0.5,
74c5a1ffe6SGreg Roach        Submitter::RECORD_TYPE  => 0.3,
75c5a1ffe6SGreg Roach    ];
76c5a1ffe6SGreg Roach
7743f2f523SGreg Roach    private TreeService $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    {
96158900c2SGreg Roach        Registry::routeFactory()->routeMap()
9714aabe72SGreg Roach            ->get('sitemap-style', '/sitemap.xsl', $this);
9814aabe72SGreg Roach
99158900c2SGreg Roach        Registry::routeFactory()->routeMap()
10016a40a66SGreg Roach            ->get('sitemap-index', '/sitemap.xml', $this);
10116a40a66SGreg Roach
102158900c2SGreg Roach        Registry::routeFactory()->routeMap()
103c5a1ffe6SGreg Roach            ->get('sitemap-file', '/sitemap-{tree}-{type}-{page}.xml', $this);
10416a40a66SGreg Roach    }
10516a40a66SGreg Roach
10616a40a66SGreg Roach    /**
107a5f7ed67SGreg Roach     * A sentence describing what this module does.
108a5f7ed67SGreg Roach     *
109a5f7ed67SGreg Roach     * @return string
110a5f7ed67SGreg Roach     */
11149a243cbSGreg Roach    public function description(): string
112c1010edaSGreg Roach    {
113bbb76c12SGreg Roach        /* I18N: Description of the “Sitemaps” module */
114bbb76c12SGreg Roach        return I18N::translate('Generate sitemap files for search engines.');
1158c2e8227SGreg Roach    }
1168c2e8227SGreg Roach
11776692c8bSGreg Roach    /**
118abafa13cSGreg Roach     * Should this module be enabled when it is first installed?
119abafa13cSGreg Roach     *
120abafa13cSGreg Roach     * @return bool
121abafa13cSGreg Roach     */
122abafa13cSGreg Roach    public function isEnabledByDefault(): bool
123abafa13cSGreg Roach    {
124abafa13cSGreg Roach        return false;
125abafa13cSGreg Roach    }
126abafa13cSGreg Roach
127abafa13cSGreg Roach    /**
12857ab2231SGreg Roach     * @param ServerRequestInterface $request
12957ab2231SGreg Roach     *
1306ccdf4f0SGreg Roach     * @return ResponseInterface
1318c2e8227SGreg Roach     */
132a09ea7ccSGreg Roach    public function getAdminAction(/** @scrutinizer ignore-unused */ ServerRequestInterface $request): ResponseInterface
133c1010edaSGreg Roach    {
134a5f7ed67SGreg Roach        $this->layout = 'layouts/administration';
135a5f7ed67SGreg Roach
13616a40a66SGreg Roach        $sitemap_url = route('sitemap-index');
137a5f7ed67SGreg Roach
138ce42304aSGreg Roach        // This list comes from https://en.wikipedia.org/wiki/Sitemaps
139a5f7ed67SGreg Roach        $submit_urls = [
140a5f7ed67SGreg Roach            'Bing/Yahoo' => Html::url('https://www.bing.com/webmaster/ping.aspx', ['siteMap' => $sitemap_url]),
141a5f7ed67SGreg Roach            'Google'     => Html::url('https://www.google.com/webmasters/tools/ping', ['sitemap' => $sitemap_url]),
142a5f7ed67SGreg Roach        ];
143a5f7ed67SGreg Roach
144291c1b19SGreg Roach        return $this->viewResponse('modules/sitemap/config', [
1453df1e584SGreg Roach            'all_trees'   => $this->tree_service->all(),
146a5f7ed67SGreg Roach            'sitemap_url' => $sitemap_url,
147a5f7ed67SGreg Roach            'submit_urls' => $submit_urls,
14849a243cbSGreg Roach            'title'       => $this->title(),
149a5f7ed67SGreg Roach        ]);
1508c2e8227SGreg Roach    }
1518c2e8227SGreg Roach
1528c2e8227SGreg Roach    /**
1536ccdf4f0SGreg Roach     * How should this module be identified in the control panel, etc.?
154a5f7ed67SGreg Roach     *
1556ccdf4f0SGreg Roach     * @return string
1568c2e8227SGreg Roach     */
1576ccdf4f0SGreg Roach    public function title(): string
1586ccdf4f0SGreg Roach    {
159ad3143ccSGreg Roach        /* I18N: Name of a module - see https://en.wikipedia.org/wiki/Sitemaps */
1606ccdf4f0SGreg Roach        return I18N::translate('Sitemaps');
1616ccdf4f0SGreg Roach    }
1626ccdf4f0SGreg Roach
1636ccdf4f0SGreg Roach    /**
1646ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
1656ccdf4f0SGreg Roach     *
1666ccdf4f0SGreg Roach     * @return ResponseInterface
1676ccdf4f0SGreg Roach     */
1686ccdf4f0SGreg Roach    public function postAdminAction(ServerRequestInterface $request): ResponseInterface
169c1010edaSGreg Roach    {
170b46c87bdSGreg Roach        $params = (array) $request->getParsedBody();
171b6b9dcc9SGreg Roach
1723df1e584SGreg Roach        foreach ($this->tree_service->all() as $tree) {
173b6b9dcc9SGreg Roach            $include_in_sitemap = (bool) ($params['sitemap' . $tree->id()] ?? false);
174a5f7ed67SGreg Roach            $tree->setPreference('include_in_sitemap', (string) $include_in_sitemap);
1758c2e8227SGreg Roach        }
176a5f7ed67SGreg Roach
17749a243cbSGreg Roach        FlashMessages::addMessage(I18N::translate('The preferences for the module “%s” have been updated.', $this->title()), 'success');
178a5f7ed67SGreg Roach
1796ccdf4f0SGreg Roach        return redirect($this->getConfigLink());
1808c2e8227SGreg Roach    }
1818c2e8227SGreg Roach
1828c2e8227SGreg Roach    /**
18357ab2231SGreg Roach     * @param ServerRequestInterface $request
18457ab2231SGreg Roach     *
1856ccdf4f0SGreg Roach     * @return ResponseInterface
1868c2e8227SGreg Roach     */
18716a40a66SGreg Roach    public function handle(ServerRequestInterface $request): ResponseInterface
188c1010edaSGreg Roach    {
189b55cbc6bSGreg Roach        $route = Validator::attributes($request)->route();
190a5f7ed67SGreg Roach
19114aabe72SGreg Roach        if ($route->name === 'sitemap-style') {
19214aabe72SGreg Roach            $content = view('modules/sitemap/sitemap-xsl');
19314aabe72SGreg Roach
19414aabe72SGreg Roach            return response($content, StatusCodeInterface::STATUS_OK, [
19514aabe72SGreg Roach                'Content-Type' => 'application/xml',
19614aabe72SGreg Roach            ]);
19714aabe72SGreg Roach        }
19814aabe72SGreg Roach
19916a40a66SGreg Roach        if ($route->name === 'sitemap-index') {
20016a40a66SGreg Roach            return $this->siteMapIndex($request);
20116a40a66SGreg Roach        }
20216a40a66SGreg Roach
20316a40a66SGreg Roach        return $this->siteMapFile($request);
20416a40a66SGreg Roach    }
20516a40a66SGreg Roach
20616a40a66SGreg Roach    /**
20716a40a66SGreg Roach     * @param ServerRequestInterface $request
20816a40a66SGreg Roach     *
20916a40a66SGreg Roach     * @return ResponseInterface
21016a40a66SGreg Roach     */
211a09ea7ccSGreg Roach    private function siteMapIndex(/** @scrutinizer ignore-unused */ ServerRequestInterface $request): ResponseInterface
21216a40a66SGreg Roach    {
2136b9cb339SGreg Roach        $content = Registry::cache()->file()->remember('sitemap.xml', function (): string {
214659aa375SGreg Roach            // Which trees have sitemaps enabled?
215659aa375SGreg Roach            $tree_ids = $this->tree_service->all()->filter(static function (Tree $tree): bool {
216659aa375SGreg Roach                return $tree->getPreference('include_in_sitemap') === '1';
217659aa375SGreg Roach            })->map(static function (Tree $tree): int {
218659aa375SGreg Roach                return $tree->id();
219659aa375SGreg Roach            });
220659aa375SGreg Roach
221c5a1ffe6SGreg Roach            $count_families = DB::table('families')
222c5a1ffe6SGreg Roach                ->join('gedcom', 'f_file', '=', 'gedcom_id')
223c5a1ffe6SGreg Roach                ->whereIn('gedcom_id', $tree_ids)
224c5a1ffe6SGreg Roach                ->groupBy(['gedcom_id'])
225c5a1ffe6SGreg Roach                ->select([new Expression('COUNT(*) AS total'), 'gedcom_name'])
226c5a1ffe6SGreg Roach                ->pluck('total', 'gedcom_name');
227c5a1ffe6SGreg Roach
228fa17fb66SGreg Roach            $count_individuals = DB::table('individuals')
22916a40a66SGreg Roach                ->join('gedcom', 'i_file', '=', 'gedcom_id')
23016a40a66SGreg Roach                ->whereIn('gedcom_id', $tree_ids)
23116a40a66SGreg Roach                ->groupBy(['gedcom_id'])
23216a40a66SGreg Roach                ->select([new Expression('COUNT(*) AS total'), 'gedcom_name'])
23316a40a66SGreg Roach                ->pluck('total', 'gedcom_name');
234a5f7ed67SGreg Roach
235fa17fb66SGreg Roach            $count_media = DB::table('media')
23616a40a66SGreg Roach                ->join('gedcom', 'm_file', '=', 'gedcom_id')
23716a40a66SGreg Roach                ->whereIn('gedcom_id', $tree_ids)
23816a40a66SGreg Roach                ->groupBy(['gedcom_id'])
23916a40a66SGreg Roach                ->select([new Expression('COUNT(*) AS total'), 'gedcom_name'])
24016a40a66SGreg Roach                ->pluck('total', 'gedcom_name');
241a5f7ed67SGreg Roach
242fa17fb66SGreg Roach            $count_notes = DB::table('other')
24316a40a66SGreg Roach                ->join('gedcom', 'o_file', '=', 'gedcom_id')
24416a40a66SGreg Roach                ->whereIn('gedcom_id', $tree_ids)
245c5a1ffe6SGreg Roach                ->where('o_type', '=', Note::RECORD_TYPE)
24616a40a66SGreg Roach                ->groupBy(['gedcom_id'])
24716a40a66SGreg Roach                ->select([new Expression('COUNT(*) AS total'), 'gedcom_name'])
24816a40a66SGreg Roach                ->pluck('total', 'gedcom_name');
249a5f7ed67SGreg Roach
250fa17fb66SGreg Roach            $count_repositories = DB::table('other')
25116a40a66SGreg Roach                ->join('gedcom', 'o_file', '=', 'gedcom_id')
25216a40a66SGreg Roach                ->whereIn('gedcom_id', $tree_ids)
253c5a1ffe6SGreg Roach                ->where('o_type', '=', Repository::RECORD_TYPE)
25416a40a66SGreg Roach                ->groupBy(['gedcom_id'])
25516a40a66SGreg Roach                ->select([new Expression('COUNT(*) AS total'), 'gedcom_name'])
25616a40a66SGreg Roach                ->pluck('total', 'gedcom_name');
257a5f7ed67SGreg Roach
258fa17fb66SGreg Roach            $count_sources = DB::table('sources')
25916a40a66SGreg Roach                ->join('gedcom', 's_file', '=', 'gedcom_id')
26016a40a66SGreg Roach                ->whereIn('gedcom_id', $tree_ids)
26116a40a66SGreg Roach                ->groupBy(['gedcom_id'])
26216a40a66SGreg Roach                ->select([new Expression('COUNT(*) AS total'), 'gedcom_name'])
26316a40a66SGreg Roach                ->pluck('total', 'gedcom_name');
264a5f7ed67SGreg Roach
265c5a1ffe6SGreg Roach            $count_submitters = DB::table('other')
266c5a1ffe6SGreg Roach                ->join('gedcom', 'o_file', '=', 'gedcom_id')
267c5a1ffe6SGreg Roach                ->whereIn('gedcom_id', $tree_ids)
268c5a1ffe6SGreg Roach                ->where('o_type', '=', Submitter::RECORD_TYPE)
269c5a1ffe6SGreg Roach                ->groupBy(['gedcom_id'])
270c5a1ffe6SGreg Roach                ->select([new Expression('COUNT(*) AS total'), 'gedcom_name'])
271c5a1ffe6SGreg Roach                ->pluck('total', 'gedcom_name');
272c5a1ffe6SGreg Roach
27316a40a66SGreg Roach            // Versions 2.0.1 and earlier of this module stored large amounts of data in the settings.
27416a40a66SGreg Roach            DB::table('module_setting')
27516a40a66SGreg Roach                ->where('module_name', '=', $this->name())
27616a40a66SGreg Roach                ->delete();
27716a40a66SGreg Roach
27814aabe72SGreg Roach            return view('modules/sitemap/sitemap-index-xml', [
2793df1e584SGreg Roach                'all_trees'          => $this->tree_service->all(),
280c5a1ffe6SGreg Roach                'count_families'     => $count_families,
281a5f7ed67SGreg Roach                'count_individuals'  => $count_individuals,
282a5f7ed67SGreg Roach                'count_media'        => $count_media,
283a5f7ed67SGreg Roach                'count_notes'        => $count_notes,
284a5f7ed67SGreg Roach                'count_repositories' => $count_repositories,
285a5f7ed67SGreg Roach                'count_sources'      => $count_sources,
286c5a1ffe6SGreg Roach                'count_submitters'   => $count_submitters,
287a5f7ed67SGreg Roach                'last_mod'           => date('Y-m-d'),
288a5f7ed67SGreg Roach                'records_per_volume' => self::RECORDS_PER_VOLUME,
28914aabe72SGreg Roach                'sitemap_xsl'        => route('sitemap-style'),
290a5f7ed67SGreg Roach            ]);
29116a40a66SGreg Roach        }, self::CACHE_LIFE);
292a5f7ed67SGreg Roach
2936ccdf4f0SGreg Roach        return response($content, StatusCodeInterface::STATUS_OK, [
294a5f7ed67SGreg Roach            'Content-Type' => 'application/xml',
295a5f7ed67SGreg Roach        ]);
296a5f7ed67SGreg Roach    }
297a5f7ed67SGreg Roach
298a5f7ed67SGreg Roach    /**
2996ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
300a5f7ed67SGreg Roach     *
3016ccdf4f0SGreg Roach     * @return ResponseInterface
302a5f7ed67SGreg Roach     */
30316a40a66SGreg Roach    private function siteMapFile(ServerRequestInterface $request): ResponseInterface
304c1010edaSGreg Roach    {
305b55cbc6bSGreg Roach        $tree = Validator::attributes($request)->tree('tree');
306b55cbc6bSGreg Roach        $type = Validator::attributes($request)->string('type');
307b55cbc6bSGreg Roach        $page = Validator::attributes($request)->integer('page');
308a5f7ed67SGreg Roach
309659aa375SGreg Roach        if ($tree->getPreference('include_in_sitemap') !== '1') {
310659aa375SGreg Roach            throw new HttpNotFoundException();
311659aa375SGreg Roach        }
312659aa375SGreg Roach
313c5a1ffe6SGreg Roach        $cache_key = 'sitemap/' . $tree->id() . '/' . $type . '/' . $page . '.xml';
31416a40a66SGreg Roach
3156b9cb339SGreg Roach        $content = Registry::cache()->file()->remember($cache_key, function () use ($tree, $type, $page): string {
316c5a1ffe6SGreg Roach            $records = $this->sitemapRecords($tree, $type, self::RECORDS_PER_VOLUME, self::RECORDS_PER_VOLUME * $page);
31716a40a66SGreg Roach
31814aabe72SGreg Roach            return view('modules/sitemap/sitemap-file-xml', [
319c5a1ffe6SGreg Roach                'priority'    => self::PRIORITY[$type],
3209e3c7a99SGreg Roach                'records'     => $records,
32114aabe72SGreg Roach                'sitemap_xsl' => route('sitemap-style'),
3229e3c7a99SGreg Roach                'tree'        => $tree,
3239e3c7a99SGreg Roach            ]);
32416a40a66SGreg Roach        }, self::CACHE_LIFE);
325a5f7ed67SGreg Roach
3266ccdf4f0SGreg Roach        return response($content, StatusCodeInterface::STATUS_OK, [
327a5f7ed67SGreg Roach            'Content-Type' => 'application/xml',
328a5f7ed67SGreg Roach        ]);
329a5f7ed67SGreg Roach    }
330a5f7ed67SGreg Roach
331a5f7ed67SGreg Roach    /**
332a5f7ed67SGreg Roach     * @param Tree   $tree
333a5f7ed67SGreg Roach     * @param string $type
334a5f7ed67SGreg Roach     * @param int    $limit
335a5f7ed67SGreg Roach     * @param int    $offset
336a5f7ed67SGreg Roach     *
33736779af1SGreg Roach     * @return Collection<int,GedcomRecord>
338a5f7ed67SGreg Roach     */
339886b77daSGreg Roach    private function sitemapRecords(Tree $tree, string $type, int $limit, int $offset): Collection
340c1010edaSGreg Roach    {
341a5f7ed67SGreg Roach        switch ($type) {
342c5a1ffe6SGreg Roach            case Family::RECORD_TYPE:
343c5a1ffe6SGreg Roach                $records = $this->sitemapFamilies($tree, $limit, $offset);
344c5a1ffe6SGreg Roach                break;
345c5a1ffe6SGreg Roach
34616a40a66SGreg Roach            case Individual::RECORD_TYPE:
347a5f7ed67SGreg Roach                $records = $this->sitemapIndividuals($tree, $limit, $offset);
348a5f7ed67SGreg Roach                break;
349a5f7ed67SGreg Roach
35016a40a66SGreg Roach            case Media::RECORD_TYPE:
351a5f7ed67SGreg Roach                $records = $this->sitemapMedia($tree, $limit, $offset);
352a5f7ed67SGreg Roach                break;
353a5f7ed67SGreg Roach
35416a40a66SGreg Roach            case Note::RECORD_TYPE:
355a5f7ed67SGreg Roach                $records = $this->sitemapNotes($tree, $limit, $offset);
356a5f7ed67SGreg Roach                break;
357a5f7ed67SGreg Roach
35816a40a66SGreg Roach            case Repository::RECORD_TYPE:
359a5f7ed67SGreg Roach                $records = $this->sitemapRepositories($tree, $limit, $offset);
360a5f7ed67SGreg Roach                break;
361a5f7ed67SGreg Roach
36216a40a66SGreg Roach            case Source::RECORD_TYPE:
363a5f7ed67SGreg Roach                $records = $this->sitemapSources($tree, $limit, $offset);
364a5f7ed67SGreg Roach                break;
365a5f7ed67SGreg Roach
366c5a1ffe6SGreg Roach            case Submitter::RECORD_TYPE:
367c5a1ffe6SGreg Roach                $records = $this->sitemapSubmitters($tree, $limit, $offset);
368c5a1ffe6SGreg Roach                break;
369c5a1ffe6SGreg Roach
370a5f7ed67SGreg Roach            default:
371d501c45dSGreg Roach                throw new HttpNotFoundException('Invalid record type: ' . $type);
372a5f7ed67SGreg Roach        }
373a5f7ed67SGreg Roach
374a5f7ed67SGreg Roach        // Skip private records.
375659aa375SGreg Roach        $records = $records->filter(static function (GedcomRecord $record): bool {
376659aa375SGreg Roach            return $record->canShow(Auth::PRIV_PRIVATE);
377659aa375SGreg Roach        });
378a5f7ed67SGreg Roach
379a5f7ed67SGreg Roach        return $records;
380a5f7ed67SGreg Roach    }
381a5f7ed67SGreg Roach
382a5f7ed67SGreg Roach    /**
383a5f7ed67SGreg Roach     * @param Tree $tree
384a5f7ed67SGreg Roach     * @param int  $limit
385a5f7ed67SGreg Roach     * @param int  $offset
386a5f7ed67SGreg Roach     *
38736779af1SGreg Roach     * @return Collection<int,Family>
388c5a1ffe6SGreg Roach     */
389c5a1ffe6SGreg Roach    private function sitemapFamilies(Tree $tree, int $limit, int $offset): Collection
390c5a1ffe6SGreg Roach    {
391c5a1ffe6SGreg Roach        return DB::table('families')
392c5a1ffe6SGreg Roach            ->where('f_file', '=', $tree->id())
393c5a1ffe6SGreg Roach            ->orderBy('f_id')
394c5a1ffe6SGreg Roach            ->skip($offset)
395c5a1ffe6SGreg Roach            ->take($limit)
396c5a1ffe6SGreg Roach            ->get()
3976b9cb339SGreg Roach            ->map(Registry::familyFactory()->mapper($tree));
398c5a1ffe6SGreg Roach    }
399c5a1ffe6SGreg Roach
400c5a1ffe6SGreg Roach    /**
401c5a1ffe6SGreg Roach     * @param Tree $tree
402c5a1ffe6SGreg Roach     * @param int  $limit
403c5a1ffe6SGreg Roach     * @param int  $offset
404c5a1ffe6SGreg Roach     *
40536779af1SGreg Roach     * @return Collection<int,Individual>
406a5f7ed67SGreg Roach     */
407886b77daSGreg Roach    private function sitemapIndividuals(Tree $tree, int $limit, int $offset): Collection
408c1010edaSGreg Roach    {
409886b77daSGreg Roach        return DB::table('individuals')
410fa17fb66SGreg Roach            ->where('i_file', '=', $tree->id())
411fa17fb66SGreg Roach            ->orderBy('i_id')
412fa17fb66SGreg Roach            ->skip($offset)
413fa17fb66SGreg Roach            ->take($limit)
414886b77daSGreg Roach            ->get()
4156b9cb339SGreg Roach            ->map(Registry::individualFactory()->mapper($tree));
4168c2e8227SGreg Roach    }
417a5f7ed67SGreg Roach
418a5f7ed67SGreg Roach    /**
419a5f7ed67SGreg Roach     * @param Tree $tree
420a5f7ed67SGreg Roach     * @param int  $limit
421a5f7ed67SGreg Roach     * @param int  $offset
422a5f7ed67SGreg Roach     *
42336779af1SGreg Roach     * @return Collection<int,Media>
424a5f7ed67SGreg Roach     */
425886b77daSGreg Roach    private function sitemapMedia(Tree $tree, int $limit, int $offset): Collection
426c1010edaSGreg Roach    {
427886b77daSGreg Roach        return DB::table('media')
428fa17fb66SGreg Roach            ->where('m_file', '=', $tree->id())
429fa17fb66SGreg Roach            ->orderBy('m_id')
430fa17fb66SGreg Roach            ->skip($offset)
431fa17fb66SGreg Roach            ->take($limit)
432886b77daSGreg Roach            ->get()
4336b9cb339SGreg Roach            ->map(Registry::mediaFactory()->mapper($tree));
4348c2e8227SGreg Roach    }
4358c2e8227SGreg Roach
4368c2e8227SGreg Roach    /**
437a5f7ed67SGreg Roach     * @param Tree $tree
438a5f7ed67SGreg Roach     * @param int  $limit
439a5f7ed67SGreg Roach     * @param int  $offset
440a5f7ed67SGreg Roach     *
44136779af1SGreg Roach     * @return Collection<int,Note>
4428c2e8227SGreg Roach     */
443886b77daSGreg Roach    private function sitemapNotes(Tree $tree, int $limit, int $offset): Collection
444c1010edaSGreg Roach    {
445886b77daSGreg Roach        return DB::table('other')
446fa17fb66SGreg Roach            ->where('o_file', '=', $tree->id())
447c5a1ffe6SGreg Roach            ->where('o_type', '=', Note::RECORD_TYPE)
448fa17fb66SGreg Roach            ->orderBy('o_id')
449fa17fb66SGreg Roach            ->skip($offset)
450fa17fb66SGreg Roach            ->take($limit)
451886b77daSGreg Roach            ->get()
4526b9cb339SGreg Roach            ->map(Registry::noteFactory()->mapper($tree));
4538c2e8227SGreg Roach    }
4548c2e8227SGreg Roach
455a5f7ed67SGreg Roach    /**
456a5f7ed67SGreg Roach     * @param Tree $tree
457a5f7ed67SGreg Roach     * @param int  $limit
458a5f7ed67SGreg Roach     * @param int  $offset
459a5f7ed67SGreg Roach     *
46036779af1SGreg Roach     * @return Collection<int,Repository>
461a5f7ed67SGreg Roach     */
462886b77daSGreg Roach    private function sitemapRepositories(Tree $tree, int $limit, int $offset): Collection
463c1010edaSGreg Roach    {
464886b77daSGreg Roach        return DB::table('other')
465fa17fb66SGreg Roach            ->where('o_file', '=', $tree->id())
466c5a1ffe6SGreg Roach            ->where('o_type', '=', Repository::RECORD_TYPE)
467fa17fb66SGreg Roach            ->orderBy('o_id')
468fa17fb66SGreg Roach            ->skip($offset)
469fa17fb66SGreg Roach            ->take($limit)
470886b77daSGreg Roach            ->get()
4716b9cb339SGreg Roach            ->map(Registry::repositoryFactory()->mapper($tree));
472a5f7ed67SGreg Roach    }
473a5f7ed67SGreg Roach
474a5f7ed67SGreg Roach    /**
475a5f7ed67SGreg Roach     * @param Tree $tree
476a5f7ed67SGreg Roach     * @param int  $limit
477a5f7ed67SGreg Roach     * @param int  $offset
478a5f7ed67SGreg Roach     *
47936779af1SGreg Roach     * @return Collection<int,Source>
480a5f7ed67SGreg Roach     */
481886b77daSGreg Roach    private function sitemapSources(Tree $tree, int $limit, int $offset): Collection
482c1010edaSGreg Roach    {
483886b77daSGreg Roach        return DB::table('sources')
484fa17fb66SGreg Roach            ->where('s_file', '=', $tree->id())
485fa17fb66SGreg Roach            ->orderBy('s_id')
486fa17fb66SGreg Roach            ->skip($offset)
487fa17fb66SGreg Roach            ->take($limit)
488886b77daSGreg Roach            ->get()
4896b9cb339SGreg Roach            ->map(Registry::sourceFactory()->mapper($tree));
4908c2e8227SGreg Roach    }
491c5a1ffe6SGreg Roach
492c5a1ffe6SGreg Roach    /**
493c5a1ffe6SGreg Roach     * @param Tree $tree
494c5a1ffe6SGreg Roach     * @param int  $limit
495c5a1ffe6SGreg Roach     * @param int  $offset
496c5a1ffe6SGreg Roach     *
49736779af1SGreg Roach     * @return Collection<int,Submitter>
498c5a1ffe6SGreg Roach     */
499c5a1ffe6SGreg Roach    private function sitemapSubmitters(Tree $tree, int $limit, int $offset): Collection
500c5a1ffe6SGreg Roach    {
501c5a1ffe6SGreg Roach        return DB::table('other')
502c5a1ffe6SGreg Roach            ->where('o_file', '=', $tree->id())
503c5a1ffe6SGreg Roach            ->where('o_type', '=', Submitter::RECORD_TYPE)
504c5a1ffe6SGreg Roach            ->orderBy('o_id')
505c5a1ffe6SGreg Roach            ->skip($offset)
506c5a1ffe6SGreg Roach            ->take($limit)
507c5a1ffe6SGreg Roach            ->get()
5086b9cb339SGreg Roach            ->map(Registry::submitterFactory()->mapper($tree));
509c5a1ffe6SGreg Roach    }
5108c2e8227SGreg Roach}
511