summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGreg Roach <fisharebest@webtrees.net>2018-05-28 12:21:56 +0100
committerGreg Roach <fisharebest@webtrees.net>2018-05-28 12:21:56 +0100
commita5f7ed670e0cf1bcf88f372085c8bf017fd1c5bd (patch)
treebfae128153b2a2cae6f5dc6031e34a2fb6a40f38 /app
parent98f91fab6dbf852034ba195e33e632dbfe2fa346 (diff)
downloadwebtrees-a5f7ed670e0cf1bcf88f372085c8bf017fd1c5bd.tar.gz
webtrees-a5f7ed670e0cf1bcf88f372085c8bf017fd1c5bd.tar.bz2
webtrees-a5f7ed670e0cf1bcf88f372085c8bf017fd1c5bd.zip
Convert sitemap module to http/controller
Diffstat (limited to 'app')
-rw-r--r--app/Module/SiteMapModule.php570
1 files changed, 303 insertions, 267 deletions
diff --git a/app/Module/SiteMapModule.php b/app/Module/SiteMapModule.php
index 1e351df412..ca03b23444 100644
--- a/app/Module/SiteMapModule.php
+++ b/app/Module/SiteMapModule.php
@@ -15,11 +15,9 @@
*/
namespace Fisharebest\Webtrees\Module;
-use Fisharebest\Webtrees\Auth;
-use Fisharebest\Webtrees\Bootstrap4;
-use Fisharebest\Webtrees\Controller\PageController;
use Fisharebest\Webtrees\Database;
-use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\FlashMessages;
+use Fisharebest\Webtrees\GedcomRecord;
use Fisharebest\Webtrees\Html;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Individual;
@@ -28,6 +26,10 @@ use Fisharebest\Webtrees\Note;
use Fisharebest\Webtrees\Repository;
use Fisharebest\Webtrees\Source;
use Fisharebest\Webtrees\Tree;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Class SiteMapModule
@@ -36,314 +38,348 @@ class SiteMapModule extends AbstractModule implements ModuleConfigInterface {
const RECORDS_PER_VOLUME = 500; // Keep sitemap files small, for memory, CPU and max_allowed_packet limits.
const CACHE_LIFE = 1209600; // Two weeks
- /** {@inheritdoc} */
+ /**
+ * How should this module be labelled on tabs, menus, etc.?
+ *
+ * @return string
+ */
public function getTitle() {
- return /* I18N: Name of a module - see http://en.wikipedia.org/wiki/Sitemaps */ I18N::translate('Sitemaps');
+ return /* I18N: Name of a module - see http://en.wikipedia.org/wiki/Sitemaps */
+ I18N::translate('Sitemaps');
}
- /** {@inheritdoc} */
+ /**
+ * A sentence describing what this module does.
+ *
+ * @return string
+ */
public function getDescription() {
- return /* I18N: Description of the “Sitemaps” module */ I18N::translate('Generate sitemap files for search engines.');
+ return /* I18N: Description of the “Sitemaps” module */
+ I18N::translate('Generate sitemap files for search engines.');
}
/**
- * This is a general purpose hook, allowing modules to respond to routes
- * of the form module.php?mod=FOO&mod_action=BAR
+ * The URL to a page where the user can modify the configuration of this module.
*
- * @param string $mod_action
+ * @return string
*/
- public function modAction($mod_action) {
- switch ($mod_action) {
- case 'admin':
- $this->admin();
- break;
- case 'generate':
- $this->generate(Filter::get('file'));
- break;
- default:
- http_response_code(404);
- }
+ public function getConfigLink() {
+ return route('module', ['module' => 'sitemap', 'action' => 'Admin']);
}
/**
- * Generate an XML file.
+ * @param Request $request
*
- * @param string $file
+ * @return Response
*/
- private function generate($file) {
- if ($file == 'sitemap.xml') {
- $this->generateIndex();
- } elseif (preg_match('/^sitemap-(\d+)-([isrmn])-(\d+).xml$/', $file, $match)) {
- $this->generateFile($match[1], $match[2], $match[3]);
- } else {
- http_response_code(404);
+ public function getAdminAction(Request $request): Response {
+ $this->layout = 'layouts/administration';
+
+ $sitemap_url = route('module', ['module' => 'sitemap', 'action' => 'Index']);
+
+ // This list comes from http://en.wikipedia.org/wiki/Sitemaps
+ $submit_urls = [
+ 'Bing/Yahoo' => Html::url('https://www.bing.com/webmaster/ping.aspx', ['siteMap' => $sitemap_url]),
+ 'Google' => Html::url('https://www.google.com/webmasters/tools/ping', ['sitemap' => $sitemap_url]),
+ ];
+
+ return $this->viewResponse('modules/sitemap/admin', [
+ 'all_trees' => Tree::getAll(),
+ 'sitemap_url' => $sitemap_url,
+ 'submit_urls' => $submit_urls,
+ 'title' => $this->getTitle(),
+ ]);
+ }
+
+ /**
+ * @param Request $request
+ *
+ * @return RedirectResponse
+ */
+ public function postAdminAction(Request $request): RedirectResponse {
+ foreach (Tree::getAll() as $tree) {
+ $include_in_sitemap = (bool) $request->get('sitemap' . $tree->getTreeId());
+ $tree->setPreference('include_in_sitemap', (string) $include_in_sitemap);
}
+
+ FlashMessages::addMessage(I18N::translate('The preferences for the module “%s” have been updated.', $this->getName()), 'success');
+
+ return new RedirectResponse($this->getConfigLink());
}
/**
- * The index file contains references to all the other files.
- * These files are the same for visitors/users/admins.
+ * @param Request $request
+ *
+ * @return Response
*/
- private function generateIndex() {
- // Check the cache
+ public function getIndexAction(Request $request): Response {
$timestamp = (int) $this->getPreference('sitemap.timestamp');
+
if ($timestamp > WT_TIMESTAMP - self::CACHE_LIFE) {
- $data = $this->getPreference('sitemap.xml');
+ $content = $this->getPreference('sitemap.xml');
} else {
- $data = '';
- $lastmod = '<lastmod>' . date('Y-m-d') . '</lastmod>';
- foreach (Tree::getAll() as $tree) {
- if ($tree->getPreference('include_in_sitemap')) {
- $n = Database::prepare(
- "SELECT COUNT(*) FROM `##individuals` WHERE i_file = :tree_id"
- )->execute(['tree_id' => $tree->getTreeId()])->fetchOne();
- for ($i = 0; $i <= $n / self::RECORDS_PER_VOLUME; ++$i) {
- $data .= '<sitemap><loc>' . WT_BASE_URL . 'module.php?mod=' . $this->getName() . '&amp;mod_action=generate&amp;file=sitemap-' . $tree->getTreeId() . '-i-' . $i . '.xml</loc>' . $lastmod . '</sitemap>' . PHP_EOL;
- }
- $n = Database::prepare(
- "SELECT COUNT(*) FROM `##sources` WHERE s_file = :tree_id"
- )->execute(['tree_id' => $tree->getTreeId()])->fetchOne();
- if ($n) {
- for ($i = 0; $i <= $n / self::RECORDS_PER_VOLUME; ++$i) {
- $data .= '<sitemap><loc>' . WT_BASE_URL . 'module.php?mod=' . $this->getName() . '&amp;mod_action=generate&amp;file=sitemap-' . $tree->getTreeId() . '-s-' . $i . '.xml</loc>' . $lastmod . '</sitemap>' . PHP_EOL;
- }
- }
- $n = Database::prepare(
- "SELECT COUNT(*) FROM `##other` WHERE o_file = :tree_id AND o_type = 'REPO'"
- )->execute(['tree_id' => $tree->getTreeId()])->fetchOne();
- if ($n) {
- for ($i = 0; $i <= $n / self::RECORDS_PER_VOLUME; ++$i) {
- $data .= '<sitemap><loc>' . WT_BASE_URL . 'module.php?mod=' . $this->getName() . '&amp;mod_action=generate&amp;file=sitemap-' . $tree->getTreeId() . '-r-' . $i . '.xml</loc>' . $lastmod . '</sitemap>' . PHP_EOL;
- }
- }
- $n = Database::prepare(
- "SELECT COUNT(*) FROM `##other` WHERE o_file = :tree_id AND o_type = 'NOTE'"
- )->execute(['tree_id' => $tree->getTreeId()])->fetchOne();
- if ($n) {
- for ($i = 0; $i <= $n / self::RECORDS_PER_VOLUME; ++$i) {
- $data .= '<sitemap><loc>' . WT_BASE_URL . 'module.php?mod=' . $this->getName() . '&amp;mod_action=generate&amp;file=sitemap-' . $tree->getTreeId() . '-n-' . $i . '.xml</loc>' . $lastmod . '</sitemap>' . PHP_EOL;
- }
- }
- $n = Database::prepare(
- "SELECT COUNT(*) FROM `##media` WHERE m_file = :tree_id"
- )->execute(['tree_id' => $tree->getTreeId()])->fetchOne();
- if ($n) {
- for ($i = 0; $i <= $n / self::RECORDS_PER_VOLUME; ++$i) {
- $data .= '<sitemap><loc>' . WT_BASE_URL . 'module.php?mod=' . $this->getName() . '&amp;mod_action=generate&amp;file=sitemap-' . $tree->getTreeId() . '-m-' . $i . '.xml</loc>' . $lastmod . '</sitemap>' . PHP_EOL;
- }
- }
- }
- }
- $data = '<' . '?xml version="1.0" encoding="UTF-8" ?' . '>' . PHP_EOL . '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . PHP_EOL . $data . '</sitemapindex>' . PHP_EOL;
- // Cache this data.
- $this->setPreference('sitemap.xml', $data);
- $this->setPreference('sitemap.timestamp', WT_TIMESTAMP);
+ $count_individuals = Database::prepare(
+ "SELECT i_file, COUNT(*) FROM `##individuals` GROUP BY i_file"
+ )->execute()->fetchAssoc();
+
+ $count_media = Database::prepare(
+ "SELECT m_file, COUNT(*) FROM `##media` GROUP BY m_file"
+ )->execute()->fetchAssoc();
+
+ $count_notes = Database::prepare(
+ "SELECT o_file, COUNT(*) FROM `##other` WHERE o_type='NOTE' GROUP BY o_file"
+ )->execute()->fetchAssoc();
+
+ $count_repositories = Database::prepare(
+ "SELECT o_file, COUNT(*) FROM `##other` WHERE o_type='REPO' GROUP BY o_file"
+ )->execute()->fetchAssoc();
+
+ $count_sources = Database::prepare(
+ "SELECT s_file, COUNT(*) FROM `##sources` GROUP BY s_file"
+ )->execute()->fetchAssoc();
+
+ $content = view('modules/sitemap/sitemap-index', [
+ 'all_trees' => Tree::getAll(),
+ 'count_individuals' => $count_individuals,
+ 'count_media' => $count_media,
+ 'count_notes' => $count_notes,
+ 'count_repositories' => $count_repositories,
+ 'count_sources' => $count_sources,
+ 'last_mod' => date('Y-m-d'),
+ 'records_per_volume' => self::RECORDS_PER_VOLUME,
+ ]);
+
+ $this->setPreference('sitemap.xml', $content);
}
- header('Content-Type: application/xml');
- header('Content-Length: ' . strlen($data));
- echo $data;
+
+ return new Response($content, Response::HTTP_OK, [
+ 'Content-Type' => 'application/xml',
+ ]);
}
/**
- * A separate file for each family tree and each record type.
- * These files depend on access levels, so only cache for visitors.
+ * @param Request $request
*
- * @param int $ged_id
- * @param string $rec_type
- * @param string $volume
+ * @return Response
*/
- private function generateFile($ged_id, $rec_type, $volume) {
- $tree = Tree::findById($ged_id);
- // Check the cache
- $timestamp = (int) $this->getPreference('sitemap-' . $ged_id . '-' . $rec_type . '-' . $volume . '.timestamp');
- if ($timestamp > WT_TIMESTAMP - self::CACHE_LIFE && !Auth::check()) {
- $data = $this->getPreference('sitemap-' . $ged_id . '-' . $rec_type . '-' . $volume . '.xml');
+ public function getFileAction(Request $request): Response {
+ $file = $request->get('file', '');
+
+ if (!preg_match('/^(\d+)-([imnrs])-(\d+)$/', $file, $match)) {
+ throw new NotFoundHttpException('Bad sitemap file');
+ }
+
+ $timestamp = (int) $this->getPreference('sitemap-' . $file . '.timestamp');
+
+ if ($timestamp > WT_TIMESTAMP - self::CACHE_LIFE) {
+ $content = $this->getPreference('sitemap-' . $file . '.xml');
} else {
- $data = '<url><loc>' . WT_BASE_URL . 'index.php?ctype=gedcom&amp;ged=' . $tree->getNameUrl() . '</loc></url>' . PHP_EOL;
- $records = [];
- switch ($rec_type) {
- case 'i':
- $rows = Database::prepare(
- "SELECT i_id AS xref, i_gedcom AS gedcom" .
- " FROM `##individuals`" .
- " WHERE i_file = :tree_id" .
- " ORDER BY i_id" .
- " LIMIT :limit OFFSET :offset"
- )->execute([
- 'tree_id' => $ged_id,
- 'limit' => self::RECORDS_PER_VOLUME,
- 'offset' => self::RECORDS_PER_VOLUME * $volume,
- ])->fetchAll();
- foreach ($rows as $row) {
- $records[] = Individual::getInstance($row->xref, $tree, $row->gedcom);
- }
- break;
- case 's':
- $rows = Database::prepare(
- "SELECT s_id AS xref, s_gedcom AS gedcom" .
- " FROM `##sources`" .
- " WHERE s_file = :tree_id" .
- " ORDER BY s_id" .
- " LIMIT :limit OFFSET :offset"
- )->execute([
- 'tree_id' => $ged_id,
- 'limit' => self::RECORDS_PER_VOLUME,
- 'offset' => self::RECORDS_PER_VOLUME * $volume,
- ])->fetchAll();
- foreach ($rows as $row) {
- $records[] = Source::getInstance($row->xref, $tree, $row->gedcom);
- }
- break;
- case 'r':
- $rows = Database::prepare(
- "SELECT o_id AS xref, o_gedcom AS gedcom" .
- " FROM `##other`" .
- " WHERE o_file = :tree_id AND o_type = 'REPO'" .
- " ORDER BY o_id" .
- " LIMIT :limit OFFSET :offset"
- )->execute([
- 'tree_id' => $ged_id,
- 'limit' => self::RECORDS_PER_VOLUME,
- 'offset' => self::RECORDS_PER_VOLUME * $volume,
- ])->fetchAll();
- foreach ($rows as $row) {
- $records[] = Repository::getInstance($row->xref, $tree, $row->gedcom);
- }
- break;
- case 'n':
- $rows = Database::prepare(
- "SELECT o_id AS xref, o_gedcom AS gedcom" .
- " FROM `##other`" .
- " WHERE o_file = :tree_id AND o_type = 'NOTE'" .
- " ORDER BY o_id" .
- " LIMIT :limit OFFSET :offset"
- )->execute([
- 'tree_id' => $ged_id,
- 'limit' => self::RECORDS_PER_VOLUME,
- 'offset' => self::RECORDS_PER_VOLUME * $volume,
- ])->fetchAll();
- foreach ($rows as $row) {
- $records[] = Note::getInstance($row->xref, $tree, $row->gedcom);
- }
- break;
- case 'm':
- $rows = Database::prepare(
- "SELECT m_id AS xref, m_gedcom AS gedcom" .
- " FROM `##media`" .
- " WHERE m_file = :tree_id" .
- " ORDER BY m_id" .
- " LIMIT :limit OFFSET :offset"
- )->execute([
- 'tree_id' => $ged_id,
- 'limit' => self::RECORDS_PER_VOLUME,
- 'offset' => self::RECORDS_PER_VOLUME * $volume,
- ])->fetchAll();
- foreach ($rows as $row) {
- $records[] = Media::getInstance($row->xref, $tree, $row->gedcom);
- }
- break;
- }
- foreach ($records as $record) {
- if ($record->canShowName()) {
- $data .= '<url>';
- $data .= '<loc>' . WT_BASE_URL . e($record->url()) . '</loc>';
- $chan = $record->getFirstFact('CHAN');
- if ($chan) {
- $date = $chan->getDate();
- if ($date->isOK()) {
- $data .= '<lastmod>' . $date->minimumDate()->Format('%Y-%m-%d') . '</lastmod>';
- }
- }
- $data .= '</url>' . PHP_EOL;
- }
- }
- $data = '<' . '?xml version="1.0" encoding="UTF-8" ?' . '>' . PHP_EOL . '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">' . PHP_EOL . $data . '</urlset>' . PHP_EOL;
- // Cache this data - but only for visitors, as we don’t want
- // visitors to see data created by signed-in users.
- if (!Auth::check()) {
- $this->setPreference('sitemap-' . $ged_id . '-' . $rec_type . '-' . $volume . '.xml', $data);
- $this->setPreference('sitemap-' . $ged_id . '-' . $rec_type . '-' . $volume . '.timestamp', WT_TIMESTAMP);
+ $tree = Tree::findById((int) $match[1]);
+
+ if ($tree === null) {
+ throw new NotFoundHttpException('No such tree');
}
+
+ $records = $this->sitemapRecords($tree, $match[2], self::RECORDS_PER_VOLUME,
+ self::RECORDS_PER_VOLUME * $match[3]);
+
+ $content = view('modules/sitemap/sitemap-file', ['records' => $records]);
+
+ $this->setPreference('sitemap.xml', $content);
}
- header('Content-Type: application/xml');
- header('Content-Length: ' . strlen($data));
- echo $data;
+
+ return new Response($content, Response::HTTP_OK, [
+ 'Content-Type' => 'application/xml',
+ ]);
}
/**
- * Edit the configuration
+ * @param Tree $tree
+ * @param string $type
+ * @param int $limit
+ * @param int $offset
+ *
+ * @return array
*/
- private function admin() {
- $controller = new PageController;
- $controller
- ->restrictAccess(Auth::isAdmin())
- ->setPageTitle($this->getTitle())
- ->pageHeader();
+ private function sitemapRecords(Tree $tree, string $type, int $limit, int $offset): array {
+ switch ($type) {
+ case 'i':
+ $records = $this->sitemapIndividuals($tree, $limit, $offset);
+ break;
- // Save the updated preferences
- if (Filter::post('action') == 'save') {
- foreach (Tree::getAll() as $tree) {
- $tree->setPreference('include_in_sitemap', Filter::postBool('include' . $tree->getTreeId()));
- }
- // Clear cache and force files to be regenerated
- Database::prepare(
- "DELETE FROM `##module_setting` WHERE setting_name LIKE 'sitemap%'"
- )->execute();
+ case 'm':
+ $records = $this->sitemapMedia($tree, $limit, $offset);
+ break;
+
+ case 'n':
+ $records = $this->sitemapNotes($tree, $limit, $offset);
+ break;
+
+ case 'r':
+ $records = $this->sitemapRepositories($tree, $limit, $offset);
+ break;
+
+ case 's':
+ $records = $this->sitemapSources($tree, $limit, $offset);
+ break;
+
+ default:
+ throw new NotFoundHttpException('Invalid record type: ' . $type);
}
- $include_any = false;
+ // Skip records that no longer exist.
+ $records = array_filter($records);
- echo Bootstrap4::breadcrumbs([
- route('admin-control-panel') => I18N::translate('Control panel'),
- route('admin-modules') => I18N::translate('Module administration'),
- ], $controller->getPageTitle());
- ?>
+ // Skip private records.
+ $records = array_filter($records, function (GedcomRecord $record) {
+ return $record->canShow();
+ });
- <h1><?= $controller->getPageTitle() ?></h1>
- <?php
+ return $records;
+ }
- echo
- '<p>',
- /* I18N: The www.sitemaps.org site is translated into many languages (e.g. http://www.sitemaps.org/fr/) - choose an appropriate URL. */
- I18N::translate('Sitemaps are a way for webmasters to tell search engines about the pages on a website that are available for crawling. All major search engines support sitemaps. For more information, see <a href="http://www.sitemaps.org/">www.sitemaps.org</a>.') .
- '</p>',
- '<p>', /* I18N: Label for a configuration option */ I18N::translate('Which family trees should be included in the sitemaps'), '</p>',
- '<form method="post" action="module.php?mod=' . $this->getName() . '&amp;mod_action=admin">',
- '<input type="hidden" name="action" value="save">';
- foreach (Tree::getAll() as $tree) {
- echo '<div class="form-check"><label><input type="checkbox" name="include', $tree->getTreeId(), '" ';
- if ($tree->getPreference('include_in_sitemap')) {
- echo 'checked';
- $include_any = true;
- }
- echo '>', e($tree->getTitle()), '</label></div>';
+ /**
+ * @param Tree $tree
+ * @param int $limit
+ * @param int $offset
+ *
+ * @return array
+ */
+ private function sitemapIndividuals(Tree $tree, int $limit, int $offset): array {
+ $rows = Database::prepare(
+ "SELECT i_id AS xref, i_gedcom AS gedcom" .
+ " FROM `##individuals`" .
+ " WHERE i_file = :tree_id" .
+ " ORDER BY i_id" .
+ " LIMIT :limit OFFSET :offset"
+ )->execute([
+ 'tree_id' => $tree->getTreeId(),
+ 'limit' => $limit,
+ 'offset' => $offset,
+ ])->fetchAll();
+
+ $records = [];
+
+ foreach ($rows as $row) {
+ $records[] = Individual::getInstance($row->xref, $tree, $row->gedcom);
}
- echo
- '<input type="submit" value="', I18N::translate('save'), '">',
- '</form>',
- '<hr>';
- if ($include_any) {
- $site_map_url1 = WT_BASE_URL . 'module.php?mod=' . $this->getName() . '&amp;mod_action=generate&amp;file=sitemap.xml';
- $site_map_url2 = rawurlencode(WT_BASE_URL . 'module.php?mod=' . $this->getName() . '&mod_action=generate&file=sitemap.xml');
- echo
- '<p>', I18N::translate('To tell search engines that sitemaps are available, you should add the following line to your robots.txt file.'), '</p>',
- '<pre>Sitemap: ', $site_map_url1, '</pre>',
- '<hr>',
- '<p>', I18N::translate('To tell search engines that sitemaps are available, you can use the following links.'), '</p>',
- '<ul>',
- // This list comes from http://en.wikipedia.org/wiki/Sitemaps
- '<li><a href="https://www.bing.com/webmaster/ping.aspx?siteMap=' . $site_map_url2 . '">Bing</a></li>',
- '<li><a href="https://www.google.com/webmasters/tools/ping?sitemap=' . $site_map_url2 . '">Google</a></li>',
- '</ul>';
+ return $records;
+ }
+
+ /**
+ * @param Tree $tree
+ * @param int $limit
+ * @param int $offset
+ *
+ * @return array
+ */
+ private function sitemapMedia(Tree $tree, int $limit, int $offset): array {
+ $rows = Database::prepare(
+ "SELECT m_id AS xref, m_gedcom AS gedcom" .
+ " FROM `##media`" .
+ " WHERE m_file = :tree_id" .
+ " ORDER BY m_id" .
+ " LIMIT :limit OFFSET :offset"
+ )->execute([
+ 'tree_id' => $tree->getTreeId(),
+ 'limit' => $limit,
+ 'offset' => $offset,
+ ])->fetchAll();
+
+ $records = [];
+
+ foreach ($rows as $row) {
+ $records[] = Media::getInstance($row->xref, $tree, $row->gedcom);
}
+
+ return $records;
}
- /** {@inheritdoc} */
- public function getConfigLink() {
- return Html::url('module.php', [
- 'mod' => $this->getName(),
- 'mod_action' => 'admin',
- ]);
+ /**
+ * @param Tree $tree
+ * @param int $limit
+ * @param int $offset
+ *
+ * @return array
+ */
+ private function sitemapNotes(Tree $tree, int $limit, int $offset): array {
+ $rows = Database::prepare(
+ "SELECT o_id AS xref, o_gedcom AS gedcom" .
+ " FROM `##other`" .
+ " WHERE o_file = :tree_id AND o_type = 'NOTE'" .
+ " ORDER BY o_id" .
+ " LIMIT :limit OFFSET :offset"
+ )->execute([
+ 'tree_id' => $tree->getTreeId(),
+ 'limit' => $limit,
+ 'offset' => $offset,
+ ])->fetchAll();
+
+ $records = [];
+
+ foreach ($rows as $row) {
+ $records[] = Note::getInstance($row->xref, $tree, $row->gedcom);
+ }
+
+ return $records;
+ }
+
+ /**
+ * @param Tree $tree
+ * @param int $limit
+ * @param int $offset
+ *
+ * @return array
+ */
+ private function sitemapRepositories(Tree $tree, int $limit, int $offset): array {
+ $rows = Database::prepare(
+ "SELECT o_id AS xref, o_gedcom AS gedcom" .
+ " FROM `##other`" .
+ " WHERE o_file = :tree_id AND o_type = 'REPO'" .
+ " ORDER BY o_id" .
+ " LIMIT :limit OFFSET :offset"
+ )->execute([
+ 'tree_id' => $tree->getTreeId(),
+ 'limit' => $limit,
+ 'offset' => $offset,
+ ])->fetchAll();
+
+ $records = [];
+
+ foreach ($rows as $row) {
+ $records[] = Repository::getInstance($row->xref, $tree, $row->gedcom);
+ }
+
+ return $records;
+ }
+
+ /**
+ * @param Tree $tree
+ * @param int $limit
+ * @param int $offset
+ *
+ * @return array
+ */
+ private function sitemapSources(Tree $tree, int $limit, int $offset): array {
+ $rows = Database::prepare(
+ "SELECT s_id AS xref, s_gedcom AS gedcom" .
+ " FROM `##sources`" .
+ " WHERE s_file = :tree_id" .
+ " ORDER BY s_id" .
+ " LIMIT :limit OFFSET :offset"
+ )->execute([
+ 'tree_id' => $tree->getTreeId(),
+ 'limit' => $limit,
+ 'offset' => $offset,
+ ])->fetchAll();
+
+ $records = [];
+
+ foreach ($rows as $row) {
+ $records[] = Source::getInstance($row->xref, $tree, $row->gedcom);
+ }
+
+ return $records;
}
}