18c2e8227SGreg Roach<?php 28c2e8227SGreg Roachnamespace Fisharebest\Webtrees; 38c2e8227SGreg Roach 48c2e8227SGreg Roach/** 58c2e8227SGreg Roach * webtrees: online genealogy 68c2e8227SGreg Roach * Copyright (C) 2015 webtrees development team 78c2e8227SGreg Roach * This program is free software: you can redistribute it and/or modify 88c2e8227SGreg Roach * it under the terms of the GNU General Public License as published by 98c2e8227SGreg Roach * the Free Software Foundation, either version 3 of the License, or 108c2e8227SGreg Roach * (at your option) any later version. 118c2e8227SGreg Roach * This program is distributed in the hope that it will be useful, 128c2e8227SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of 138c2e8227SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 148c2e8227SGreg Roach * GNU General Public License for more details. 158c2e8227SGreg Roach * You should have received a copy of the GNU General Public License 168c2e8227SGreg Roach * along with this program. If not, see <http://www.gnu.org/licenses/>. 178c2e8227SGreg Roach */ 188c2e8227SGreg Roach 198c2e8227SGreg Roachuse Zend_Session; 208c2e8227SGreg Roach 218c2e8227SGreg Roach/** 228c2e8227SGreg Roach * Class SiteMapModule 238c2e8227SGreg Roach */ 24*e2a378d3SGreg Roachclass SiteMapModule extends AbstractModule implements ModuleConfigInterface { 258c2e8227SGreg Roach const RECORDS_PER_VOLUME = 500; // Keep sitemap files small, for memory, CPU and max_allowed_packet limits. 268c2e8227SGreg Roach const CACHE_LIFE = 1209600; // Two weeks 278c2e8227SGreg Roach 288c2e8227SGreg Roach /** {@inheritdoc} */ 298c2e8227SGreg Roach public function getTitle() { 308c2e8227SGreg Roach return /* I18N: Name of a module - see http://en.wikipedia.org/wiki/Sitemaps */ I18N::translate('Sitemaps'); 318c2e8227SGreg Roach } 328c2e8227SGreg Roach 338c2e8227SGreg Roach /** {@inheritdoc} */ 348c2e8227SGreg Roach public function getDescription() { 358c2e8227SGreg Roach return /* I18N: Description of the “Sitemaps” module */ I18N::translate('Generate sitemap files for search engines.'); 368c2e8227SGreg Roach } 378c2e8227SGreg Roach 388c2e8227SGreg Roach /** {@inheritdoc} */ 398c2e8227SGreg Roach public function modAction($mod_action) { 408c2e8227SGreg Roach switch ($mod_action) { 418c2e8227SGreg Roach case 'admin': 428c2e8227SGreg Roach $this->admin(); 438c2e8227SGreg Roach break; 448c2e8227SGreg Roach case 'generate': 458c2e8227SGreg Roach Zend_Session::writeClose(); 468c2e8227SGreg Roach $this->generate(Filter::get('file')); 478c2e8227SGreg Roach break; 488c2e8227SGreg Roach default: 498c2e8227SGreg Roach http_response_code(404); 508c2e8227SGreg Roach } 518c2e8227SGreg Roach } 528c2e8227SGreg Roach 538c2e8227SGreg Roach /** 548c2e8227SGreg Roach * @param string $file 558c2e8227SGreg Roach */ 568c2e8227SGreg Roach private function generate($file) { 578c2e8227SGreg Roach if ($file == 'sitemap.xml') { 588c2e8227SGreg Roach $this->generateIndex(); 598c2e8227SGreg Roach } elseif (preg_match('/^sitemap-(\d+)-([isrmn])-(\d+).xml$/', $file, $match)) { 608c2e8227SGreg Roach $this->generateFile($match[1], $match[2], $match[3]); 618c2e8227SGreg Roach } else { 628c2e8227SGreg Roach http_response_code(404); 638c2e8227SGreg Roach } 648c2e8227SGreg Roach } 658c2e8227SGreg Roach 668c2e8227SGreg Roach /** 678c2e8227SGreg Roach * The index file contains references to all the other files. 688c2e8227SGreg Roach * These files are the same for visitors/users/admins. 698c2e8227SGreg Roach */ 708c2e8227SGreg Roach private function generateIndex() { 718c2e8227SGreg Roach // Check the cache 728c2e8227SGreg Roach $timestamp = $this->getSetting('sitemap.timestamp'); 738c2e8227SGreg Roach if ($timestamp > WT_TIMESTAMP - self::CACHE_LIFE) { 748c2e8227SGreg Roach $data = $this->getSetting('sitemap.xml'); 758c2e8227SGreg Roach } else { 768c2e8227SGreg Roach $data = ''; 778c2e8227SGreg Roach $lastmod = '<lastmod>' . date('Y-m-d') . '</lastmod>'; 788c2e8227SGreg Roach foreach (Tree::getAll() as $tree) { 798c2e8227SGreg Roach if ($tree->getPreference('include_in_sitemap')) { 808c2e8227SGreg Roach $n = Database::prepare( 818c2e8227SGreg Roach "SELECT COUNT(*) FROM `##individuals` WHERE i_file = :tree_id" 828c2e8227SGreg Roach )->execute(array('tree_id' => $tree->getTreeId()))->fetchOne(); 838c2e8227SGreg Roach for ($i = 0; $i <= $n / self::RECORDS_PER_VOLUME; ++$i) { 848c2e8227SGreg Roach $data .= '<sitemap><loc>' . WT_BASE_URL . 'module.php?mod=' . $this->getName() . '&mod_action=generate&file=sitemap-' . $tree->getTreeId() . '-i-' . $i . '.xml</loc>' . $lastmod . '</sitemap>' . PHP_EOL; 858c2e8227SGreg Roach } 868c2e8227SGreg Roach $n = Database::prepare( 878c2e8227SGreg Roach "SELECT COUNT(*) FROM `##sources` WHERE s_file = :tree_id" 888c2e8227SGreg Roach )->execute(array('tree_id' => $tree->getTreeId()))->fetchOne(); 898c2e8227SGreg Roach if ($n) { 908c2e8227SGreg Roach for ($i = 0; $i <= $n / self::RECORDS_PER_VOLUME; ++$i) { 918c2e8227SGreg Roach $data .= '<sitemap><loc>' . WT_BASE_URL . 'module.php?mod=' . $this->getName() . '&mod_action=generate&file=sitemap-' . $tree->getTreeId() . '-s-' . $i . '.xml</loc>' . $lastmod . '</sitemap>' . PHP_EOL; 928c2e8227SGreg Roach } 938c2e8227SGreg Roach } 948c2e8227SGreg Roach $n = Database::prepare( 958c2e8227SGreg Roach "SELECT COUNT(*) FROM `##other` WHERE o_file = :tree_id AND o_type = 'REPO'" 968c2e8227SGreg Roach )->execute(array('tree_id' => $tree->getTreeId()))->fetchOne(); 978c2e8227SGreg Roach if ($n) { 988c2e8227SGreg Roach for ($i = 0; $i <= $n / self::RECORDS_PER_VOLUME; ++$i) { 998c2e8227SGreg Roach $data .= '<sitemap><loc>' . WT_BASE_URL . 'module.php?mod=' . $this->getName() . '&mod_action=generate&file=sitemap-' . $tree->getTreeId() . '-r-' . $i . '.xml</loc>' . $lastmod . '</sitemap>' . PHP_EOL; 1008c2e8227SGreg Roach } 1018c2e8227SGreg Roach } 1028c2e8227SGreg Roach $n = Database::prepare( 1038c2e8227SGreg Roach "SELECT COUNT(*) FROM `##other` WHERE o_file = :tree_id AND o_type = 'NOTE'" 1048c2e8227SGreg Roach )->execute(array('tree_id' => $tree->getTreeId()))->fetchOne(); 1058c2e8227SGreg Roach if ($n) { 1068c2e8227SGreg Roach for ($i = 0; $i <= $n / self::RECORDS_PER_VOLUME; ++$i) { 1078c2e8227SGreg Roach $data .= '<sitemap><loc>' . WT_BASE_URL . 'module.php?mod=' . $this->getName() . '&mod_action=generate&file=sitemap-' . $tree->getTreeId() . '-n-' . $i . '.xml</loc>' . $lastmod . '</sitemap>' . PHP_EOL; 1088c2e8227SGreg Roach } 1098c2e8227SGreg Roach } 1108c2e8227SGreg Roach $n = Database::prepare( 1118c2e8227SGreg Roach "SELECT COUNT(*) FROM `##media` WHERE m_file = :tree_id" 1128c2e8227SGreg Roach )->execute(array('tree_id' => $tree->getTreeId()))->fetchOne(); 1138c2e8227SGreg Roach if ($n) { 1148c2e8227SGreg Roach for ($i = 0; $i <= $n / self::RECORDS_PER_VOLUME; ++$i) { 1158c2e8227SGreg Roach $data .= '<sitemap><loc>' . WT_BASE_URL . 'module.php?mod=' . $this->getName() . '&mod_action=generate&file=sitemap-' . $tree->getTreeId() . '-m-' . $i . '.xml</loc>' . $lastmod . '</sitemap>' . PHP_EOL; 1168c2e8227SGreg Roach } 1178c2e8227SGreg Roach } 1188c2e8227SGreg Roach } 1198c2e8227SGreg Roach } 1208c2e8227SGreg 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; 1218c2e8227SGreg Roach // Cache this data. 1228c2e8227SGreg Roach $this->setSetting('sitemap.xml', $data); 1238c2e8227SGreg Roach $this->setSetting('sitemap.timestamp', WT_TIMESTAMP); 1248c2e8227SGreg Roach } 1258c2e8227SGreg Roach header('Content-Type: application/xml'); 1268c2e8227SGreg Roach header('Content-Length: ' . strlen($data)); 1278c2e8227SGreg Roach echo $data; 1288c2e8227SGreg Roach } 1298c2e8227SGreg Roach 1308c2e8227SGreg Roach /** 1318c2e8227SGreg Roach * A separate file for each family tree and each record type. 1328c2e8227SGreg Roach * These files depend on access levels, so only cache for visitors. 1338c2e8227SGreg Roach * 1348c2e8227SGreg Roach * @param integer $ged_id 1358c2e8227SGreg Roach * @param string $rec_type 1368c2e8227SGreg Roach * @param string $volume 1378c2e8227SGreg Roach */ 1388c2e8227SGreg Roach private function generateFile($ged_id, $rec_type, $volume) { 13924ec66ceSGreg Roach $tree = Tree::findById($ged_id); 1408c2e8227SGreg Roach // Check the cache 1418c2e8227SGreg Roach $timestamp = $this->getSetting('sitemap-' . $ged_id . '-' . $rec_type . '-' . $volume . '.timestamp'); 1428c2e8227SGreg Roach if ($timestamp > WT_TIMESTAMP - self::CACHE_LIFE && !Auth::check()) { 1438c2e8227SGreg Roach $data = $this->getSetting('sitemap-' . $ged_id . '-' . $rec_type . '-' . $volume . '.xml'); 1448c2e8227SGreg Roach } else { 1458c2e8227SGreg Roach $data = '<url><loc>' . WT_BASE_URL . 'index.php?ctype=gedcom&ged=' . $tree->getNameUrl() . '</loc></url>' . PHP_EOL; 1468c2e8227SGreg Roach $records = array(); 1478c2e8227SGreg Roach switch ($rec_type) { 1488c2e8227SGreg Roach case 'i': 1498c2e8227SGreg Roach $rows = Database::prepare( 15024ec66ceSGreg Roach "SELECT i_id AS xref, i_gedcom AS gedcom" . 1518c2e8227SGreg Roach " FROM `##individuals`" . 1528c2e8227SGreg Roach " WHERE i_file = :tree_id" . 1538c2e8227SGreg Roach " ORDER BY i_id" . 1548c2e8227SGreg Roach " LIMIT :limit OFFSET :offset" 1558c2e8227SGreg Roach )->execute(array( 1568c2e8227SGreg Roach 'tree_id' => $ged_id, 1578c2e8227SGreg Roach 'limit' => self::RECORDS_PER_VOLUME, 1588c2e8227SGreg Roach 'offset' => self::RECORDS_PER_VOLUME * $volume, 1598c2e8227SGreg Roach ))->fetchAll(); 1608c2e8227SGreg Roach foreach ($rows as $row) { 16124ec66ceSGreg Roach $records[] = Individual::getInstance($row->xref, $tree, $row->gedcom); 1628c2e8227SGreg Roach } 1638c2e8227SGreg Roach break; 1648c2e8227SGreg Roach case 's': 1658c2e8227SGreg Roach $rows = Database::prepare( 16624ec66ceSGreg Roach "SELECT s_id AS xref, s_gedcom AS gedcom" . 1678c2e8227SGreg Roach " FROM `##sources`" . 1688c2e8227SGreg Roach " WHERE s_file = :tree_id" . 1698c2e8227SGreg Roach " ORDER BY s_id" . 1708c2e8227SGreg Roach " LIMIT :limit OFFSET :offset" 1718c2e8227SGreg Roach )->execute(array( 1728c2e8227SGreg Roach 'tree_id' => $ged_id, 1738c2e8227SGreg Roach 'limit' => self::RECORDS_PER_VOLUME, 1748c2e8227SGreg Roach 'offset' => self::RECORDS_PER_VOLUME * $volume, 1758c2e8227SGreg Roach ))->fetchAll(); 1768c2e8227SGreg Roach foreach ($rows as $row) { 17724ec66ceSGreg Roach $records[] = Source::getInstance($row->xref, $tree, $row->gedcom); 1788c2e8227SGreg Roach } 1798c2e8227SGreg Roach break; 1808c2e8227SGreg Roach case 'r': 1818c2e8227SGreg Roach $rows = Database::prepare( 18224ec66ceSGreg Roach "SELECT o_id AS xref, o_gedcom AS gedcom" . 1838c2e8227SGreg Roach " FROM `##other`" . 1848c2e8227SGreg Roach " WHERE o_file = :tree_id AND o_type = 'REPO'" . 1858c2e8227SGreg Roach " ORDER BY o_id" . 1868c2e8227SGreg Roach " LIMIT :limit OFFSET :offset" 1878c2e8227SGreg Roach )->execute(array( 1888c2e8227SGreg Roach 'tree_id' => $ged_id, 1898c2e8227SGreg Roach 'limit' => self::RECORDS_PER_VOLUME, 1908c2e8227SGreg Roach 'offset' => self::RECORDS_PER_VOLUME * $volume, 1918c2e8227SGreg Roach ))->fetchAll(); 1928c2e8227SGreg Roach foreach ($rows as $row) { 19324ec66ceSGreg Roach $records[] = Repository::getInstance($row->xref, $tree, $row->gedcom); 1948c2e8227SGreg Roach } 1958c2e8227SGreg Roach break; 1968c2e8227SGreg Roach case 'n': 1978c2e8227SGreg Roach $rows = Database::prepare( 19824ec66ceSGreg Roach "SELECT o_id AS xref, o_gedcom AS gedcom" . 1998c2e8227SGreg Roach " FROM `##other`" . 2008c2e8227SGreg Roach " WHERE o_file = :tree_id AND o_type = 'NOTE'" . 2018c2e8227SGreg Roach " ORDER BY o_id" . 2028c2e8227SGreg Roach " LIMIT :limit OFFSET :offset" 2038c2e8227SGreg Roach )->execute(array( 2048c2e8227SGreg Roach 'tree_id' => $ged_id, 2058c2e8227SGreg Roach 'limit' => self::RECORDS_PER_VOLUME, 2068c2e8227SGreg Roach 'offset' => self::RECORDS_PER_VOLUME * $volume, 2078c2e8227SGreg Roach ))->fetchAll(); 2088c2e8227SGreg Roach foreach ($rows as $row) { 20924ec66ceSGreg Roach $records[] = Note::getInstance($row->xref, $tree, $row->gedcom); 2108c2e8227SGreg Roach } 2118c2e8227SGreg Roach break; 2128c2e8227SGreg Roach case 'm': 2138c2e8227SGreg Roach $rows = Database::prepare( 21424ec66ceSGreg Roach "SELECT m_id AS xref, m_gedcom AS gedcom" . 2158c2e8227SGreg Roach " FROM `##media`" . 2168c2e8227SGreg Roach " WHERE m_file = :tree_id" . 2178c2e8227SGreg Roach " ORDER BY m_id" . 2188c2e8227SGreg Roach " LIMIT :limit OFFSET :offset" 2198c2e8227SGreg Roach )->execute(array( 2208c2e8227SGreg Roach 'tree_id' => $ged_id, 2218c2e8227SGreg Roach 'limit' => self::RECORDS_PER_VOLUME, 2228c2e8227SGreg Roach 'offset' => self::RECORDS_PER_VOLUME * $volume, 2238c2e8227SGreg Roach ))->fetchAll(); 2248c2e8227SGreg Roach foreach ($rows as $row) { 22524ec66ceSGreg Roach $records[] = Media::getInstance($row->xref, $tree, $row->gedcom); 2268c2e8227SGreg Roach } 2278c2e8227SGreg Roach break; 2288c2e8227SGreg Roach } 2298c2e8227SGreg Roach foreach ($records as $record) { 2308c2e8227SGreg Roach if ($record->canShowName()) { 2318c2e8227SGreg Roach $data .= '<url>'; 2328c2e8227SGreg Roach $data .= '<loc>' . WT_BASE_URL . $record->getHtmlUrl() . '</loc>'; 2338c2e8227SGreg Roach $chan = $record->getFirstFact('CHAN'); 2348c2e8227SGreg Roach if ($chan) { 2358c2e8227SGreg Roach $date = $chan->getDate(); 2368c2e8227SGreg Roach if ($date->isOK()) { 2372a05b1e4SGreg Roach $data .= '<lastmod>' . $date->minimumDate()->Format('%Y-%m-%d') . '</lastmod>'; 2388c2e8227SGreg Roach } 2398c2e8227SGreg Roach } 2408c2e8227SGreg Roach $data .= '</url>' . PHP_EOL; 2418c2e8227SGreg Roach } 2428c2e8227SGreg Roach } 2438c2e8227SGreg 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; 2448c2e8227SGreg Roach // Cache this data - but only for visitors, as we don’t want 2458c2e8227SGreg Roach // visitors to see data created by logged-in users. 2468c2e8227SGreg Roach if (!Auth::check()) { 2478c2e8227SGreg Roach $this->setSetting('sitemap-' . $ged_id . '-' . $rec_type . '-' . $volume . '.xml', $data); 2488c2e8227SGreg Roach $this->setSetting('sitemap-' . $ged_id . '-' . $rec_type . '-' . $volume . '.timestamp', WT_TIMESTAMP); 2498c2e8227SGreg Roach } 2508c2e8227SGreg Roach } 2518c2e8227SGreg Roach header('Content-Type: application/xml'); 2528c2e8227SGreg Roach header('Content-Length: ' . strlen($data)); 2538c2e8227SGreg Roach echo $data; 2548c2e8227SGreg Roach } 2558c2e8227SGreg Roach 2568c2e8227SGreg Roach /** 2578c2e8227SGreg Roach * Edit the configuration 2588c2e8227SGreg Roach */ 2598c2e8227SGreg Roach private function admin() { 2608c2e8227SGreg Roach $controller = new PageController; 2618c2e8227SGreg Roach $controller 2628c2e8227SGreg Roach ->restrictAccess(Auth::isAdmin()) 2638c2e8227SGreg Roach ->setPageTitle($this->getTitle()) 2648c2e8227SGreg Roach ->pageHeader(); 2658c2e8227SGreg Roach 2668c2e8227SGreg Roach // Save the updated preferences 2678c2e8227SGreg Roach if (Filter::post('action') == 'save') { 2688c2e8227SGreg Roach foreach (Tree::getAll() as $tree) { 2698c2e8227SGreg Roach $tree->setPreference('include_in_sitemap', Filter::postBool('include' . $tree->getTreeId())); 2708c2e8227SGreg Roach } 2718c2e8227SGreg Roach // Clear cache and force files to be regenerated 2728c2e8227SGreg Roach Database::prepare( 2738c2e8227SGreg Roach "DELETE FROM `##module_setting` WHERE setting_name LIKE 'sitemap%'" 2748c2e8227SGreg Roach )->execute(); 2758c2e8227SGreg Roach } 2768c2e8227SGreg Roach 2778c2e8227SGreg Roach $include_any = false; 2788c2e8227SGreg Roach 2798c2e8227SGreg Roach ?> 2808c2e8227SGreg Roach <ol class="breadcrumb small"> 2818c2e8227SGreg Roach <li><a href="admin.php"><?php echo I18N::translate('Control panel'); ?></a></li> 2828c2e8227SGreg Roach <li><a href="admin_modules.php"><?php echo I18N::translate('Module administration'); ?></a></li> 2838c2e8227SGreg Roach <li class="active"><?php echo $controller->getPageTitle(); ?></li> 2848c2e8227SGreg Roach </ol> 2858c2e8227SGreg Roach <h2><?php echo $controller->getPageTitle(); ?></h2> 2868c2e8227SGreg Roach <?php 2878c2e8227SGreg Roach 2888c2e8227SGreg Roach echo 2898c2e8227SGreg Roach '<p>', 2908c2e8227SGreg Roach /* I18N: The www.sitemaps.org site is translated into many languages (e.g. http://www.sitemaps.org/fr/) - choose an appropriate URL. */ 2918c2e8227SGreg 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>.') . 2928c2e8227SGreg Roach '</p>', 2938c2e8227SGreg Roach '<p>', I18N::translate('Which family trees should be included in the sitemaps?'), '</p>', 2948c2e8227SGreg Roach '<form method="post" action="module.php?mod=' . $this->getName() . '&mod_action=admin">', 2958c2e8227SGreg Roach '<input type="hidden" name="action" value="save">'; 2968c2e8227SGreg Roach foreach (Tree::getAll() as $tree) { 2978c2e8227SGreg Roach echo '<p><input type="checkbox" name="include', $tree->getTreeId(), '" '; 2988c2e8227SGreg Roach if ($tree->getPreference('include_in_sitemap')) { 2998c2e8227SGreg Roach echo 'checked'; 3008c2e8227SGreg Roach $include_any = true; 3018c2e8227SGreg Roach } 3028c2e8227SGreg Roach echo '>', $tree->getTitleHtml(), '</p>'; 3038c2e8227SGreg Roach } 3048c2e8227SGreg Roach echo 3058c2e8227SGreg Roach '<input type="submit" value="', I18N::translate('save'), '">', 3068c2e8227SGreg Roach '</form>', 3078c2e8227SGreg Roach '<hr>'; 3088c2e8227SGreg Roach 3098c2e8227SGreg Roach if ($include_any) { 3108c2e8227SGreg Roach $site_map_url1 = WT_BASE_URL . 'module.php?mod=' . $this->getName() . '&mod_action=generate&file=sitemap.xml'; 3118c2e8227SGreg Roach $site_map_url2 = rawurlencode(WT_BASE_URL . 'module.php?mod=' . $this->getName() . '&mod_action=generate&file=sitemap.xml'); 3128c2e8227SGreg Roach echo 3138c2e8227SGreg Roach '<p>', I18N::translate('To tell search engines that sitemaps are available, you should add the following line to your robots.txt file.'), '</p>', 3148c2e8227SGreg Roach '<pre>Sitemap: ', $site_map_url1, '</pre>', 3158c2e8227SGreg Roach '<hr>', 3168c2e8227SGreg Roach '<p>', I18N::translate('To tell search engines that sitemaps are available, you can use the following links.'), '</p>', 3178c2e8227SGreg Roach '<ul>', 3188c2e8227SGreg Roach // This list comes from http://en.wikipedia.org/wiki/Sitemaps 3198c2e8227SGreg Roach '<li><a target="_blank" href="http://www.bing.com/webmaster/ping.aspx?siteMap=' . $site_map_url2 . '">Bing</a></li>', 3208c2e8227SGreg Roach '<li><a target="_blank" href="http://www.google.com/webmasters/tools/ping?sitemap=' . $site_map_url2 . '">Google</a></li>', 3218c2e8227SGreg Roach '</ul>'; 3228c2e8227SGreg Roach 3238c2e8227SGreg Roach } 3248c2e8227SGreg Roach } 3258c2e8227SGreg Roach 3268c2e8227SGreg Roach /** {@inheritdoc} */ 3278c2e8227SGreg Roach public function getConfigLink() { 3288c2e8227SGreg Roach return 'module.php?mod=' . $this->getName() . '&mod_action=admin'; 3298c2e8227SGreg Roach } 3308c2e8227SGreg Roach} 331