xref: /webtrees/app/Module/SiteMapModule.php (revision f25fc0f929f69ab8124cf0cecde45e457db7574a)
18c2e8227SGreg Roach<?php
23976b470SGreg Roach
38c2e8227SGreg Roach/**
48c2e8227SGreg Roach * webtrees: online genealogy
5d11be702SGreg Roach * Copyright (C) 2023 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
226ccdf4f0SGreg Roachuse Fig\Http\Message\StatusCodeInterface;
23659aa375SGreg Roachuse Fisharebest\Webtrees\Auth;
246f4ec3caSGreg Roachuse Fisharebest\Webtrees\DB;
25c5a1ffe6SGreg Roachuse Fisharebest\Webtrees\Family;
26a5f7ed67SGreg Roachuse Fisharebest\Webtrees\FlashMessages;
27a5f7ed67SGreg Roachuse Fisharebest\Webtrees\GedcomRecord;
28b1b85189SGreg Roachuse Fisharebest\Webtrees\Html;
2981b729d3SGreg Roachuse Fisharebest\Webtrees\Http\Exceptions\HttpNotFoundException;
300e62c4b8SGreg Roachuse Fisharebest\Webtrees\I18N;
310e62c4b8SGreg Roachuse Fisharebest\Webtrees\Individual;
320e62c4b8SGreg Roachuse Fisharebest\Webtrees\Media;
330e62c4b8SGreg Roachuse Fisharebest\Webtrees\Note;
3481b729d3SGreg Roachuse Fisharebest\Webtrees\Registry;
350e62c4b8SGreg Roachuse Fisharebest\Webtrees\Repository;
363df1e584SGreg Roachuse Fisharebest\Webtrees\Services\TreeService;
370e62c4b8SGreg Roachuse Fisharebest\Webtrees\Source;
38c5a1ffe6SGreg Roachuse Fisharebest\Webtrees\Submitter;
390e62c4b8SGreg Roachuse Fisharebest\Webtrees\Tree;
40b55cbc6bSGreg Roachuse Fisharebest\Webtrees\Validator;
41a69f5655SGreg Roachuse Illuminate\Database\Query\Expression;
42886b77daSGreg Roachuse Illuminate\Support\Collection;
436ccdf4f0SGreg Roachuse Psr\Http\Message\ResponseInterface;
446ccdf4f0SGreg Roachuse Psr\Http\Message\ServerRequestInterface;
4516a40a66SGreg Roachuse Psr\Http\Server\RequestHandlerInterface;
46d519210eSGreg Roach
4716a40a66SGreg Roachuse function date;
483df1e584SGreg Roachuse function redirect;
4916a40a66SGreg Roachuse function response;
5016a40a66SGreg Roachuse function route;
513df1e584SGreg Roachuse function view;
523df1e584SGreg Roach
538c2e8227SGreg Roach/**
548c2e8227SGreg Roach * Class SiteMapModule
558c2e8227SGreg Roach */
5616a40a66SGreg Roachclass SiteMapModule extends AbstractModule implements ModuleConfigInterface, RequestHandlerInterface
57c1010edaSGreg Roach{
5849a243cbSGreg Roach    use ModuleConfigTrait;
5949a243cbSGreg Roach
6016d6367aSGreg Roach    private const RECORDS_PER_VOLUME = 500; // Keep sitemap files small, for memory, CPU and max_allowed_packet limits.
610daf451eSGreg Roach    private const CACHE_LIFE         = 209600; // Two weeks
628c2e8227SGreg Roach
63c5a1ffe6SGreg Roach    private const PRIORITY = [
64c5a1ffe6SGreg Roach        Family::RECORD_TYPE     => 0.7,
65c5a1ffe6SGreg Roach        Individual::RECORD_TYPE => 0.9,
66c5a1ffe6SGreg Roach        Media::RECORD_TYPE      => 0.5,
67c5a1ffe6SGreg Roach        Note::RECORD_TYPE       => 0.3,
68c5a1ffe6SGreg Roach        Repository::RECORD_TYPE => 0.5,
69c5a1ffe6SGreg Roach        Source::RECORD_TYPE     => 0.5,
70c5a1ffe6SGreg Roach        Submitter::RECORD_TYPE  => 0.3,
71c5a1ffe6SGreg Roach    ];
72c5a1ffe6SGreg Roach
7343f2f523SGreg Roach    private TreeService $tree_service;
743df1e584SGreg Roach
753df1e584SGreg Roach    /**
763df1e584SGreg Roach     * @param TreeService $tree_service
773df1e584SGreg Roach     */
783df1e584SGreg Roach    public function __construct(TreeService $tree_service)
793df1e584SGreg Roach    {
803df1e584SGreg Roach        $this->tree_service = $tree_service;
813df1e584SGreg Roach    }
823df1e584SGreg Roach
83a5f7ed67SGreg Roach    /**
8416a40a66SGreg Roach     * Initialization.
8516a40a66SGreg Roach     *
8616a40a66SGreg Roach     * @return void
8716a40a66SGreg Roach     */
8816a40a66SGreg Roach    public function boot(): void
8916a40a66SGreg Roach    {
90158900c2SGreg Roach        Registry::routeFactory()->routeMap()
9114aabe72SGreg Roach            ->get('sitemap-style', '/sitemap.xsl', $this);
9214aabe72SGreg Roach
93158900c2SGreg Roach        Registry::routeFactory()->routeMap()
9416a40a66SGreg Roach            ->get('sitemap-index', '/sitemap.xml', $this);
9516a40a66SGreg Roach
96158900c2SGreg Roach        Registry::routeFactory()->routeMap()
97c5a1ffe6SGreg Roach            ->get('sitemap-file', '/sitemap-{tree}-{type}-{page}.xml', $this);
9816a40a66SGreg Roach    }
9916a40a66SGreg Roach
10016a40a66SGreg Roach    /**
101a5f7ed67SGreg Roach     * A sentence describing what this module does.
102a5f7ed67SGreg Roach     *
103a5f7ed67SGreg Roach     * @return string
104a5f7ed67SGreg Roach     */
10549a243cbSGreg Roach    public function description(): string
106c1010edaSGreg Roach    {
107bbb76c12SGreg Roach        /* I18N: Description of the “Sitemaps” module */
108bbb76c12SGreg Roach        return I18N::translate('Generate sitemap files for search engines.');
1098c2e8227SGreg Roach    }
1108c2e8227SGreg Roach
11176692c8bSGreg Roach    /**
112abafa13cSGreg Roach     * Should this module be enabled when it is first installed?
113abafa13cSGreg Roach     *
114abafa13cSGreg Roach     * @return bool
115abafa13cSGreg Roach     */
116abafa13cSGreg Roach    public function isEnabledByDefault(): bool
117abafa13cSGreg Roach    {
118abafa13cSGreg Roach        return false;
119abafa13cSGreg Roach    }
120abafa13cSGreg Roach
121abafa13cSGreg Roach    /**
12257ab2231SGreg Roach     * @param ServerRequestInterface $request
12357ab2231SGreg Roach     *
1246ccdf4f0SGreg Roach     * @return ResponseInterface
1258c2e8227SGreg Roach     */
12649528f2bSGreg Roach    public function getAdminAction(ServerRequestInterface $request): ResponseInterface
127c1010edaSGreg Roach    {
128a5f7ed67SGreg Roach        $this->layout = 'layouts/administration';
129a5f7ed67SGreg Roach
13016a40a66SGreg Roach        $sitemap_url = route('sitemap-index');
131a5f7ed67SGreg Roach
132ce42304aSGreg Roach        // This list comes from https://en.wikipedia.org/wiki/Sitemaps
133a5f7ed67SGreg Roach        $submit_urls = [
134a5f7ed67SGreg Roach            'Bing/Yahoo' => Html::url('https://www.bing.com/webmaster/ping.aspx', ['siteMap' => $sitemap_url]),
135a5f7ed67SGreg Roach            'Google'     => Html::url('https://www.google.com/webmasters/tools/ping', ['sitemap' => $sitemap_url]),
136a5f7ed67SGreg Roach        ];
137a5f7ed67SGreg Roach
138291c1b19SGreg Roach        return $this->viewResponse('modules/sitemap/config', [
1393df1e584SGreg Roach            'all_trees'   => $this->tree_service->all(),
140a5f7ed67SGreg Roach            'sitemap_url' => $sitemap_url,
141a5f7ed67SGreg Roach            'submit_urls' => $submit_urls,
14249a243cbSGreg Roach            'title'       => $this->title(),
143a5f7ed67SGreg Roach        ]);
1448c2e8227SGreg Roach    }
1458c2e8227SGreg Roach
1468c2e8227SGreg Roach    /**
1476ccdf4f0SGreg Roach     * How should this module be identified in the control panel, etc.?
148a5f7ed67SGreg Roach     *
1496ccdf4f0SGreg Roach     * @return string
1508c2e8227SGreg Roach     */
1516ccdf4f0SGreg Roach    public function title(): string
1526ccdf4f0SGreg Roach    {
153ad3143ccSGreg Roach        /* I18N: Name of a module - see https://en.wikipedia.org/wiki/Sitemaps */
1546ccdf4f0SGreg Roach        return I18N::translate('Sitemaps');
1556ccdf4f0SGreg Roach    }
1566ccdf4f0SGreg Roach
1576ccdf4f0SGreg Roach    /**
1586ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
1596ccdf4f0SGreg Roach     *
1606ccdf4f0SGreg Roach     * @return ResponseInterface
1616ccdf4f0SGreg Roach     */
1626ccdf4f0SGreg Roach    public function postAdminAction(ServerRequestInterface $request): ResponseInterface
163c1010edaSGreg Roach    {
1643df1e584SGreg Roach        foreach ($this->tree_service->all() as $tree) {
165748dbe15SGreg Roach            $include_in_sitemap = Validator::parsedBody($request)->boolean('sitemap' . $tree->id(), false);
166a5f7ed67SGreg Roach            $tree->setPreference('include_in_sitemap', (string) $include_in_sitemap);
1678c2e8227SGreg Roach        }
168a5f7ed67SGreg Roach
16949a243cbSGreg Roach        FlashMessages::addMessage(I18N::translate('The preferences for the module “%s” have been updated.', $this->title()), 'success');
170a5f7ed67SGreg Roach
1716ccdf4f0SGreg Roach        return redirect($this->getConfigLink());
1728c2e8227SGreg Roach    }
1738c2e8227SGreg Roach
1748c2e8227SGreg Roach    /**
17557ab2231SGreg Roach     * @param ServerRequestInterface $request
17657ab2231SGreg Roach     *
1776ccdf4f0SGreg Roach     * @return ResponseInterface
1788c2e8227SGreg Roach     */
17916a40a66SGreg Roach    public function handle(ServerRequestInterface $request): ResponseInterface
180c1010edaSGreg Roach    {
181b55cbc6bSGreg Roach        $route = Validator::attributes($request)->route();
182a5f7ed67SGreg Roach
18314aabe72SGreg Roach        if ($route->name === 'sitemap-style') {
18414aabe72SGreg Roach            $content = view('modules/sitemap/sitemap-xsl');
18514aabe72SGreg Roach
18614aabe72SGreg Roach            return response($content, StatusCodeInterface::STATUS_OK, [
1876172e7f6SGreg Roach                'content-type' => 'application/xml',
18814aabe72SGreg Roach            ]);
18914aabe72SGreg Roach        }
19014aabe72SGreg Roach
19116a40a66SGreg Roach        if ($route->name === 'sitemap-index') {
19216a40a66SGreg Roach            return $this->siteMapIndex($request);
19316a40a66SGreg Roach        }
19416a40a66SGreg Roach
19516a40a66SGreg Roach        return $this->siteMapFile($request);
19616a40a66SGreg Roach    }
19716a40a66SGreg Roach
19816a40a66SGreg Roach    /**
19916a40a66SGreg Roach     * @param ServerRequestInterface $request
20016a40a66SGreg Roach     *
20116a40a66SGreg Roach     * @return ResponseInterface
20216a40a66SGreg Roach     */
20349528f2bSGreg Roach    private function siteMapIndex(ServerRequestInterface $request): ResponseInterface
20416a40a66SGreg Roach    {
2056b9cb339SGreg Roach        $content = Registry::cache()->file()->remember('sitemap.xml', function (): string {
206659aa375SGreg Roach            // Which trees have sitemaps enabled?
207*f25fc0f9SGreg Roach            $tree_ids = $this->tree_service->all()
208*f25fc0f9SGreg Roach                ->filter(static fn(Tree $tree): bool => $tree->getPreference('include_in_sitemap') === '1')
209*f25fc0f9SGreg Roach                ->map(static fn(Tree $tree): int => $tree->id());
210659aa375SGreg Roach
211c5a1ffe6SGreg Roach            $count_families = DB::table('families')
212c5a1ffe6SGreg Roach                ->join('gedcom', 'f_file', '=', 'gedcom_id')
213c5a1ffe6SGreg Roach                ->whereIn('gedcom_id', $tree_ids)
214c5a1ffe6SGreg Roach                ->groupBy(['gedcom_id'])
215059898c9SGreg Roach                ->pluck(new Expression('COUNT(*) AS total'), 'gedcom_name');
216c5a1ffe6SGreg Roach
217fa17fb66SGreg Roach            $count_individuals = DB::table('individuals')
21816a40a66SGreg Roach                ->join('gedcom', 'i_file', '=', 'gedcom_id')
21916a40a66SGreg Roach                ->whereIn('gedcom_id', $tree_ids)
22016a40a66SGreg Roach                ->groupBy(['gedcom_id'])
221059898c9SGreg Roach                ->pluck(new Expression('COUNT(*) AS total'), 'gedcom_name');
222a5f7ed67SGreg Roach
223fa17fb66SGreg Roach            $count_media = DB::table('media')
22416a40a66SGreg Roach                ->join('gedcom', 'm_file', '=', 'gedcom_id')
22516a40a66SGreg Roach                ->whereIn('gedcom_id', $tree_ids)
22616a40a66SGreg Roach                ->groupBy(['gedcom_id'])
227059898c9SGreg Roach                ->pluck(new Expression('COUNT(*) AS total'), 'gedcom_name');
228a5f7ed67SGreg Roach
229fa17fb66SGreg Roach            $count_notes = DB::table('other')
23016a40a66SGreg Roach                ->join('gedcom', 'o_file', '=', 'gedcom_id')
23116a40a66SGreg Roach                ->whereIn('gedcom_id', $tree_ids)
232c5a1ffe6SGreg Roach                ->where('o_type', '=', Note::RECORD_TYPE)
23316a40a66SGreg Roach                ->groupBy(['gedcom_id'])
234059898c9SGreg Roach                ->pluck(new Expression('COUNT(*) AS total'), 'gedcom_name');
235a5f7ed67SGreg Roach
236fa17fb66SGreg Roach            $count_repositories = DB::table('other')
23716a40a66SGreg Roach                ->join('gedcom', 'o_file', '=', 'gedcom_id')
23816a40a66SGreg Roach                ->whereIn('gedcom_id', $tree_ids)
239c5a1ffe6SGreg Roach                ->where('o_type', '=', Repository::RECORD_TYPE)
24016a40a66SGreg Roach                ->groupBy(['gedcom_id'])
241059898c9SGreg Roach                ->pluck(new Expression('COUNT(*) AS total'), 'gedcom_name');
242a5f7ed67SGreg Roach
243fa17fb66SGreg Roach            $count_sources = DB::table('sources')
24416a40a66SGreg Roach                ->join('gedcom', 's_file', '=', 'gedcom_id')
24516a40a66SGreg Roach                ->whereIn('gedcom_id', $tree_ids)
24616a40a66SGreg Roach                ->groupBy(['gedcom_id'])
247059898c9SGreg Roach                ->pluck(new Expression('COUNT(*) AS total'), 'gedcom_name');
248a5f7ed67SGreg Roach
249c5a1ffe6SGreg Roach            $count_submitters = DB::table('other')
250c5a1ffe6SGreg Roach                ->join('gedcom', 'o_file', '=', 'gedcom_id')
251c5a1ffe6SGreg Roach                ->whereIn('gedcom_id', $tree_ids)
252c5a1ffe6SGreg Roach                ->where('o_type', '=', Submitter::RECORD_TYPE)
253c5a1ffe6SGreg Roach                ->groupBy(['gedcom_id'])
254059898c9SGreg Roach                ->pluck(new Expression('COUNT(*) AS total'), 'gedcom_name');
255c5a1ffe6SGreg Roach
25616a40a66SGreg Roach            // Versions 2.0.1 and earlier of this module stored large amounts of data in the settings.
25716a40a66SGreg Roach            DB::table('module_setting')
25816a40a66SGreg Roach                ->where('module_name', '=', $this->name())
25916a40a66SGreg Roach                ->delete();
26016a40a66SGreg Roach
26114aabe72SGreg Roach            return view('modules/sitemap/sitemap-index-xml', [
2623df1e584SGreg Roach                'all_trees'          => $this->tree_service->all(),
263c5a1ffe6SGreg Roach                'count_families'     => $count_families,
264a5f7ed67SGreg Roach                'count_individuals'  => $count_individuals,
265a5f7ed67SGreg Roach                'count_media'        => $count_media,
266a5f7ed67SGreg Roach                'count_notes'        => $count_notes,
267a5f7ed67SGreg Roach                'count_repositories' => $count_repositories,
268a5f7ed67SGreg Roach                'count_sources'      => $count_sources,
269c5a1ffe6SGreg Roach                'count_submitters'   => $count_submitters,
270a5f7ed67SGreg Roach                'last_mod'           => date('Y-m-d'),
271a5f7ed67SGreg Roach                'records_per_volume' => self::RECORDS_PER_VOLUME,
27214aabe72SGreg Roach                'sitemap_xsl'        => route('sitemap-style'),
273a5f7ed67SGreg Roach            ]);
27416a40a66SGreg Roach        }, self::CACHE_LIFE);
275a5f7ed67SGreg Roach
2766ccdf4f0SGreg Roach        return response($content, StatusCodeInterface::STATUS_OK, [
2776172e7f6SGreg Roach            'content-type' => 'application/xml',
278a5f7ed67SGreg Roach        ]);
279a5f7ed67SGreg Roach    }
280a5f7ed67SGreg Roach
281a5f7ed67SGreg Roach    /**
2826ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
283a5f7ed67SGreg Roach     *
2846ccdf4f0SGreg Roach     * @return ResponseInterface
285a5f7ed67SGreg Roach     */
28616a40a66SGreg Roach    private function siteMapFile(ServerRequestInterface $request): ResponseInterface
287c1010edaSGreg Roach    {
288b55cbc6bSGreg Roach        $tree = Validator::attributes($request)->tree('tree');
289b55cbc6bSGreg Roach        $type = Validator::attributes($request)->string('type');
290b55cbc6bSGreg Roach        $page = Validator::attributes($request)->integer('page');
291a5f7ed67SGreg Roach
292659aa375SGreg Roach        if ($tree->getPreference('include_in_sitemap') !== '1') {
293659aa375SGreg Roach            throw new HttpNotFoundException();
294659aa375SGreg Roach        }
295659aa375SGreg Roach
296c5a1ffe6SGreg Roach        $cache_key = 'sitemap/' . $tree->id() . '/' . $type . '/' . $page . '.xml';
29716a40a66SGreg Roach
2986b9cb339SGreg Roach        $content = Registry::cache()->file()->remember($cache_key, function () use ($tree, $type, $page): string {
299c5a1ffe6SGreg Roach            $records = $this->sitemapRecords($tree, $type, self::RECORDS_PER_VOLUME, self::RECORDS_PER_VOLUME * $page);
30016a40a66SGreg Roach
30114aabe72SGreg Roach            return view('modules/sitemap/sitemap-file-xml', [
302c5a1ffe6SGreg Roach                'priority'    => self::PRIORITY[$type],
3039e3c7a99SGreg Roach                'records'     => $records,
30414aabe72SGreg Roach                'sitemap_xsl' => route('sitemap-style'),
3059e3c7a99SGreg Roach                'tree'        => $tree,
3069e3c7a99SGreg Roach            ]);
30716a40a66SGreg Roach        }, self::CACHE_LIFE);
308a5f7ed67SGreg Roach
3096ccdf4f0SGreg Roach        return response($content, StatusCodeInterface::STATUS_OK, [
3106172e7f6SGreg Roach            'content-type' => 'application/xml',
311a5f7ed67SGreg Roach        ]);
312a5f7ed67SGreg Roach    }
313a5f7ed67SGreg Roach
314a5f7ed67SGreg Roach    /**
315a5f7ed67SGreg Roach     * @param Tree   $tree
316a5f7ed67SGreg Roach     * @param string $type
317a5f7ed67SGreg Roach     * @param int    $limit
318a5f7ed67SGreg Roach     * @param int    $offset
319a5f7ed67SGreg Roach     *
32036779af1SGreg Roach     * @return Collection<int,GedcomRecord>
321a5f7ed67SGreg Roach     */
322886b77daSGreg Roach    private function sitemapRecords(Tree $tree, string $type, int $limit, int $offset): Collection
323c1010edaSGreg Roach    {
324a5f7ed67SGreg Roach        switch ($type) {
325c5a1ffe6SGreg Roach            case Family::RECORD_TYPE:
326c5a1ffe6SGreg Roach                $records = $this->sitemapFamilies($tree, $limit, $offset);
327c5a1ffe6SGreg Roach                break;
328c5a1ffe6SGreg Roach
32916a40a66SGreg Roach            case Individual::RECORD_TYPE:
330a5f7ed67SGreg Roach                $records = $this->sitemapIndividuals($tree, $limit, $offset);
331a5f7ed67SGreg Roach                break;
332a5f7ed67SGreg Roach
33316a40a66SGreg Roach            case Media::RECORD_TYPE:
334a5f7ed67SGreg Roach                $records = $this->sitemapMedia($tree, $limit, $offset);
335a5f7ed67SGreg Roach                break;
336a5f7ed67SGreg Roach
33716a40a66SGreg Roach            case Note::RECORD_TYPE:
338a5f7ed67SGreg Roach                $records = $this->sitemapNotes($tree, $limit, $offset);
339a5f7ed67SGreg Roach                break;
340a5f7ed67SGreg Roach
34116a40a66SGreg Roach            case Repository::RECORD_TYPE:
342a5f7ed67SGreg Roach                $records = $this->sitemapRepositories($tree, $limit, $offset);
343a5f7ed67SGreg Roach                break;
344a5f7ed67SGreg Roach
34516a40a66SGreg Roach            case Source::RECORD_TYPE:
346a5f7ed67SGreg Roach                $records = $this->sitemapSources($tree, $limit, $offset);
347a5f7ed67SGreg Roach                break;
348a5f7ed67SGreg Roach
349c5a1ffe6SGreg Roach            case Submitter::RECORD_TYPE:
350c5a1ffe6SGreg Roach                $records = $this->sitemapSubmitters($tree, $limit, $offset);
351c5a1ffe6SGreg Roach                break;
352c5a1ffe6SGreg Roach
353a5f7ed67SGreg Roach            default:
354d501c45dSGreg Roach                throw new HttpNotFoundException('Invalid record type: ' . $type);
355a5f7ed67SGreg Roach        }
356a5f7ed67SGreg Roach
357a5f7ed67SGreg Roach        // Skip private records.
358*f25fc0f9SGreg Roach        $records = $records->filter(static fn(GedcomRecord $record): bool => $record->canShow(Auth::PRIV_PRIVATE));
359a5f7ed67SGreg Roach
360a5f7ed67SGreg Roach        return $records;
361a5f7ed67SGreg Roach    }
362a5f7ed67SGreg Roach
363a5f7ed67SGreg Roach    /**
364a5f7ed67SGreg Roach     * @param Tree $tree
365a5f7ed67SGreg Roach     * @param int  $limit
366a5f7ed67SGreg Roach     * @param int  $offset
367a5f7ed67SGreg Roach     *
36836779af1SGreg Roach     * @return Collection<int,Family>
369c5a1ffe6SGreg Roach     */
370c5a1ffe6SGreg Roach    private function sitemapFamilies(Tree $tree, int $limit, int $offset): Collection
371c5a1ffe6SGreg Roach    {
372c5a1ffe6SGreg Roach        return DB::table('families')
373c5a1ffe6SGreg Roach            ->where('f_file', '=', $tree->id())
374c5a1ffe6SGreg Roach            ->orderBy('f_id')
375c5a1ffe6SGreg Roach            ->skip($offset)
376c5a1ffe6SGreg Roach            ->take($limit)
377c5a1ffe6SGreg Roach            ->get()
3786b9cb339SGreg Roach            ->map(Registry::familyFactory()->mapper($tree));
379c5a1ffe6SGreg Roach    }
380c5a1ffe6SGreg Roach
381c5a1ffe6SGreg Roach    /**
382c5a1ffe6SGreg Roach     * @param Tree $tree
383c5a1ffe6SGreg Roach     * @param int  $limit
384c5a1ffe6SGreg Roach     * @param int  $offset
385c5a1ffe6SGreg Roach     *
38636779af1SGreg Roach     * @return Collection<int,Individual>
387a5f7ed67SGreg Roach     */
388886b77daSGreg Roach    private function sitemapIndividuals(Tree $tree, int $limit, int $offset): Collection
389c1010edaSGreg Roach    {
390886b77daSGreg Roach        return DB::table('individuals')
391fa17fb66SGreg Roach            ->where('i_file', '=', $tree->id())
392fa17fb66SGreg Roach            ->orderBy('i_id')
393fa17fb66SGreg Roach            ->skip($offset)
394fa17fb66SGreg Roach            ->take($limit)
395886b77daSGreg Roach            ->get()
3966b9cb339SGreg Roach            ->map(Registry::individualFactory()->mapper($tree));
3978c2e8227SGreg Roach    }
398a5f7ed67SGreg Roach
399a5f7ed67SGreg Roach    /**
400a5f7ed67SGreg Roach     * @param Tree $tree
401a5f7ed67SGreg Roach     * @param int  $limit
402a5f7ed67SGreg Roach     * @param int  $offset
403a5f7ed67SGreg Roach     *
40436779af1SGreg Roach     * @return Collection<int,Media>
405a5f7ed67SGreg Roach     */
406886b77daSGreg Roach    private function sitemapMedia(Tree $tree, int $limit, int $offset): Collection
407c1010edaSGreg Roach    {
408886b77daSGreg Roach        return DB::table('media')
409fa17fb66SGreg Roach            ->where('m_file', '=', $tree->id())
410fa17fb66SGreg Roach            ->orderBy('m_id')
411fa17fb66SGreg Roach            ->skip($offset)
412fa17fb66SGreg Roach            ->take($limit)
413886b77daSGreg Roach            ->get()
4146b9cb339SGreg Roach            ->map(Registry::mediaFactory()->mapper($tree));
4158c2e8227SGreg Roach    }
4168c2e8227SGreg Roach
4178c2e8227SGreg Roach    /**
418a5f7ed67SGreg Roach     * @param Tree $tree
419a5f7ed67SGreg Roach     * @param int  $limit
420a5f7ed67SGreg Roach     * @param int  $offset
421a5f7ed67SGreg Roach     *
42236779af1SGreg Roach     * @return Collection<int,Note>
4238c2e8227SGreg Roach     */
424886b77daSGreg Roach    private function sitemapNotes(Tree $tree, int $limit, int $offset): Collection
425c1010edaSGreg Roach    {
426886b77daSGreg Roach        return DB::table('other')
427fa17fb66SGreg Roach            ->where('o_file', '=', $tree->id())
428c5a1ffe6SGreg Roach            ->where('o_type', '=', Note::RECORD_TYPE)
429fa17fb66SGreg Roach            ->orderBy('o_id')
430fa17fb66SGreg Roach            ->skip($offset)
431fa17fb66SGreg Roach            ->take($limit)
432886b77daSGreg Roach            ->get()
4336b9cb339SGreg Roach            ->map(Registry::noteFactory()->mapper($tree));
4348c2e8227SGreg Roach    }
4358c2e8227SGreg Roach
436a5f7ed67SGreg Roach    /**
437a5f7ed67SGreg Roach     * @param Tree $tree
438a5f7ed67SGreg Roach     * @param int  $limit
439a5f7ed67SGreg Roach     * @param int  $offset
440a5f7ed67SGreg Roach     *
44136779af1SGreg Roach     * @return Collection<int,Repository>
442a5f7ed67SGreg Roach     */
443886b77daSGreg Roach    private function sitemapRepositories(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', '=', Repository::RECORD_TYPE)
448fa17fb66SGreg Roach            ->orderBy('o_id')
449fa17fb66SGreg Roach            ->skip($offset)
450fa17fb66SGreg Roach            ->take($limit)
451886b77daSGreg Roach            ->get()
4526b9cb339SGreg Roach            ->map(Registry::repositoryFactory()->mapper($tree));
453a5f7ed67SGreg Roach    }
454a5f7ed67SGreg 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,Source>
461a5f7ed67SGreg Roach     */
462886b77daSGreg Roach    private function sitemapSources(Tree $tree, int $limit, int $offset): Collection
463c1010edaSGreg Roach    {
464886b77daSGreg Roach        return DB::table('sources')
465fa17fb66SGreg Roach            ->where('s_file', '=', $tree->id())
466fa17fb66SGreg Roach            ->orderBy('s_id')
467fa17fb66SGreg Roach            ->skip($offset)
468fa17fb66SGreg Roach            ->take($limit)
469886b77daSGreg Roach            ->get()
4706b9cb339SGreg Roach            ->map(Registry::sourceFactory()->mapper($tree));
4718c2e8227SGreg Roach    }
472c5a1ffe6SGreg Roach
473c5a1ffe6SGreg Roach    /**
474c5a1ffe6SGreg Roach     * @param Tree $tree
475c5a1ffe6SGreg Roach     * @param int  $limit
476c5a1ffe6SGreg Roach     * @param int  $offset
477c5a1ffe6SGreg Roach     *
47836779af1SGreg Roach     * @return Collection<int,Submitter>
479c5a1ffe6SGreg Roach     */
480c5a1ffe6SGreg Roach    private function sitemapSubmitters(Tree $tree, int $limit, int $offset): Collection
481c5a1ffe6SGreg Roach    {
482c5a1ffe6SGreg Roach        return DB::table('other')
483c5a1ffe6SGreg Roach            ->where('o_file', '=', $tree->id())
484c5a1ffe6SGreg Roach            ->where('o_type', '=', Submitter::RECORD_TYPE)
485c5a1ffe6SGreg Roach            ->orderBy('o_id')
486c5a1ffe6SGreg Roach            ->skip($offset)
487c5a1ffe6SGreg Roach            ->take($limit)
488c5a1ffe6SGreg Roach            ->get()
4896b9cb339SGreg Roach            ->map(Registry::submitterFactory()->mapper($tree));
490c5a1ffe6SGreg Roach    }
4918c2e8227SGreg Roach}
492