xref: /webtrees/app/Module/SiteMapModule.php (revision b1f1e4ef9803ff38b1a05784d23abe6119b8d697)
18c2e8227SGreg Roach<?php
28c2e8227SGreg Roach/**
38c2e8227SGreg Roach * webtrees: online genealogy
46bdf7674SGreg Roach * Copyright (C) 2017 webtrees development team
58c2e8227SGreg Roach * This program is free software: you can redistribute it and/or modify
68c2e8227SGreg Roach * it under the terms of the GNU General Public License as published by
78c2e8227SGreg Roach * the Free Software Foundation, either version 3 of the License, or
88c2e8227SGreg Roach * (at your option) any later version.
98c2e8227SGreg Roach * This program is distributed in the hope that it will be useful,
108c2e8227SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of
118c2e8227SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
128c2e8227SGreg Roach * GNU General Public License for more details.
138c2e8227SGreg Roach * You should have received a copy of the GNU General Public License
148c2e8227SGreg Roach * along with this program. If not, see <http://www.gnu.org/licenses/>.
158c2e8227SGreg Roach */
1676692c8bSGreg Roachnamespace Fisharebest\Webtrees\Module;
1776692c8bSGreg Roach
180e62c4b8SGreg Roachuse Fisharebest\Webtrees\Auth;
1915d603e7SGreg Roachuse Fisharebest\Webtrees\Bootstrap4;
200e62c4b8SGreg Roachuse Fisharebest\Webtrees\Controller\PageController;
210e62c4b8SGreg Roachuse Fisharebest\Webtrees\Database;
220e62c4b8SGreg Roachuse Fisharebest\Webtrees\Filter;
23b1b85189SGreg Roachuse Fisharebest\Webtrees\Html;
240e62c4b8SGreg Roachuse Fisharebest\Webtrees\I18N;
250e62c4b8SGreg Roachuse Fisharebest\Webtrees\Individual;
260e62c4b8SGreg Roachuse Fisharebest\Webtrees\Media;
270e62c4b8SGreg Roachuse Fisharebest\Webtrees\Note;
280e62c4b8SGreg Roachuse Fisharebest\Webtrees\Repository;
290e62c4b8SGreg Roachuse Fisharebest\Webtrees\Source;
300e62c4b8SGreg Roachuse Fisharebest\Webtrees\Tree;
318c2e8227SGreg Roach
328c2e8227SGreg Roach/**
338c2e8227SGreg Roach * Class SiteMapModule
348c2e8227SGreg Roach */
35e2a378d3SGreg Roachclass SiteMapModule extends AbstractModule implements ModuleConfigInterface {
368c2e8227SGreg Roach	const RECORDS_PER_VOLUME = 500; // Keep sitemap files small, for memory, CPU and max_allowed_packet limits.
378c2e8227SGreg Roach	const CACHE_LIFE         = 1209600; // Two weeks
388c2e8227SGreg Roach
398c2e8227SGreg Roach	/** {@inheritdoc} */
408c2e8227SGreg Roach	public function getTitle() {
418c2e8227SGreg Roach		return /* I18N: Name of a module - see http://en.wikipedia.org/wiki/Sitemaps */ I18N::translate('Sitemaps');
428c2e8227SGreg Roach	}
438c2e8227SGreg Roach
448c2e8227SGreg Roach	/** {@inheritdoc} */
458c2e8227SGreg Roach	public function getDescription() {
468c2e8227SGreg Roach		return /* I18N: Description of the “Sitemaps” module */ I18N::translate('Generate sitemap files for search engines.');
478c2e8227SGreg Roach	}
488c2e8227SGreg Roach
4976692c8bSGreg Roach	/**
5076692c8bSGreg Roach	 * This is a general purpose hook, allowing modules to respond to routes
5176692c8bSGreg Roach	 * of the form module.php?mod=FOO&mod_action=BAR
5276692c8bSGreg Roach	 *
5376692c8bSGreg Roach	 * @param string $mod_action
5476692c8bSGreg Roach	 */
558c2e8227SGreg Roach	public function modAction($mod_action) {
568c2e8227SGreg Roach		switch ($mod_action) {
578c2e8227SGreg Roach			case 'admin':
588c2e8227SGreg Roach				$this->admin();
598c2e8227SGreg Roach				break;
608c2e8227SGreg Roach			case 'generate':
618c2e8227SGreg Roach				$this->generate(Filter::get('file'));
628c2e8227SGreg Roach				break;
638c2e8227SGreg Roach			default:
648c2e8227SGreg Roach				http_response_code(404);
658c2e8227SGreg Roach		}
668c2e8227SGreg Roach	}
678c2e8227SGreg Roach
688c2e8227SGreg Roach	/**
6976692c8bSGreg Roach	 * Generate an XML file.
7076692c8bSGreg Roach	 *
718c2e8227SGreg Roach	 * @param string $file
728c2e8227SGreg Roach	 */
738c2e8227SGreg Roach	private function generate($file) {
748c2e8227SGreg Roach		if ($file == 'sitemap.xml') {
758c2e8227SGreg Roach			$this->generateIndex();
768c2e8227SGreg Roach		} elseif (preg_match('/^sitemap-(\d+)-([isrmn])-(\d+).xml$/', $file, $match)) {
778c2e8227SGreg Roach			$this->generateFile($match[1], $match[2], $match[3]);
788c2e8227SGreg Roach		} else {
798c2e8227SGreg Roach			http_response_code(404);
808c2e8227SGreg Roach		}
818c2e8227SGreg Roach	}
828c2e8227SGreg Roach
838c2e8227SGreg Roach	/**
848c2e8227SGreg Roach	 * The index file contains references to all the other files.
858c2e8227SGreg Roach	 * These files are the same for visitors/users/admins.
868c2e8227SGreg Roach	 */
878c2e8227SGreg Roach	private function generateIndex() {
888c2e8227SGreg Roach		// Check the cache
8915d603e7SGreg Roach		$timestamp = (int) $this->getPreference('sitemap.timestamp');
908c2e8227SGreg Roach		if ($timestamp > WT_TIMESTAMP - self::CACHE_LIFE) {
9115d603e7SGreg Roach			$data = $this->getPreference('sitemap.xml');
928c2e8227SGreg Roach		} else {
938c2e8227SGreg Roach			$data    = '';
948c2e8227SGreg Roach			$lastmod = '<lastmod>' . date('Y-m-d') . '</lastmod>';
958c2e8227SGreg Roach			foreach (Tree::getAll() as $tree) {
968c2e8227SGreg Roach				if ($tree->getPreference('include_in_sitemap')) {
978c2e8227SGreg Roach					$n = Database::prepare(
988c2e8227SGreg Roach						"SELECT COUNT(*) FROM `##individuals` WHERE i_file = :tree_id"
9913abd6f3SGreg Roach					)->execute(['tree_id' => $tree->getTreeId()])->fetchOne();
1008c2e8227SGreg Roach					for ($i = 0; $i <= $n / self::RECORDS_PER_VOLUME; ++$i) {
1018c2e8227SGreg Roach						$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;
1028c2e8227SGreg Roach					}
1038c2e8227SGreg Roach					$n = Database::prepare(
1048c2e8227SGreg Roach						"SELECT COUNT(*) FROM `##sources` WHERE s_file = :tree_id"
10513abd6f3SGreg Roach					)->execute(['tree_id' => $tree->getTreeId()])->fetchOne();
1068c2e8227SGreg Roach					if ($n) {
1078c2e8227SGreg Roach						for ($i = 0; $i <= $n / self::RECORDS_PER_VOLUME; ++$i) {
1088c2e8227SGreg Roach							$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;
1098c2e8227SGreg Roach						}
1108c2e8227SGreg Roach					}
1118c2e8227SGreg Roach					$n = Database::prepare(
1128c2e8227SGreg Roach						"SELECT COUNT(*) FROM `##other` WHERE o_file = :tree_id AND o_type = 'REPO'"
11313abd6f3SGreg Roach					)->execute(['tree_id' => $tree->getTreeId()])->fetchOne();
1148c2e8227SGreg Roach					if ($n) {
1158c2e8227SGreg Roach						for ($i = 0; $i <= $n / self::RECORDS_PER_VOLUME; ++$i) {
1168c2e8227SGreg Roach							$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;
1178c2e8227SGreg Roach						}
1188c2e8227SGreg Roach					}
1198c2e8227SGreg Roach					$n = Database::prepare(
1208c2e8227SGreg Roach						"SELECT COUNT(*) FROM `##other` WHERE o_file = :tree_id AND o_type = 'NOTE'"
12113abd6f3SGreg Roach					)->execute(['tree_id' => $tree->getTreeId()])->fetchOne();
1228c2e8227SGreg Roach					if ($n) {
1238c2e8227SGreg Roach						for ($i = 0; $i <= $n / self::RECORDS_PER_VOLUME; ++$i) {
1248c2e8227SGreg Roach							$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;
1258c2e8227SGreg Roach						}
1268c2e8227SGreg Roach					}
1278c2e8227SGreg Roach					$n = Database::prepare(
1288c2e8227SGreg Roach						"SELECT COUNT(*) FROM `##media` WHERE m_file = :tree_id"
12913abd6f3SGreg Roach					)->execute(['tree_id' => $tree->getTreeId()])->fetchOne();
1308c2e8227SGreg Roach					if ($n) {
1318c2e8227SGreg Roach						for ($i = 0; $i <= $n / self::RECORDS_PER_VOLUME; ++$i) {
1328c2e8227SGreg Roach							$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;
1338c2e8227SGreg Roach						}
1348c2e8227SGreg Roach					}
1358c2e8227SGreg Roach				}
1368c2e8227SGreg Roach			}
1378c2e8227SGreg Roach			$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;
1388c2e8227SGreg Roach			// Cache this data.
13915d603e7SGreg Roach			$this->setPreference('sitemap.xml', $data);
14015d603e7SGreg Roach			$this->setPreference('sitemap.timestamp', WT_TIMESTAMP);
1418c2e8227SGreg Roach		}
1428c2e8227SGreg Roach		header('Content-Type: application/xml');
1438c2e8227SGreg Roach		header('Content-Length: ' . strlen($data));
1448c2e8227SGreg Roach		echo $data;
1458c2e8227SGreg Roach	}
1468c2e8227SGreg Roach
1478c2e8227SGreg Roach	/**
1488c2e8227SGreg Roach	 * A separate file for each family tree and each record type.
1498c2e8227SGreg Roach	 * These files depend on access levels, so only cache for visitors.
1508c2e8227SGreg Roach	 *
151cbc1590aSGreg Roach	 * @param int    $ged_id
1528c2e8227SGreg Roach	 * @param string $rec_type
1538c2e8227SGreg Roach	 * @param string $volume
1548c2e8227SGreg Roach	 */
1558c2e8227SGreg Roach	private function generateFile($ged_id, $rec_type, $volume) {
15624ec66ceSGreg Roach		$tree = Tree::findById($ged_id);
1578c2e8227SGreg Roach		// Check the cache
15815d603e7SGreg Roach		$timestamp = (int) $this->getPreference('sitemap-' . $ged_id . '-' . $rec_type . '-' . $volume . '.timestamp');
1598c2e8227SGreg Roach		if ($timestamp > WT_TIMESTAMP - self::CACHE_LIFE && !Auth::check()) {
16015d603e7SGreg Roach			$data = $this->getPreference('sitemap-' . $ged_id . '-' . $rec_type . '-' . $volume . '.xml');
1618c2e8227SGreg Roach		} else {
1628c2e8227SGreg Roach			$data    = '<url><loc>' . WT_BASE_URL . 'index.php?ctype=gedcom&amp;ged=' . $tree->getNameUrl() . '</loc></url>' . PHP_EOL;
16313abd6f3SGreg Roach			$records = [];
1648c2e8227SGreg Roach			switch ($rec_type) {
1658c2e8227SGreg Roach				case 'i':
1668c2e8227SGreg Roach					$rows = Database::prepare(
16724ec66ceSGreg Roach						"SELECT i_id AS xref, i_gedcom AS gedcom" .
1688c2e8227SGreg Roach						" FROM `##individuals`" .
1698c2e8227SGreg Roach						" WHERE i_file = :tree_id" .
1708c2e8227SGreg Roach						" ORDER BY i_id" .
1718c2e8227SGreg Roach						" LIMIT :limit OFFSET :offset"
17213abd6f3SGreg Roach					)->execute([
1738c2e8227SGreg Roach						'tree_id' => $ged_id,
1748c2e8227SGreg Roach						'limit'   => self::RECORDS_PER_VOLUME,
1758c2e8227SGreg Roach						'offset'  => self::RECORDS_PER_VOLUME * $volume,
17613abd6f3SGreg Roach					])->fetchAll();
1778c2e8227SGreg Roach					foreach ($rows as $row) {
17824ec66ceSGreg Roach						$records[] = Individual::getInstance($row->xref, $tree, $row->gedcom);
1798c2e8227SGreg Roach					}
1808c2e8227SGreg Roach					break;
1818c2e8227SGreg Roach				case 's':
1828c2e8227SGreg Roach					$rows = Database::prepare(
18324ec66ceSGreg Roach						"SELECT s_id AS xref, s_gedcom AS gedcom" .
1848c2e8227SGreg Roach						" FROM `##sources`" .
1858c2e8227SGreg Roach						" WHERE s_file = :tree_id" .
1868c2e8227SGreg Roach						" ORDER BY s_id" .
1878c2e8227SGreg Roach						" LIMIT :limit OFFSET :offset"
18813abd6f3SGreg Roach					)->execute([
1898c2e8227SGreg Roach						'tree_id' => $ged_id,
1908c2e8227SGreg Roach						'limit'   => self::RECORDS_PER_VOLUME,
1918c2e8227SGreg Roach						'offset'  => self::RECORDS_PER_VOLUME * $volume,
19213abd6f3SGreg Roach					])->fetchAll();
1938c2e8227SGreg Roach					foreach ($rows as $row) {
19424ec66ceSGreg Roach						$records[] = Source::getInstance($row->xref, $tree, $row->gedcom);
1958c2e8227SGreg Roach					}
1968c2e8227SGreg Roach					break;
1978c2e8227SGreg Roach				case 'r':
1988c2e8227SGreg Roach					$rows = Database::prepare(
19924ec66ceSGreg Roach						"SELECT o_id AS xref, o_gedcom AS gedcom" .
2008c2e8227SGreg Roach						" FROM `##other`" .
2018c2e8227SGreg Roach						" WHERE o_file = :tree_id AND o_type = 'REPO'" .
2028c2e8227SGreg Roach						" ORDER BY o_id" .
2038c2e8227SGreg Roach						" LIMIT :limit OFFSET :offset"
20413abd6f3SGreg Roach					)->execute([
2058c2e8227SGreg Roach						'tree_id' => $ged_id,
2068c2e8227SGreg Roach						'limit'   => self::RECORDS_PER_VOLUME,
2078c2e8227SGreg Roach						'offset'  => self::RECORDS_PER_VOLUME * $volume,
20813abd6f3SGreg Roach					])->fetchAll();
2098c2e8227SGreg Roach					foreach ($rows as $row) {
21024ec66ceSGreg Roach						$records[] = Repository::getInstance($row->xref, $tree, $row->gedcom);
2118c2e8227SGreg Roach					}
2128c2e8227SGreg Roach					break;
2138c2e8227SGreg Roach				case 'n':
2148c2e8227SGreg Roach					$rows = Database::prepare(
21524ec66ceSGreg Roach						"SELECT o_id AS xref, o_gedcom AS gedcom" .
2168c2e8227SGreg Roach						" FROM `##other`" .
2178c2e8227SGreg Roach						" WHERE o_file = :tree_id AND o_type = 'NOTE'" .
2188c2e8227SGreg Roach						" ORDER BY o_id" .
2198c2e8227SGreg Roach						" LIMIT :limit OFFSET :offset"
22013abd6f3SGreg Roach					)->execute([
2218c2e8227SGreg Roach						'tree_id' => $ged_id,
2228c2e8227SGreg Roach						'limit'   => self::RECORDS_PER_VOLUME,
2238c2e8227SGreg Roach						'offset'  => self::RECORDS_PER_VOLUME * $volume,
22413abd6f3SGreg Roach					])->fetchAll();
2258c2e8227SGreg Roach					foreach ($rows as $row) {
22624ec66ceSGreg Roach						$records[] = Note::getInstance($row->xref, $tree, $row->gedcom);
2278c2e8227SGreg Roach					}
2288c2e8227SGreg Roach					break;
2298c2e8227SGreg Roach				case 'm':
2308c2e8227SGreg Roach					$rows = Database::prepare(
23124ec66ceSGreg Roach						"SELECT m_id AS xref, m_gedcom AS gedcom" .
2328c2e8227SGreg Roach						" FROM `##media`" .
2338c2e8227SGreg Roach						" WHERE m_file = :tree_id" .
2348c2e8227SGreg Roach						" ORDER BY m_id" .
2358c2e8227SGreg Roach						" LIMIT :limit OFFSET :offset"
23613abd6f3SGreg Roach					)->execute([
2378c2e8227SGreg Roach						'tree_id' => $ged_id,
2388c2e8227SGreg Roach						'limit'   => self::RECORDS_PER_VOLUME,
2398c2e8227SGreg Roach						'offset'  => self::RECORDS_PER_VOLUME * $volume,
24013abd6f3SGreg Roach					])->fetchAll();
2418c2e8227SGreg Roach					foreach ($rows as $row) {
24224ec66ceSGreg Roach						$records[] = Media::getInstance($row->xref, $tree, $row->gedcom);
2438c2e8227SGreg Roach					}
2448c2e8227SGreg Roach					break;
2458c2e8227SGreg Roach			}
2468c2e8227SGreg Roach			foreach ($records as $record) {
2478c2e8227SGreg Roach				if ($record->canShowName()) {
2488c2e8227SGreg Roach					$data .= '<url>';
249*b1f1e4efSGreg Roach					$data .= '<loc>' . WT_BASE_URL . e($record->url()) . '</loc>';
2508c2e8227SGreg Roach					$chan = $record->getFirstFact('CHAN');
2518c2e8227SGreg Roach					if ($chan) {
2528c2e8227SGreg Roach						$date = $chan->getDate();
2538c2e8227SGreg Roach						if ($date->isOK()) {
2542a05b1e4SGreg Roach							$data .= '<lastmod>' . $date->minimumDate()->Format('%Y-%m-%d') . '</lastmod>';
2558c2e8227SGreg Roach						}
2568c2e8227SGreg Roach					}
2578c2e8227SGreg Roach					$data .= '</url>' . PHP_EOL;
2588c2e8227SGreg Roach				}
2598c2e8227SGreg Roach			}
2608c2e8227SGreg Roach			$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;
2618c2e8227SGreg Roach			// Cache this data - but only for visitors, as we don’t want
262cdc90107SGreg Roach			// visitors to see data created by signed-in users.
2638c2e8227SGreg Roach			if (!Auth::check()) {
26415d603e7SGreg Roach				$this->setPreference('sitemap-' . $ged_id . '-' . $rec_type . '-' . $volume . '.xml', $data);
26515d603e7SGreg Roach				$this->setPreference('sitemap-' . $ged_id . '-' . $rec_type . '-' . $volume . '.timestamp', WT_TIMESTAMP);
2668c2e8227SGreg Roach			}
2678c2e8227SGreg Roach		}
2688c2e8227SGreg Roach		header('Content-Type: application/xml');
2698c2e8227SGreg Roach		header('Content-Length: ' . strlen($data));
2708c2e8227SGreg Roach		echo $data;
2718c2e8227SGreg Roach	}
2728c2e8227SGreg Roach
2738c2e8227SGreg Roach	/**
2748c2e8227SGreg Roach	 * Edit the configuration
2758c2e8227SGreg Roach	 */
2768c2e8227SGreg Roach	private function admin() {
2778c2e8227SGreg Roach		$controller = new PageController;
2788c2e8227SGreg Roach		$controller
2798c2e8227SGreg Roach			->restrictAccess(Auth::isAdmin())
2808c2e8227SGreg Roach			->setPageTitle($this->getTitle())
2818c2e8227SGreg Roach			->pageHeader();
2828c2e8227SGreg Roach
2838c2e8227SGreg Roach		// Save the updated preferences
2848c2e8227SGreg Roach		if (Filter::post('action') == 'save') {
2858c2e8227SGreg Roach			foreach (Tree::getAll() as $tree) {
2868c2e8227SGreg Roach				$tree->setPreference('include_in_sitemap', Filter::postBool('include' . $tree->getTreeId()));
2878c2e8227SGreg Roach			}
2888c2e8227SGreg Roach			// Clear cache and force files to be regenerated
2898c2e8227SGreg Roach			Database::prepare(
2908c2e8227SGreg Roach				"DELETE FROM `##module_setting` WHERE setting_name LIKE 'sitemap%'"
2918c2e8227SGreg Roach			)->execute();
2928c2e8227SGreg Roach		}
2938c2e8227SGreg Roach
2948c2e8227SGreg Roach		$include_any = false;
2958c2e8227SGreg Roach
29615d603e7SGreg Roach		echo Bootstrap4::breadcrumbs([
2971f3fb95cSGreg Roach			route('admin-control-panel') => I18N::translate('Control panel'),
2981f3fb95cSGreg Roach			route('admin-modules')       => I18N::translate('Module administration'),
29915d603e7SGreg Roach		], $controller->getPageTitle());
3008c2e8227SGreg Roach		?>
30115d603e7SGreg Roach
30215d603e7SGreg Roach		<h1><?= $controller->getPageTitle() ?></h1>
3038c2e8227SGreg Roach		<?php
3048c2e8227SGreg Roach
3058c2e8227SGreg Roach		echo
3068c2e8227SGreg Roach		'<p>',
3078c2e8227SGreg Roach			/* I18N: The www.sitemaps.org site is translated into many languages (e.g. http://www.sitemaps.org/fr/) - choose an appropriate URL. */
3088c2e8227SGreg Roach			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>.') .
3098c2e8227SGreg Roach			'</p>',
310a4d5a9c2SGreg Roach		'<p>', /* I18N: Label for a configuration option */ I18N::translate('Which family trees should be included in the sitemaps'), '</p>',
3118c2e8227SGreg Roach			'<form method="post" action="module.php?mod=' . $this->getName() . '&amp;mod_action=admin">',
3128c2e8227SGreg Roach		'<input type="hidden" name="action" value="save">';
3138c2e8227SGreg Roach		foreach (Tree::getAll() as $tree) {
314fff20713SGreg Roach			echo '<div class="form-check"><label><input type="checkbox" name="include', $tree->getTreeId(), '" ';
3158c2e8227SGreg Roach			if ($tree->getPreference('include_in_sitemap')) {
3168c2e8227SGreg Roach				echo 'checked';
3178c2e8227SGreg Roach				$include_any = true;
3188c2e8227SGreg Roach			}
319adffb21dSGreg Roach			echo '>', $tree->getTitleHtml(), '</label></div>';
3208c2e8227SGreg Roach		}
3218c2e8227SGreg Roach		echo
3228c2e8227SGreg Roach		'<input type="submit" value="', I18N::translate('save'), '">',
3238c2e8227SGreg Roach		'</form>',
3248c2e8227SGreg Roach		'<hr>';
3258c2e8227SGreg Roach
3268c2e8227SGreg Roach		if ($include_any) {
3278c2e8227SGreg Roach			$site_map_url1 = WT_BASE_URL . 'module.php?mod=' . $this->getName() . '&amp;mod_action=generate&amp;file=sitemap.xml';
3288c2e8227SGreg Roach			$site_map_url2 = rawurlencode(WT_BASE_URL . 'module.php?mod=' . $this->getName() . '&mod_action=generate&file=sitemap.xml');
3298c2e8227SGreg Roach			echo
3308c2e8227SGreg Roach				'<p>', I18N::translate('To tell search engines that sitemaps are available, you should add the following line to your robots.txt file.'), '</p>',
3318c2e8227SGreg Roach				'<pre>Sitemap: ', $site_map_url1, '</pre>',
3328c2e8227SGreg Roach				'<hr>',
3338c2e8227SGreg Roach				'<p>', I18N::translate('To tell search engines that sitemaps are available, you can use the following links.'), '</p>',
3348c2e8227SGreg Roach				'<ul>',
3358c2e8227SGreg Roach				// This list comes from http://en.wikipedia.org/wiki/Sitemaps
3362032bcddSRobert Scheck				'<li><a href="https://www.bing.com/webmaster/ping.aspx?siteMap=' . $site_map_url2 . '">Bing</a></li>',
3372032bcddSRobert Scheck				'<li><a href="https://www.google.com/webmasters/tools/ping?sitemap=' . $site_map_url2 . '">Google</a></li>',
3388c2e8227SGreg Roach				'</ul>';
3398c2e8227SGreg Roach		}
3408c2e8227SGreg Roach	}
3418c2e8227SGreg Roach
3428c2e8227SGreg Roach	/** {@inheritdoc} */
3438c2e8227SGreg Roach	public function getConfigLink() {
344b1b85189SGreg Roach		return Html::url('module.php', [
345b1b85189SGreg Roach			'mod'        => $this->getName(),
346b1b85189SGreg Roach			'mod_action' => 'admin',
347b1b85189SGreg Roach		]);
3488c2e8227SGreg Roach	}
3498c2e8227SGreg Roach}
350