''. xmlsitemap_url('sitemap.xml', drupal_lookup_path('alias', 'sitemap.xml') ? drupal_lookup_path('alias', 'sitemap.xml') : NULL, NULL, NULL, TRUE) .'')); case 'admin/settings/xmlsitemap/engines': return t('Configure behavior for search engines.'); case 'admin/settings/xmlsitemap/additional': return t('Set up additional links for your site map.'); } } /** * Implementation of hook_menu(). */ function xmlsitemap_menu($may_cache) { $items = array(); if ($may_cache) { $items[] = array( 'path' => 'admin/settings/xmlsitemap', 'title' => t('XML Sitemap'), 'description' => t('Configure site map.'), 'callback' => 'drupal_get_form', 'callback arguments' => array('xmlsitemap_settings_sitemap'), 'access' => user_access('administer site configuration'), ); $items[] = array( 'path' => 'admin/settings/xmlsitemap/settings', 'title' => t('Site map'), 'description' => t('Configure site map.'), 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -1, ); $items[] = array( 'path' => 'admin/settings/xmlsitemap/engines', 'title' => t('Search engines'), 'description' => t('Configure search engines.'), 'callback' => 'drupal_get_form', 'callback arguments' => array('xmlsitemap_settings_engines'), 'type' => MENU_LOCAL_TASK, 'access' => user_access('administer site configuration'), ); $items[] = array( 'path' => 'admin/settings/xmlsitemap/additional', 'title' => t('Additional'), 'description' => t('Configure additional links.'), 'callback' => 'drupal_get_form', 'callback arguments' => array('xmlsitemap_settings_additional'), 'type' => MENU_LOCAL_TASK, 'access' => user_access('administer site configuration'), 'weight' => 1, ); $items[] = array( 'path' => 'sitemap.xml', 'title' => t('Site map index'), 'callback' => '_xmlsitemap_output', 'type' => MENU_CALLBACK, 'access' => user_access('access content'), ); } else { $chunk_count = variable_get('xmlsitemap_chunk_count', 0); for ($chunk = 0; $chunk < $chunk_count; ++$chunk) { $items[] = array( 'path' => "sitemap$chunk.xml", 'title' => t('Site map !number', array('!number' => $chunk)), 'callback' => '_xmlsitemap_output', 'callback arguments' => array($chunk), 'type' => MENU_CALLBACK, 'access' => user_access('access content'), ); } } return $items; } /** * Menu callback; return site map settings form. */ function xmlsitemap_settings_sitemap() { $form['xmlsitemap_chunk_size'] = array( '#type' => 'textfield', '#title' => t('Chunk size'), '#default_value' => variable_get('xmlsitemap_chunk_size', 50000), '#size' => 10, '#maxlength' => 5, '#description' => t('This is the number of links to include in one site map. Values can range between 1 and 50,000. If the total number of links exceeds the chunk size, multiple site maps will be generated.'), '#weight' => -1, ); $form['xmlsitemap_front_page_priority'] = array( '#type' => 'select', '#title' => t('Front page priority'), '#default_value' => variable_get('xmlsitemap_front_page_priority', 1), '#options' => xmlsitemap_priority_options(), '#description' => t('This is the absolute priority for the front page.'), '#weight' => -1, ); return system_settings_form($form); } /** * Validate site map settings form. */ function xmlsitemap_settings_sitemap_validate($form_id, $form_values) { if ($form_values['xmlsitemap_chunk_size'] > 50000) { form_set_error('xmlsitemap_chunk_size', t('Cannot send more than 50,000 links at one time.')); } } /** * Submit site map settings form. */ function xmlsitemap_settings_sitemap_submit($form_id, $form_values) { system_settings_form_submit($form_id, $form_values); xmlsitemap_update_sitemap(); } /** * Menu callback; return search engine settings form. */ function xmlsitemap_settings_engines() { $form['submission'] = array( '#type' => 'fieldset', '#title' => t('Submission settings'), ); $form['submission']['xmlsitemap_submit'] = array( '#type' => 'checkbox', '#title' => t('Submit site map when updated.'), '#default_value' => variable_get('xmlsitemap_submit', FALSE), '#description' => t('If enabled, search engines will be notified of changes to the site map each time it is updated.'), ); $form['submission']['xmlsitemap_cron_submit'] = array( '#type' => 'checkbox', '#title' => t('Submit site map on cron run.'), '#default_value' => variable_get('xmlsitemap_cron_submit', FALSE), '#description' => t('If enabled, search engines will be notified of changes to the site map each time cron is run.'), ); $form['submission']['xmlsitemap_log_access'] = array( '#type' => 'checkbox', '#title' => t('Log access.'), '#default_value' => variable_get('xmlsitemap_log_access', FALSE), '#description' => t('If enabled, a watchdog entry will be made each time the site map is accessed, containing information about the requestor.'), ); $form = array_merge($form, module_invoke_all('xmlsitemap_engines', 'form')); menu_rebuild(); return system_settings_form($form); } /** * Submit search engine settings form. */ function xmlsitemap_settings_engines_submit($form_id, $form_values) { if ($form_values['xmlsitemap_root']) { $form_values['xmlsitemap_submit'] = FALSE; $form_values['xmlsitemap_log_access'] = FALSE; } system_settings_form_submit($form_id, $form_values); } /** * Menu callback; return additional links form. */ function xmlsitemap_settings_additional() { $form['xmlsitemap_additional_links_priority'] = array( '#type' => 'select', '#title' => t('Link priority'), '#default_value' => variable_get('xmlsitemap_additional_links_priority', 0.5), '#options' => xmlsitemap_priority_options(), '#description' => t('This is the default priority for additional links.'), ); $form['delete']['#tree'] = TRUE; $form['path']['#tree'] = TRUE; $form['link']['#tree'] = TRUE; $form['link']['new'] = array( '#type' => 'textfield', '#size' => 40, '#description' => t('Enter a Drupal path to add to the site map.'), '#attributes' => array('tabindex' => 1), ); $form['old_priority']['#tree'] = TRUE; $form['priority']['#tree'] = TRUE; $form['priority']['new'] = array( '#type' => 'select', '#default_value' => 'NULL', '#options' => xmlsitemap_priority_options('default'), '#attributes' => array('tabindex' => 2), ); $result = pager_query("SELECT path, priority FROM {xmlsitemap_additional} ORDER BY last_changed DESC", 50); while ($link = db_fetch_object($result)) { $id = $link->path == 'new' ? '_new' : str_replace(array('_', '%'), array('__', '_'), urlencode($link->path)); $form['delete'][$id] = array('#type' => 'checkbox', '#default_value' => FALSE); $form['path'][$id] = array('#type' => 'value', '#value' => $link->path); $form['link'][$id] = array('#value' => l($link->path, $link->path)); $priority = isset($link->priority) ? $link->priority : 'NULL'; $form['old_priority'][$id] = array('#type' => 'value', '#value' => $priority); $form['priority'][$id] = array('#type' => 'select', '#default_value' => $priority, '#options' => xmlsitemap_priority_options('default')); } $form['pager'] = array('#value' => theme('pager', NULL, 50)); return system_settings_form($form); } /** * Theme additional links form. * @ingroup themeable */ function theme_xmlsitemap_settings_additional($form) { $output = ''; $output .= drupal_render($form['xmlsitemap_additional_links_priority']); $header = array(t('Delete'), t('Path'), t('Priority')); foreach (element_children($form['link']) as $id) { $row = array(); $row[] = isset($form['delete'][$id]) ? drupal_render($form['delete'][$id]) : array_merge(array('header' => TRUE, 'valign' => 'bottom'), theme('table_select_header_cell')); $row[] = drupal_render($form['link'][$id]); $row[] = drupal_render($form['priority'][$id]); $rows[$id] = array('data' => $row, 'valign' => 'top'); } if (!empty($rows)) { $output .= theme('table', $header, $rows); } $output .= drupal_render($form); drupal_add_js('document.getElementById("edit-link-new").focus()', 'inline', 'footer'); return $output; } /** * Submit additional links form. */ function xmlsitemap_settings_additional_submit($form_id, $form_values) { $update = FALSE; if ($form_values['op'] == t('Save configuration')) { if ($form_values['xmlsitemap_additional_links_priority'] != variable_get('xmlsitemap_additional_links_priority', 0.1)) { $update = TRUE; } if (!empty($form_values['delete'])) { foreach ($form_values['delete'] as $id => $delete) { if ($delete || $form_values['path'][$id] == trim($form_values['link']['new'])) { db_query("DELETE FROM {xmlsitemap_additional} WHERE path = '%s'", $form_values['path'][$id]); unset($form_values['priority'][$id]); $update = TRUE; } } unset($form_values['delete']); } $path = trim($form_values['link']['new']); $pid = db_result(db_query("SELECT pid FROM {url_alias} WHERE src = '%s'", $path)); if (!empty($path)) { db_query(" INSERT INTO {xmlsitemap_additional} (path, pid, last_changed, priority) VALUES ('%s', %s, %d, %s) ", $path, empty($pid) ? 'NULL' : $pid, time(), $form_values['priority']['new']); unset($form_values['link'], $form_values['priority']['new']); $update = TRUE; } if (!empty($form_values['priority'])) { foreach ($form_values['priority'] as $id => $priority) { if ($priority != $form_values['old_priority'][$id]) { $pid = db_result(db_query("SELECT pid FROM {url_alias} WHERE src = '%s'", $form_values['path'][$id])); db_query(" UPDATE {xmlsitemap_additional} SET pid = %s, previously_changed = last_changed, last_changed = %d, priority = %s WHERE path = '%s' ", empty($pid) ? 'NULL' : $pid, time(), $priority, $form_values['path'][$id]); $update = TRUE; } } unset($form_values['path'], $form_values['priority'], $form_values['old_priority']); } } elseif (variable_get('xmlsitemap_additional_links_priority', 0.1) != 0.1) { if (in_array('NULL', $form_values['old_priority'])) { $update = TRUE; } unset($form_values['delete'], $form_values['path'], $form_values['link'], $form_values['old_priority'], $form_values['priority']); } system_settings_form_submit($form_id, $form_values); if ($update) { xmlsitemap_update_sitemap(); } } /** * Get an array of site map priority options. * @param $option: * If not given, the array will include priority values from 0.0 to 1.0. * - exclude: Add option to exclude item from site map. * - default: Add option to use default priority. Only for cases where a * default priority exists. * - both: Add both the default and exclude options. * @return An array of priority options. */ function xmlsitemap_priority_options($option = NULL) { if ($option == 'default' || $option == 'both') { $options['NULL'] = t('Default'); } $options['1'] = '1.0'; $values = array('0.9', '0.8', '0.7', '0.6', '0.5', '0.4', '0.3', '0.2', '0.1'); foreach ($values as $value) { $options[$value] = $value; } $options['0'] = '0.0'; if ($option == 'exclude' || $option == 'both') { $options['-1'] = t('Not in site map'); } return $options; } /** * Implementation of hook_robotstxt(). */ function xmlsitemap_robotstxt() { return array("Sitemap: ". xmlsitemap_url('sitemap.xml', drupal_lookup_path('alias', 'sitemap.xml') ? drupal_lookup_path('alias', 'sitemap.xml') : NULL, NULL, NULL, TRUE)); } /** * Menu callback; display the site map. * @param $chunk: * An integer specifying which chunk of the site map is being requested. If * not set and there is more than one chunk, display the site map index. * @return None */ function _xmlsitemap_output($chunk = NULL) { drupal_set_header('Content-type: text/xml; charset=utf-8'); global $user; $dest = file_directory_path() .'/xmlsitemap'; file_check_directory($dest, FILE_CREATE_DIRECTORY); if (isset($chunk)) { $dest .= "/sitemap$chunk.xml.gz"; $type = t('Site map @chunk', array('@chunk' => $chunk)); } else { $dest .= '/sitemap.xml.gz'; $link_count = _xmlsitemap_link_count(); $chunk_size = variable_get('xmlsitemap_chunk_size', 50000); $type = $link_count > $chunk_size ? t('Site map index') : t('Site map'); } $status = TRUE; if (!file_exists($dest) || variable_get('xmlsitemap_update', FALSE)) { $page = isset($chunk) ? $chunk : 'index'; $status = _xmlsitemap_update_cache($page); } if ($status) { if (strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') === FALSE || zlib_get_coding_type() !== FALSE || extension_loaded('eAccelerator') || (variable_get('cache', CACHE_DISABLED) != CACHE_DISABLED && empty($user->uid))) { readgzfile($dest); } else { drupal_set_header('Content-Encoding: gzip'); print file_get_contents($dest); } if (variable_get('xmlsitemap_log_access', FALSE)) { $message = array_shift(module_invoke_all('xmlsitemap_engines', 'access', $type)); $message = isset($message) ? $message : t('!sitemap downloaded by @user-agent at @address.', array( '!sitemap' => $type, '@user-agent' => $_SERVER['HTTP_USER_AGENT'], '@address' => $_SERVER['REMOTE_ADDR'], )); watchdog('xmlsitemap', $message); } } else { drupal_not_found(); } } /** * Count the total number of links in the site. * @return An integer containing the total number of links */ function _xmlsitemap_link_count() { static $count; $count = isset($count) ? $count : count(_xmlsitemap_links()); return $count; } /** * Update the cached site map files. * @return TRUE if the update was successful, FALSE otherwise. */ function _xmlsitemap_update_cache($page = NULL) { global $user; $current_user = $user; $user = user_load(array('uid' => 0)); $path = file_directory_path() .'/xmlsitemap'; file_check_directory($path, FILE_CREATE_DIRECTORY); $node_count = db_result(db_query("SELECT COUNT(*) FROM {node}")); $dest = $path .'/sitemap.xml.gz'; $link_count = _xmlsitemap_link_count(); $chunk_size = variable_get('xmlsitemap_chunk_size', 50000); $status = TRUE; if ($link_count > $chunk_size) { $data = gzencode(_xmlsitemap_output_index($link_count)); if (file_save_data($data, $dest, FILE_EXISTS_REPLACE) === 0 && ($page == 'index' || !isset($page))) { $status = FALSE; } for ($chunk = 0; $chunk < $link_count / $chunk_size; ++$chunk) { $dest = $path ."/sitemap$chunk.xml.gz"; $data = gzencode(_xmlsitemap_output_chunk($chunk)); if (file_save_data($data, $dest, FILE_EXISTS_REPLACE) === 0 && ($page == $chunk || !isset($page))) { $status = FALSE; } } } else { $chunk = 0; $data = gzencode(_xmlsitemap_output_chunk($chunk)); if (file_save_data($data, $dest, FILE_EXISTS_REPLACE) === 0 && ($page == 'index' || !isset($page))) { $status = FALSE; } } variable_set('xmlsitemap_chunk_count', $chunk); variable_set('xmlsitemap_update', FALSE); if (!$status) { drupal_set_message(t('Unable to load site map. Make sure that there is an xmlsitemap directory in your files directory and that it is writable by Drupal.'), 'error'); } $user = $current_user; return $status; } /** * Generate the site map index. * @param $link_count: * An integer containing the total number of links in the site * @return A string containing the site map index */ function _xmlsitemap_output_index($link_count) { $output = ''; $xsl_path = file_directory_path() .'/xmlsitemap/gss.xsl'; $xsl_path = file_exists($xsl_path) ? _xmlsitemap_file_create_url($xsl_path) : base_path(). drupal_get_path('module', 'xmlsitemap') .'/gss/gss.xsl'; $output .= ''."\n"; $output .= ''."\n"; $output .= ''; $previous = $chunk * $chunk_size; $links = array_slice(_xmlsitemap_links(), $previous, $chunk_size); $output .= ''. gmdate('Y-m-d\TH:i:s+00:00', array_reduce($links, '_xmlsitemap_chunk_last_change')) .''; $output .= "\n"; } $output .= ''; return $output; } /** * Compare link modification time to modification time of previous link. * @param $time: * Modification time of previous link * @param $value: * Link array * @return Most recent time */ function _xmlsitemap_chunk_last_change($time, $value) { if ($time < $value['#lastmod']) { $time = $value['#lastmod']; } return $time; } /** * Generate a chunk of the site map. * @param $chunk: * An integer specifying which chunk of the site map to display * @return A string containing a chunk of the site map */ function _xmlsitemap_output_chunk($chunk) { $output = ''; if (!ini_get('safe_mode')) { set_time_limit(240); } $xsl_path = file_directory_path() .'/xmlsitemap/gss.xsl'; $xsl_path = file_exists($xsl_path) ? _xmlsitemap_file_create_url($xsl_path) : base_path() . drupal_get_path('module', 'xmlsitemap') .'/gss/gss.xsl'; $output .= ''."\n"; $output .= ''."\n"; $output .= ' $link) { $lastmod[$key] = $link['#lastmod']; } array_multisort($lastmod, $entries); } $links = $entries; } return $links; } /** * Process an array of links. * @param $entries: An array of links to process * @return A string of formatted links */ function _xmlsitemap_format($entries) { $links = array(); foreach ($entries as $entry) { if (isset($entry['#loc'])) { $link = ' '."\n"; $link .= ' '. check_url($entry['#loc']) .''."\n"; if (isset($entry['#lastmod'])) { $link .= ' '. gmdate('Y-m-d\TH:i:s+00:00', $entry['#lastmod']) .''."\n"; } if (isset($entry['#changefreq'])) { $link .= ' '. _xmlsitemap_frequency($entry['#changefreq']) .''."\n"; } if (isset($entry['#priority']) && $entry['#priority'] <= 1 && $entry['#priority'] >= 0) { $link .= ' '. number_format($entry['#priority'], 1) .''."\n"; } $link .= ' '; $links[] = $link; } } return $links; } /** * Determine the frequency of updates to a link. * @param $interval: The number of seconds since last change * @return A string representing the update frequency according to the * sitemaps.org protocol */ function _xmlsitemap_frequency($interval) { $frequencies = array( 'always' => 3600, 'hourly' => 86400, 'daily' => 604800, 'weekly' => 2419200, 'monthly' => 29030400, 'yearly' => 100000000, 'never' => 0, ); $frequency = 'never'; if (!is_numeric($interval)) { if (isset($frequencies[$interval])) { $frequency = $interval; } } else { foreach ($frequencies as $frequency => $value) { if ($interval < $value) { break; } } } return $frequency; } /** * Implementation of hook_xmlsitemap_links(). */ function xmlsitemap_xmlsitemap_links($type = NULL, $excludes = array()) { $links = array(); if (!isset($type)) { global $base_url; $links[] = array('#loc' => "$base_url/", '#changefreq' => 'always', '#priority' => variable_get('xmlsitemap_front_page_priority', 1)); $links = array_merge($links, _xmlsitemap_additional_links()); $links = array_merge($links, module_invoke_all('gsitemap')); $links = array_merge($links, _xmlsitemap_xml_links()); if (!empty($links)) { foreach ($links as $key => $link) { $loc[$key] = $link['#loc']; } array_multisort($loc, $links); } } return $links; } /** * Get additional links. * @return An array of links. Each link is an array containing the XML * values for a site map URL. */ function _xmlsitemap_additional_links() { $links = array(); $result = db_query(" SELECT xa.*, ua.dst AS alias FROM {xmlsitemap_additional} xa LEFT JOIN {url_alias} ua ON xa.pid = ua.pid "); while ($link = db_fetch_object($result)) { $age = time() - $link->last_changed; if (!empty($link->previously_changed)) { $interval = $link->last_changed - $link->previously_changed; } else { $interval = 0; } $links[] = array( '#loc' => xmlsitemap_url($link->path, $link->alias, NULL, NULL, TRUE), '#lastmod' => $link->last_changed, '#changefreq' => max($age, $interval), '#priority' => isset($link->priority) ? $link->priority : variable_get('xmlsitemap_additional_links_priority', 0.1), ); } return $links; } /** * Extract links from site maps returned by hook_xmlsitemap_links(). * @return An array of links. */ function _xmlsitemap_xml_links() { $links = array(); $xml = module_invoke_all('xmlsitemap_links', 'xml'); $xml = array_merge($xml, module_invoke_all('gsitemap', 'xml')); foreach ($xml as $entries) { $start = strpos($entries, ''); if ($start !== FALSE) { $length = strpos($entries, '') - $start; $entries = substr($entries, $start, $length); $entries = explode('', $entries); foreach ($entries as $value) { if (($start = strpos($value, '')) !== FALSE) { $length = $start + strlen(''); $link['#loc'] = substr($value, $length, strpos($value, '') - $length); if (($start = strpos($value, '')) !== FALSE) { $length = $start + strlen(''); $t = array_shift(explode('+', substr($value, $length, strpos($value, '') - $length))); $t = explode('T', $t); $t = array_merge(explode('-', $t[0]), explode(':', $t[1])); $link['#lastmod'] = gmmktime($t[3], $t[4], $t[5], $t[1], $t[2], $t[0]); } if (($start = strpos($value, '')) !== FALSE) { $length = $start + strlen(''); $link['#changefreq'] = substr($value, $length, strpos($value, '') - $length); } if (($start = strpos($value, '')) !== FALSE) { $length = $start + strlen(''); $link['#priority'] = substr($value, $length, strpos($value, '') - $length); } $links[] = $link; } } } } return $links; } /** * Modified version of url(). We don't want to do a separate database query * for each url, so we pass the alias as an extra parameter. * @param $alias: The URL alias. Default is NULL. * @return The fully formatted URL */ function xmlsitemap_url($path = NULL, $alias = NULL, $query = NULL, $fragment = NULL, $absolute = FALSE) { if (isset($fragment)) { $fragment = "#$fragment"; } $colonpos = strpos($path, ':'); if ($colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && filter_xss_bad_protocol($path, FALSE) == check_plain($path)) { if (strpos($path, '#') !== FALSE) { list($path, $old_fragment) = explode('#', $path, 2); if (isset($old_fragment) && !isset($fragment)) { $fragment = "#$old_fragment"; } } if (isset($query)) { $path .= (strpos($path, '?') !== FALSE ? '&' : '?') . $query; } return $path . $fragment; } global $base_url; static $script; static $clean_url; $script = isset($script) ? $script : strpos($_SERVER['SERVER_SOFTWARE'], 'Apache') === FALSE ? 'index.php' : ''; $clean_url = isset($clean_url) ? $clean_url : variable_get('clean_url', FALSE); $base = ($absolute ? $base_url .'/' : base_path()); if (!empty($path) && $path != '') { $path = _xmlsitemap_get_path_alias($path, $alias); $path = drupal_urlencode($path); if (!$clean_url) { if (isset($query)) { return $base . $script .'?q='. $path .'&'. $query . $fragment; } else { return $base . $script .'?q='. $path . $fragment; } } else { if (isset($query)) { return $base . $path .'?'. $query . $fragment; } else { return $base . $path . $fragment; } } } else { if (isset($query)) { return $base . $script .'?'. $query . $fragment; } else { return $base . $fragment; } } } /** * Modified version of drupal_get_path_alias() for xmlsitemap_url(). * @param $path: An internal Drupal path * @param $alias: The URL alias. Default is NULL. * @return A processed path */ function _xmlsitemap_get_path_alias($path, $alias = NULL) { $result = $path; if (!empty($alias)) { $result = $alias; } if (function_exists('custom_url_rewrite')) { $result = custom_url_rewrite('alias', $result, $path); } if (module_exists('i18n')) { i18n_get_lang_prefix($result, TRUE); } return $result; } /** * Implementation of hook_cron(). */ function xmlsitemap_cron() { if (variable_get('xmlsitemap_cron_submit', FALSE) && variable_get('xmlsitemap_changed', FALSE)) { _xmlsitemap_ping(); } } /** * Mark the site map as changed and the cache as needing update. * @return None */ function xmlsitemap_update_sitemap() { variable_set('xmlsitemap_changed', TRUE); variable_set('xmlsitemap_update', TRUE); if (variable_get('xmlsitemap_submit', FALSE)) { _xmlsitemap_submit_on_exit(); } } /** * Schedule a call to _xmlsitemap_ping() to be run on exit. Use this * function instead of _xmlsitemap_ping() to avoid a delay in outputting * the page to the user. * @return TRUE if the function has been called previously, FALSE otherwise. */ function _xmlsitemap_submit_on_exit() { static $called = FALSE; $return = $called; $called = TRUE; return $return; } /** * Implementation of hook_exit(). */ function xmlsitemap_exit() { if (_xmlsitemap_submit_on_exit()) { _xmlsitemap_ping(); } } /** * Submit the site map to search engines. * @return None */ function _xmlsitemap_ping() { $status = _xmlsitemap_update_cache(); if ($status) { module_invoke_all('xmlsitemap_engines', 'ping'); variable_set('xmlsitemap_changed', FALSE); } } /** * @} End of "addtogroup xmlsitemap". */